/* Atlas Epoxy Floors site styles.
   Tokens are the landing page's, unchanged, because that design passed a blind
   brand-qa pass and its contrast pairs are measured in clients/atlas_epoxy_floors/tokens.json.
   Gaps are the four LAYOUT_RULES section 2 allows: 8 / 24 / 48 / 96.
   Body leading stays inside 1.4-1.7 per rule 4.2 as widened 2026-08-29. */

@font-face{font-family:"Source Serif 4";font-weight:400;font-style:normal;font-display:swap;
  src:url("fonts/source-serif-4-400-latin.woff2") format("woff2")}
@font-face{font-family:"Source Serif 4";font-weight:400;font-style:italic;font-display:swap;
  src:url("fonts/source-serif-4-400-italic-latin.woff2") format("woff2")}
@font-face{font-family:Inter;font-weight:400;font-style:normal;font-display:swap;
  src:url("fonts/inter-400-latin.woff2") format("woff2")}
@font-face{font-family:Inter;font-weight:600;font-style:normal;font-display:swap;
  src:url("fonts/inter-600-latin.woff2") format("woff2")}

:root{
  --display:"Source Serif 4",Georgia,serif;
  --body:Inter,-apple-system,"Segoe UI",sans-serif;
  --ink:#0E0E0C; --char:#1A1A18; --char2:#232320;
  --paper:#F5F1E8; --paper2:#EDE7DA; --stone:#C8C6C0;
  --muted:#66635E;      /* body and small text on cream */
  --muted-dk:#908E88;   /* the same role on dark grounds */
  --gold:#D8B761; --gold-d:#78603C;
  --field:#6F6E67;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
[id]{scroll-margin-top:80px}
body{background:var(--paper);color:var(--ink);font-family:var(--body);
  line-height:1.6;-webkit-font-smoothing:antialiased;overflow-x:hidden}
img,video{max-width:100%;display:block}
a{color:inherit}
.wrap{max-width:1240px;margin:0 auto;padding:0 24px}
@media(min-width:900px){.wrap{padding:0 48px}}

.skip{position:absolute;left:-9999px}
.skip:focus{left:24px;top:8px;background:var(--gold);color:var(--ink);
  padding:12px 24px;z-index:200}

.util{background:var(--ink);color:var(--stone);font-size:.72rem;letter-spacing:.07em}
.util .wrap{display:flex;justify-content:space-between;gap:24px;flex-wrap:wrap;
  padding-top:8px;padding-bottom:8px}
.util b{color:var(--gold);font-weight:600;white-space:nowrap}

header{position:sticky;top:0;z-index:100;background:rgba(26,26,24,.97);
  border-bottom:1px solid rgba(200,198,192,.16);color:var(--paper)}
header .wrap{display:flex;align-items:center;gap:48px;
  padding-top:12px;padding-bottom:12px}
.brand{text-decoration:none;flex:none}
.wm span{display:block;font-family:var(--display);font-size:1.15rem;
  letter-spacing:.04em;line-height:1.2}
.wm em{display:block;font-style:normal;font-size:.6rem;letter-spacing:.26em;
  text-transform:uppercase;color:var(--gold);line-height:1.2}
nav{display:flex;gap:24px;margin-left:auto;font-size:.76rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--stone);flex-wrap:wrap}
nav a{text-decoration:none;padding-bottom:4px;border-bottom:1px solid transparent}
nav a:hover{border-bottom-color:var(--gold);color:var(--paper)}
.hcall{display:flex;align-items:center;gap:24px;flex:none}
.hcall .tel{font-family:var(--display);font-size:1.1rem;color:var(--paper);
  text-decoration:none;white-space:nowrap;display:inline-block;padding:4px 0}
.btn{display:inline-block;text-decoration:none;font-size:.76rem;letter-spacing:.14em;
  text-transform:uppercase;font-weight:600;padding:12px 24px;white-space:nowrap;
  background:var(--gold);color:var(--ink);border:1px solid var(--gold);
  font-family:var(--body)}
.btn.ghost{background:transparent;color:var(--paper);border-color:var(--muted-dk)}
.btn.big{padding:16px 32px;font-size:.8rem}
@media(max-width:900px){
  header .wrap{flex-wrap:wrap;row-gap:8px;gap:24px}
  /* The CTA is shown by the mobile header block; hiding it here made the
     primary conversion action on a lead-gen site vanish on a phone. */
  #sitenav{width:100%;margin-left:0}
}
@media(max-width:400px){.btn{white-space:normal}}

.crumb{background:var(--paper2);font-size:.72rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--muted)}
.crumb .wrap{padding-top:12px;padding-bottom:12px}
.crumb a{text-decoration:none;border-bottom:1px solid var(--gold-d)}
.crumb span{color:var(--gold-d);padding:0 8px}

section{padding-top:48px;padding-bottom:48px}
@media(min-width:900px){section{padding-top:80px;padding-bottom:80px}}
.hero-sm{background:var(--char);color:var(--paper)}
.kicker{font-size:.68rem;letter-spacing:.26em;text-transform:uppercase;
  color:var(--gold);margin-bottom:16px}
.hero-sm .kicker{color:var(--gold)}
h1{font-family:var(--display);font-weight:400;
  font-size:clamp(2rem,4vw,3.2rem);line-height:1.06;letter-spacing:-.015em;
  max-width:20ch}
h2{font-family:var(--display);font-weight:400;
  font-size:clamp(1.5rem,2.6vw,2.1rem);line-height:1.15;letter-spacing:-.01em;
  max-width:26ch}
h3{font-family:var(--display);font-weight:400;font-size:1.15rem;line-height:1.3;
  margin-top:24px}
/* The BLUF. First block under the H1, and the one an answer engine lifts. */
.bluf{margin-top:24px;font-size:1.05rem;line-height:1.65;color:var(--stone);
  max-width:62ch}
.bluf2{margin-top:16px;font-size:1rem;line-height:1.65;color:var(--muted);
  max-width:62ch}
.sec p{margin-top:8px;font-size:.95rem;line-height:1.65;color:var(--muted);
  max-width:70ch}
.sec h2{color:var(--ink)}
.cta{margin-top:48px;display:flex;gap:24px;flex-wrap:wrap;align-items:center}

.faq{background:var(--paper2)}
.faq details{border-top:1px solid var(--stone);padding:16px 0}
.faq summary{cursor:pointer;font-family:var(--display);font-size:1.1rem;
  line-height:1.3}
.faq details p{margin-top:12px;font-size:.95rem;line-height:1.65;
  color:var(--muted);max-width:70ch}
.faq h2{margin-bottom:24px}

.rel .relgrid{list-style:none;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:24px;
  margin-top:24px;align-items:start}
.rel .relgrid a{text-decoration:none;display:block;border-top:2px solid var(--gold-d);
  padding-top:16px;font-family:var(--display);font-size:1.1rem;line-height:1.3}
.rel .relgrid a:hover{border-top-color:var(--gold)}

.close{background:var(--ink);color:var(--paper);text-align:left}
.close h2{color:var(--paper)}
.close .bluf{color:var(--stone);margin-bottom:24px}

footer{background:var(--char);color:var(--stone);font-size:.86rem;
  padding-top:48px}
footer .fgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
  gap:24px;line-height:1.65}
footer b{color:var(--paper);font-weight:600}
footer a{text-decoration:none}
footer .hic{color:var(--gold)}
.legal{border-top:1px solid rgba(200,198,192,.16);font-size:.72rem;margin-top:48px;
  color:var(--muted-dk)}
.legal .wrap{padding-top:16px;padding-bottom:32px;line-height:1.65}
.legal b{color:var(--gold)}

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

/* --- PLACEHOLDER PRICING -----------------------------------------------------
   Visible on purpose. Every figure inside .mock is invented and none of it is
   Atlas's. build_site.py refuses --production while these render. */
.mockbar{background:#7A1D1D;color:#FFF4F4;font-size:.76rem;letter-spacing:.06em;
  font-weight:600}
.mockbar .wrap{padding-top:8px;padding-bottom:8px;line-height:1.65}
.mock{border-bottom:2px dashed currentColor;padding-bottom:1px;
  background:rgba(216,183,97,.16);font-style:italic}

/* --- rate table --------------------------------------------------------------
   The only table on the site. A price list is the one place where tabular is
   the honest presentation rather than a layout crutch. */
.tablewrap{overflow-x:auto;margin-top:24px}
.rates{border-collapse:collapse;width:100%;min-width:520px;font-size:.95rem}
.rates caption{caption-side:bottom;margin-top:16px;font-size:.78rem;
  line-height:1.65;color:var(--muted);text-align:left;max-width:70ch}
.rates th,.rates td{text-align:left;padding:16px 24px 16px 0;
  border-bottom:1px solid var(--stone);line-height:1.65}
.rates thead th{font-family:var(--body);font-size:.68rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--gold-d);border-bottom:2px solid var(--gold-d);
  padding-bottom:12px}
.rates tbody th{font-weight:400;color:var(--ink)}
/* A town name in a row header is a link to that town's page, and inline it
   was a 19px target. Filling the cell it already occupies makes it 51px
   without moving a pixel of layout. WCAG 2.2 SC 2.5.8. */
.rates tbody th a{display:block;padding:16px 0;margin:-16px 0}
.rates tbody td{font-family:var(--display);font-size:1.15rem;color:var(--ink)}

/* --- quote form --------------------------------------------------------------
   Dark ground, same as the landing page. Field borders use --field, measured at
   3.775:1 on --ink, against the 3:1 floor for a non-text control. */
.quote{background:var(--char);color:var(--paper)}
.quote h2{color:var(--paper)}
.quote .bluf2{color:var(--stone)}
.quote .kicker{color:var(--gold)}
.qgrid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:96px;
  align-items:start}
@media(max-width:900px){.qgrid{grid-template-columns:minmax(0,1fr);gap:48px}}
.quote form{background:var(--ink);padding:32px;border:1px solid rgba(200,198,192,.2)}
.f{margin-bottom:24px}
.f2{display:grid;grid-template-columns:1fr 1fr;gap:24px}
@media(max-width:620px){.f2{grid-template-columns:1fr}}
.f label{display:block;font-size:.66rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--gold);margin-bottom:8px}
.f input,.f select,.f textarea{width:100%;background:var(--char);color:var(--paper);
  border:1px solid var(--field);padding:12px;font-family:var(--body);
  font-size:.95rem;line-height:1.6}
.f textarea{resize:vertical}
.f input:focus,.f select:focus,.f textarea:focus{outline:2px solid var(--gold);
  outline-offset:2px}
.quote button.btn{width:100%;cursor:pointer}
.consent{font-size:.7rem;color:var(--muted-dk);line-height:1.65;margin-top:24px}
a.inline{color:var(--gold);text-decoration:none;border-bottom:1px solid var(--gold-d)}

/* --- reviews ---------------------------------------------------------------- */
.rvgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  gap:24px;margin-top:24px;align-items:start}
.rv{background:var(--paper2);border-left:2px solid var(--gold-d);padding:24px}
.rv blockquote{font-family:var(--display);font-size:1.05rem;line-height:1.45;
  color:var(--ink)}
.rv blockquote::before{content:"\201C"}
.rv blockquote::after{content:"\201D"}
.rv figcaption{margin-top:16px;font-size:.7rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted);line-height:1.65}
.rv figcaption b{display:block;color:var(--gold-d);letter-spacing:.06em;
  font-size:.78rem;text-transform:none;font-weight:600}

/* --- gallery ---------------------------------------------------------------- */
.gallery{background:var(--char);color:var(--paper)}
.ggrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  gap:24px;margin-top:24px;align-items:start}
.gitem{background:var(--ink)}
.gitem img,.gitem video{width:100%;height:auto;aspect-ratio:3/2;object-fit:cover;
  display:block}
.gitem figcaption{padding:16px;font-size:.86rem;line-height:1.65;color:var(--stone)}
.filmToggle{background:transparent;border:1px solid var(--muted-dk);
  color:var(--paper);font-family:var(--body);font-size:.7rem;letter-spacing:.14em;
  text-transform:uppercase;padding:12px 24px;cursor:pointer;margin-bottom:24px}
.filmToggle:hover{border-color:var(--gold);color:var(--gold)}

/* --- mega menu ---------------------------------------------------------------
   Every spoke is a real link in the DOM on every page. The panel opens on hover
   and on focus-within only, so nothing depends on script and a crawler sees all
   35 pages from any one of them. */
.brand{display:flex;align-items:center;gap:8px}
.brand img{height:48px;width:auto}
nav{display:flex;gap:24px;margin-left:auto;flex-wrap:wrap;align-items:center}
.navitem{position:relative}
.navtop{display:inline-block;text-decoration:none;font-size:.76rem;
  letter-spacing:.1em;text-transform:uppercase;color:var(--stone);
  padding:12px 0;border-bottom:1px solid transparent}
.navitem:hover .navtop,.navitem:focus-within .navtop{color:var(--paper);
  border-bottom-color:var(--gold)}
.navpanel{position:absolute;left:-24px;top:100%;min-width:260px;z-index:120;
  background:var(--ink);border:1px solid rgba(200,198,192,.2);
  opacity:0;visibility:hidden}
.navitem:hover .navpanel,.navitem:focus-within .navpanel{opacity:1;
  visibility:visible}
.navpanel-in{display:flex;flex-direction:column;padding:16px 0}
.navpanel a{text-decoration:none;color:var(--stone);font-size:.86rem;
  letter-spacing:.02em;text-transform:none;padding:8px 24px;line-height:1.65}
.navpanel a:hover,.navpanel a:focus{background:var(--char);color:var(--gold)}
@media(max-width:900px){
  /* No hover on touch, so the panels are simply open. The nav becomes a
     sitemap, which is the honest mobile answer and keeps every link reachable. */
  #sitenav{width:100%;margin-left:0;flex-direction:column;align-items:stretch;gap:8px}
  .navpanel{position:static;opacity:1;visibility:visible;border:0;
    background:transparent;min-width:0}
  .navpanel-in{padding:0 0 8px 16px}
  .navpanel a{padding:8px 0}
}

/* --- trust band -------------------------------------------------------------
   The client's own claims from the client's own site. Holding them back cost
   the site its four strongest signals. */
.trust{background:var(--paper2);color:var(--ink);
  border-bottom:1px solid var(--stone);font-size:.78rem;letter-spacing:.04em}
.trust .wrap{display:flex;flex-wrap:wrap;gap:24px;justify-content:space-between;
  padding-top:12px;padding-bottom:12px;line-height:1.65}
.trust b{color:var(--gold-d);font-weight:600}

/* --- offers ------------------------------------------------------------------ */
.offergrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  gap:24px;margin-top:24px;align-items:start}
.offergrid .offer{background:var(--ink);color:var(--paper);padding:24px;
  border-left:2px solid var(--gold)}
.offergrid .offer b{display:block;font-family:var(--display);font-size:2rem;
  color:var(--gold);line-height:1.15;margin-bottom:8px}
.offergrid .offer span{font-size:.9rem;line-height:1.65;color:var(--stone)}

/* --- slab readiness check ---------------------------------------------------- */
.slabtool{margin-top:24px;max-width:70ch}
.slabtool .q{border:0;border-top:1px solid var(--stone);padding:24px 0}
.slabtool legend{font-family:var(--display);font-size:1.15rem;line-height:1.3;
  padding:0;display:flex;gap:12px;align-items:baseline}
.slabtool .qn{font-size:.78rem;color:var(--gold-d);font-family:var(--body);
  font-weight:600}
.slabtool .opts{display:grid;
  grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:8px;margin-top:16px}
.slabtool .opt{display:flex;gap:8px;align-items:flex-start;cursor:pointer;
  font-size:.95rem;line-height:1.65;color:var(--muted);padding:8px;
  border:1px solid transparent}
.slabtool .opt:hover{border-color:var(--stone)}
.slabtool input[type=radio]{margin-top:5px;accent-color:var(--gold-d);flex:none}
.slabtool button{margin-top:24px}
.slabresult{margin-top:32px;padding:32px;background:var(--char);color:var(--paper);
  border-left:2px solid var(--gold);max-width:70ch}
.slabresult h3{font-family:var(--display);font-size:1.5rem;line-height:1.15;
  color:var(--gold);margin:0}
.slabresult p{margin-top:16px;font-size:.95rem;line-height:1.65;color:var(--stone)}
.slabresult ul{margin-top:16px;padding-left:24px;color:var(--stone);
  font-size:.95rem;line-height:1.65}
.slabresult .btn{margin-top:24px}

/* --- footer sitemap ---------------------------------------------------------- */
footer .fgrid a{color:var(--stone);line-height:1.9}
footer .fgrid a:hover{color:var(--gold)}

/* =============================================================================
   DESIGN PASS 2
   `[WALT 2026-08-29]` the header wraps, the logo is too small, the cards are all
   different heights, there is nothing to hover, and the whole thing is static.
   All five are the same underlying problem: it was built to validate, not to
   feel like anything. This block is the fix.
   ========================================================================== */

/* --- header: one line, and a mark that carries the brand --------------------
   The phone came out of the main bar. It was in the utility strip above and in
   the trust band below as well, and carrying it three times is what pushed the
   nav onto a second line. */
header .wrap{display:flex;align-items:center;gap:32px;flex-wrap:nowrap;
  padding-top:8px;padding-bottom:8px;min-height:84px}
.brand{flex:none;display:flex;align-items:center;gap:12px;text-decoration:none;
  position:relative;z-index:130}
/* Big, and hanging below the header into the trust band. The overlap is the
   point: it gives the mark somewhere to be other than "in a bar". */
.brand img{height:104px;width:auto;margin-bottom:-28px;
  filter:drop-shadow(0 6px 18px rgba(0,0,0,.45));
  transition:transform .35s cubic-bezier(.2,.7,.3,1)}
.brand:hover img{transform:translateY(-2px) scale(1.03)}
.wm span{font-size:1.5rem;letter-spacing:.06em}
.wm em{font-size:.66rem;letter-spacing:.3em}
nav{gap:20px;flex-wrap:nowrap}
.navtop{font-size:.7rem;letter-spacing:.08em;white-space:nowrap;
  padding:14px 0;position:relative}
/* The underline grows from the left rather than appearing. Small, and it is the
   difference between a link and a thing that responds to you. */
.navtop::after{content:"";position:absolute;left:0;bottom:8px;height:1px;width:0;
  background:var(--gold);transition:width .28s cubic-bezier(.2,.7,.3,1)}
.navitem:hover .navtop::after,.navitem:focus-within .navtop::after{width:100%}
.navtop{border-bottom:0}
.navitem:hover .navtop,.navitem:focus-within .navtop{border-bottom-color:transparent}
.navpanel{transform:translateY(6px);
  transition:opacity .22s ease,transform .22s cubic-bezier(.2,.7,.3,1),
             visibility .22s}
.navitem:hover .navpanel,.navitem:focus-within .navpanel{transform:translateY(0)}
.navpanel a{transition:background .18s ease,color .18s ease,padding-left .18s ease}
.navpanel a:hover,.navpanel a:focus{padding-left:30px}

@media(max-width:1180px){
  header .wrap{gap:24px}
  .brand img{height:84px;margin-bottom:-22px}
  .wm span{font-size:1.25rem}
  #sitenav{gap:14px}
  .navtop{font-size:.64rem;letter-spacing:.06em}
}
@media(max-width:980px){
  header .wrap{flex-wrap:wrap;row-gap:8px;min-height:0}
  .brand img{height:72px;margin-bottom:-16px}
  #sitenav{width:100%;margin-left:0;flex-direction:column;align-items:stretch;
    gap:8px;flex-wrap:wrap}
  .navtop{padding:10px 0}
  .navtop::after{display:none}
}

/* --- buttons ---------------------------------------------------------------- */
.btn{transition:background .22s ease,color .22s ease,border-color .22s ease,
  transform .22s cubic-bezier(.2,.7,.3,1),box-shadow .22s ease}
.btn:hover{background:var(--gold-l);border-color:var(--gold-l);
  transform:translateY(-2px);box-shadow:0 10px 24px rgba(0,0,0,.28)}
.btn.ghost:hover{background:transparent;border-color:var(--gold);color:var(--gold);
  box-shadow:none}
a.inline{transition:color .2s ease}
a.inline:hover{color:var(--gold-l)}

/* --- cards: same height, every grid -----------------------------------------
   They were sizing to their text, so a three-card row had three heights. Every
   card grid now stretches its items and every card is a column with its meat
   pushed to the top, so the bottoms line up whatever the copy does. */
.rvgrid,.offergrid,.ggrid,.relgrid,.riskGrid{align-items:stretch}
.rvgrid > *,.offergrid > *,.ggrid > *,.relgrid > *{
  display:flex;flex-direction:column;height:100%}
.rv blockquote{flex:1 0 auto}
.gitem figcaption{flex:1 0 auto}
.offergrid .offer span{flex:1 0 auto}
.rel .relgrid a{justify-content:flex-start;min-height:96px}

/* --- hover on every card ----------------------------------------------------- */
.rv,.offergrid .offer,.gitem,.rel .relgrid a{
  transition:transform .3s cubic-bezier(.2,.7,.3,1),box-shadow .3s ease,
             border-color .3s ease}
.rv:hover,.offergrid .offer:hover,.gitem:hover{
  transform:translateY(-4px);box-shadow:0 16px 36px rgba(0,0,0,.18)}
.rel .relgrid a:hover{transform:translateY(-4px)}
.gitem{overflow:hidden}
.gitem img,.gitem video{transition:transform .5s cubic-bezier(.2,.7,.3,1)}
.gitem:hover img,.gitem:hover video{transform:scale(1.05)}
.rates tbody tr{transition:background .2s ease}
.rates tbody tr:hover{background:var(--paper2)}
.faq details{transition:border-color .2s ease}
.faq summary{transition:color .2s ease}
.faq summary:hover{color:var(--gold-d)}
.slabtool .opt{transition:border-color .2s ease,background .2s ease,
  transform .2s cubic-bezier(.2,.7,.3,1)}
.slabtool .opt:hover{background:var(--paper2);transform:translateX(2px)}
.trust div{transition:color .2s ease}

/* --- motion: sections arrive rather than being there ------------------------
   One idea, used consistently: a short rise and fade as a block enters. It is
   applied by IntersectionObserver adding .in, so anything that never scrolls
   into view is still visible, and prefers-reduced-motion turns the whole thing
   off rather than speeding it up. */
.reveal{opacity:0;transform:translateY(18px);
  transition:opacity .6s ease,transform .6s cubic-bezier(.2,.7,.3,1)}
.reveal.in{opacity:1;transform:none}
.reveal-stagger > *{opacity:0;transform:translateY(14px);
  transition:opacity .5s ease,transform .5s cubic-bezier(.2,.7,.3,1)}
.reveal-stagger.in > *{opacity:1;transform:none}
.reveal-stagger.in > *:nth-child(2){transition-delay:.07s}
.reveal-stagger.in > *:nth-child(3){transition-delay:.14s}
.reveal-stagger.in > *:nth-child(4){transition-delay:.21s}
.reveal-stagger.in > *:nth-child(5){transition-delay:.28s}
.reveal-stagger.in > *:nth-child(6){transition-delay:.35s}

@media(prefers-reduced-motion:reduce){
  .reveal,.reveal-stagger > *{opacity:1 !important;transform:none !important;
    transition:none !important}
  .brand img,.btn,.rv,.offergrid .offer,.gitem,.gitem img,.gitem video,
  .rel .relgrid a,.navpanel,.navtop::after,.slabtool .opt{
    transition:none !important}
  .btn:hover,.rv:hover,.offergrid .offer:hover,.gitem:hover,
  .rel .relgrid a:hover{transform:none !important}
}

/* Hover has to beat the reveal's resting state.
   `.reveal-stagger.in > *` sets transform:none and carries the same specificity
   as `.rv:hover`, so once a grid had revealed its cards stopped responding to
   the mouse. Caught by measuring the computed transform before and after a
   hover rather than by looking at it. These rules are the same effects, scoped
   past the reveal. */
.reveal-stagger.in > .rv:hover,
.reveal-stagger.in > .offer:hover,
.reveal-stagger.in > .gitem:hover,
.reveal.in .rv:hover,
.reveal.in .gitem:hover{
  transform:translateY(-4px);box-shadow:0 16px 36px rgba(0,0,0,.18)}
.reveal-stagger.in > a:hover{transform:translateY(-4px)}

@media(prefers-reduced-motion:reduce){
  .reveal-stagger.in > .rv:hover,
  .reveal-stagger.in > .offer:hover,
  .reveal-stagger.in > .gitem:hover,
  .reveal.in .rv:hover,
  .reveal.in .gitem:hover,
  .reveal-stagger.in > a:hover{transform:none !important}
}

/* The logo hangs into the trust band, so the band has to start clear of it.
   Without this the mark sits on top of the rating and neither is readable. */
@media(min-width:981px){
  .trust .wrap,.proofband .wrap{padding-left:160px}
}
@media(min-width:1181px){
  .trust .wrap,.proofband .wrap{padding-left:196px}
}

/* Hero with an image. The right half was empty on a trade whose product is
   entirely visual, which is the worst possible place to have nothing. */
.hero-sm.has-img .wrap{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  gap:96px;align-items:center}
.herofig{position:relative;overflow:hidden;aspect-ratio:4/5;background:var(--ink)}
.herofig img{width:100%;height:100%;object-fit:cover;
  transition:transform .8s cubic-bezier(.2,.7,.3,1)}
.hero-sm.has-img:hover .herofig img{transform:scale(1.04)}
.herofig::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(14,14,12,0) 55%,rgba(14,14,12,.45) 100%)}
@media(max-width:900px){
  .hero-sm.has-img .wrap{grid-template-columns:minmax(0,1fr);gap:48px}
  .herofig{aspect-ratio:16/10}
}
@media(prefers-reduced-motion:reduce){
  .herofig img,.hero-sm.has-img:hover .herofig img{transition:none;transform:none}
}

/* Five trust items have to fit beside the logo clearance on one line. At the
   previous size they wrapped, which put the HIC on a row of its own and made
   the band look broken rather than deliberate. */
@media(min-width:981px){
  .trust{font-size:.72rem;letter-spacing:.02em}
  .trust .wrap{gap:20px}
}
/* nowrap only where there is room for it. Forcing one line at 1024 pushed the
   band 57px past the viewport, which is worse than two tidy rows. */
@media(min-width:1281px){
  .trust .wrap{flex-wrap:nowrap;white-space:nowrap}
}

/* --- footer: a business record, not a sitemap ------------------------------- */
footer .fgrid{grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:48px}
.fnap address{font-style:normal;margin-top:8px;line-height:1.65}
.fnap .ftel{display:block;margin-top:16px;font-family:var(--display);
  font-size:1.3rem;color:var(--paper);letter-spacing:.01em}
.fnap .fmail{display:block;margin-top:4px;color:var(--stone);font-size:.8rem}
.fnap .ftel:hover,.fnap .fmail:hover{color:var(--gold)}
.fnote{display:block;margin-top:8px;color:var(--muted-dk);font-size:.78rem}

/* =============================================================================
   MOBILE NAV
   `[WALT 2026-08-29]` "the menu does not collapse on mobile it overlays and
   take over the whole page."

   Measured at 390px before this: header 1745px tall, nav 1656px of it, and the
   page content starting 2083px down on an 844px viewport. Nearly two and a half
   screens of menu before anything else. There was no toggle at all, because I
   left the panels open and called it the honest mobile answer. It was not.

   PROGRESSIVE ENHANCEMENT, DELIBERATELY

   The collapse only applies once JS has stamped .js on <html>. Without script
   the nav stays fully open and every link works, which is ugly and functional
   rather than tidy and broken. A menu that needs JS to open is a menu that is
   sometimes not there.
   ========================================================================== */

.navtoggle{display:none}

@media(max-width:980px){
  /* Ordering and the wrap now live in the consolidated block further down.
     What stays here is the toggle's own appearance and the panel behaviour. */

  /* The button only exists once script is running, because without script the
     nav is already open and a toggle that does nothing is worse than none. */
  .js .navtoggle{display:flex;align-items:center;gap:8px;
    background:transparent;border:1px solid var(--muted-dk);color:var(--paper);
    font-family:var(--body);font-size:.7rem;letter-spacing:.14em;
    text-transform:uppercase;padding:12px 16px;cursor:pointer;min-height:44px}
  .js .navtoggle:hover{border-color:var(--gold);color:var(--gold)}
  .js .navtoggle .bars{position:relative;width:18px;height:2px;
    background:currentColor;display:block}
  .js .navtoggle .bars::before,.js .navtoggle .bars::after{content:"";
    position:absolute;left:0;width:18px;height:2px;background:currentColor;
    transition:transform .25s ease,top .25s ease}
  .js .navtoggle .bars::before{top:-6px}
  .js .navtoggle .bars::after{top:6px}
  .js .navtoggle[aria-expanded="true"] .bars{background:transparent}
  .js .navtoggle[aria-expanded="true"] .bars::before{top:0;transform:rotate(45deg)}
  .js .navtoggle[aria-expanded="true"] .bars::after{top:0;transform:rotate(-45deg)}

  /* Collapsed by default with script, and it scrolls rather than growing the
     header, so the menu can never be taller than the screen again. */
  .js #sitenav{display:none;flex-direction:column;align-items:stretch;gap:0;
    max-height:calc(100vh - 160px);overflow-y:auto;
    border-top:1px solid rgba(200,198,192,.16);margin-top:8px}
  .js #sitenav.open{display:flex}

  .navitem{border-bottom:1px solid rgba(200,198,192,.1);position:relative}
  .navtop{padding:16px 48px 16px 0;display:block;font-size:.8rem}
  .navtop::after{display:none}

  /* THE WHOLE ROW IS THE CONTROL.

     `[WALT 2026-08-30]` "I dont like the way the menu works its confusing it
     should just be an accordion expands and is scrollable."

     It was confusing because the row had two targets doing opposite things.
     The label was a link, so tapping the word navigated away and shut the
     menu, and only a 48px chevron opened the group. The button now covers the
     row and the label underneath is inert, so a tap anywhere expands. The hub
     link is the first entry inside the panel instead. */
  /* Bounded to the row, NOT inset:0. .navitem is the positioning parent and it
     contains the expanded panel, so inset:0 grew the button over the panel and
     swallowed every tap meant for a link inside it. Measured: the "All
     surfaces" hub link was covered by the button that opened the group. */
  .js .navsub{position:absolute;top:0;left:0;right:0;height:52px;
    background:transparent;border:0;cursor:pointer;padding:0;z-index:2}
  .js .navtop{pointer-events:none}
  .js .navsub::before{content:"";position:absolute;right:18px;top:21px;
    width:9px;height:9px;border-right:2px solid var(--stone);
    border-bottom:2px solid var(--stone);transform:rotate(45deg);
    transition:transform .25s ease}
  .js .navsub:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}
  .js .navitem:has(.navsub[aria-expanded="true"]) .navtop{color:var(--gold)}
  .js .navsub[aria-expanded="true"]::before{transform:rotate(-135deg);
    border-color:var(--gold)}
  .js .navpanel{display:none;position:static;opacity:1;visibility:visible;
    border:0;background:rgba(0,0,0,.22);min-width:0;transform:none}
  .js .navpanel.open{display:block}
  .js .navpanel-in{padding:4px 0 8px 16px}
  .js .navpanel a{padding:12px 0;font-size:.86rem;min-height:44px;
    display:flex;align-items:center}
  /* The hub link, which the row used to be. Marked so it does not read as
     just another sibling of the pages under it. */
  .js .navhub{color:var(--gold);letter-spacing:.06em;
    border-bottom:1px solid rgba(200,198,192,.12)}
}

/* No script: the panels stay open, which is the pre-existing behaviour and is
   at least navigable. The toggle and the chevrons are never shown. */
@media(max-width:980px){
  html:not(.js) .navsub{display:none}
}

@media(min-width:981px){
  /* The disclosure buttons are mobile-only. On desktop the hover panel does
     the work and a second control would be noise. */
  .navsub{display:none}
}

/* The header CTA was hidden below 900px by an early rule, so on a phone the
   primary conversion action on a lead-generation site did not exist. It is
   back, compact, and it sits beside the menu button rather than competing with
   it for the row. */
@media(max-width:980px){
  .hcall{order:2;margin-left:auto;gap:8px}
  .hcall .btn{display:inline-block;padding:12px 14px;font-size:.66rem;
    letter-spacing:.1em;min-height:44px;line-height:20px}
}
@media(max-width:420px){
  /* Brand and CTA sizing moved to the consolidated mobile header block, which
     carries the margin-bottom:0 that stops the logo overhanging the buttons.
     Setting height here again without it reintroduced the overlap. */
}

/* The trust band stacked into five rows on a phone, roughly 440px of claims
   before the hero. All five are worth keeping, so on mobile it becomes one
   scrollable line instead of five stacked ones. */
@media(max-width:980px){
  .trust .wrap{flex-wrap:nowrap;overflow-x:auto;gap:20px;white-space:nowrap;
    padding-top:10px;padding-bottom:10px;
    scrollbar-width:none;-ms-overflow-style:none}
  .trust .wrap::-webkit-scrollbar{display:none}
  .trust .wrap > div{flex:0 0 auto}
  .trust{font-size:.72rem;
    /* a fade on the right edge so it reads as scrollable rather than cut off */
    background:linear-gradient(90deg,var(--paper2) 90%,rgba(237,231,218,.6) 100%)}
}

/* Controls the viewport gate found under 44px. Both are cheap to fix and there
   is no reason for a control to be smaller than a thumb. */
.navpanel a{min-height:44px;display:flex;align-items:center}
/* 46, not 44: measured at exactly 44px and failed check_viewports at
   414px. Same rounding lesson as .spbtn. */
.filmToggle{min-height:46px}


/* ============================================================================
   CONVERSION COMPONENTS
   `[WALT 2026-08-29]` supplied seven reference sites and a structural spec:
   split hero with an inline lead form, proof band, statistics strip, filterable
   spaces grid, system comparison, three-step process and finish swatches.

   Every structure is here. The numbers inside them are Atlas's real ones.
   content_components.py records which of the spec's figures Atlas does not
   have and therefore does not claim.
   ========================================================================= */

/* --- hero with the lead form ------------------------------------------------
   The reference sites put the form in the hero rather than behind a click, over
   a photograph rather than a flat ground. Keeping Brad's floor as the backdrop
   means the form does not cost us the one thing this trade sells on. */
.hero-sm.has-form{position:relative;background:var(--ink)}
.hero-sm.has-form::before{content:"";position:absolute;inset:0;
  background-image:var(--herobg);background-size:cover;background-position:center;
  opacity:.72}
.hero-sm.has-form::after{content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,rgba(14,14,12,.94) 0%,rgba(14,14,12,.72) 58%,
    rgba(14,14,12,.58) 100%)}
.hero-sm.has-form .wrap{position:relative;z-index:1;
  display:grid;grid-template-columns:minmax(0,1.02fr) minmax(0,.98fr);
  gap:64px;align-items:center}
.hero-sm.has-form .herotext{max-width:none}

/* The three-bullet value list the spec asks for. Each line is a fact Atlas can
   stand behind: the preparation, Brad's own warranty, his own quote promise. */
.hbul{list-style:none;margin:32px 0 0;display:grid;gap:12px}
.hbul li{position:relative;padding-left:24px;
  font-size:.95rem;line-height:1.6;color:var(--paper2)}
.hbul li::before{content:"";position:absolute;left:0;top:8px;
  width:8px;height:8px;background:var(--gold);transform:rotate(45deg)}
.hbul b{color:var(--paper)}

/* --- the lead form ---------------------------------------------------------
   A card on the dark hero. Field contrast is the measured --field border on
   --char2, the same pair the quote form already uses. */
.leadform{background:var(--char2);padding:32px;
  border:1px solid var(--field);align-self:start}
.lf-top{margin-bottom:24px}
.lf-top b{display:block;font-family:var(--display);font-size:1.5rem;
  font-weight:400;color:var(--paper);line-height:1.4}
.lf-top span{display:block;margin-top:4px;font-size:.85rem;line-height:1.6;
  color:var(--gold)}
.leadform .f{margin-bottom:16px}
.leadform .f2{gap:16px}
.leadform label{display:block;font-size:.7rem;letter-spacing:.12em;
  text-transform:uppercase;color:var(--stone);margin-bottom:8px}
.leadform input,.leadform select{width:100%;min-height:44px;padding:8px 12px;
  background:var(--ink);color:var(--paper);border:1px solid var(--field);
  font-family:var(--body);font-size:.95rem;line-height:1.6}
.leadform input:focus,.leadform select:focus{outline:2px solid var(--gold);
  outline-offset:2px}
.leadform .btn{width:100%;text-align:center;min-height:44px}

/* Consent is a real checkbox, unticked. A pre-ticked box is not consent and the
   FCC has said so. legal/COMPLIANCE.md, TCPA. */
/* .check is itself a <label>, so `.leadform label` (0,1,1) beat a bare `.check`
   (0,1,0) and the consent sentence rendered in the uppercase field-label
   treatment. Scoping it to .leadform .check takes the specificity back. */
.leadform .check{display:flex;gap:12px;align-items:flex-start;margin:0 0 24px;
  cursor:pointer;min-height:44px;
  text-transform:none;letter-spacing:normal;font-size:.8rem;color:var(--stone)}
.check input{flex:none;width:24px;height:24px;margin-top:4px;
  accent-color:var(--gold)}
.check span{font-size:.8rem;line-height:1.6;color:var(--stone)}

@media(max-width:900px){
  .hero-sm.has-form .wrap{grid-template-columns:minmax(0,1fr);gap:40px}
  .leadform{padding:24px}
}

/* --- social proof band -----------------------------------------------------
   The spec wanted Google, HomeAdvisor and a BBB A+ badge. Atlas has a Google
   profile and neither of the other two, so this shows what exists. */
.proofband{background:var(--ink);border-top:1px solid var(--gold-d)}
.proofband .wrap{display:grid;grid-template-columns:repeat(4,1fr);gap:24px;
  padding-top:24px;padding-bottom:24px}
.pb{display:flex;flex-direction:column;gap:4px;text-align:center}
.pb b{font-family:var(--display);font-size:1.15rem;color:var(--gold);
  font-weight:400;line-height:1.4;letter-spacing:.04em}
.pb span{font-size:.8rem;line-height:1.6;color:var(--stone)}
.pb span b{font-family:var(--body);font-size:.8rem;color:var(--paper)}
@media(max-width:760px){
  .proofband .wrap{grid-template-columns:1fr 1fr;gap:16px}
}

/* --- statistics strip ------------------------------------------------------
   Five anchors, all documented. Square feet sealed and a five-figure review
   count are what the reference sites lead with and Atlas has neither, so they
   are absent rather than estimated. */
.statstrip{background:var(--paper2);border-top:1px solid var(--stone);
  border-bottom:1px solid var(--stone)}
.statstrip .wrap{display:grid;grid-template-columns:repeat(5,1fr);gap:24px;
  grid-template-rows:auto auto auto;
  padding-top:48px;padding-bottom:48px}
/* Subgrid so the figure, the label and the caption each sit on one shared
   baseline across all five, however many lines a label takes. min-height on
   the label is the fallback where subgrid is unavailable: it gets the row
   close rather than exact. */
.st{text-align:center;display:grid;grid-row:span 3;
  grid-template-rows:subgrid}
.st b{display:block;font-family:var(--display);font-size:3rem;font-weight:400;
  color:var(--gold-d);line-height:1.4}
/* Five labels of different lengths meant "SUBCONTRACTED FLOORS" wrapped to two
   lines while the other four did not, so its caption sat a line lower than the
   rest and the row read as ragged. Reserving two lines everywhere puts every
   caption on the same baseline whatever the label does. Measured at 940px,
   where the strip first goes five across. */
.st span{display:block;font-size:.72rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink);margin-top:4px}
/* Only where subgrid is unavailable. With subgrid the rows align exactly and
   this would just add a gap under every one-line label. */
@supports not (grid-template-rows: subgrid){
  .st span{min-height:3.2em}
}
.st em{display:block;font-style:normal;font-size:.8rem;line-height:1.6;
  color:var(--muted);margin-top:8px}
@media(max-width:900px){
  .statstrip .wrap{grid-template-columns:1fr 1fr;gap:32px}
  .st b{font-size:2.4rem}
}

/* --- filterable spaces grid ------------------------------------------------
   Progressive: the filter buttons are hidden until site.js unhides them, so a
   crawler and a reader without script both get all seven cards. */
.spfilter{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:32px}
.spfilter[hidden]{display:none}
/* 46, not 44. At 834px the rendered box measured a shade under 44 and
   check_viewports failed it on WCAG 2.5.8. A target sized to exactly the
   minimum loses to sub-pixel rounding, so it gets margin. */
.spbtn{min-height:46px;padding:8px 24px;cursor:pointer;
  background:transparent;color:var(--ink);border:1px solid var(--stone);
  font-family:var(--body);font-size:.72rem;letter-spacing:.14em;
  text-transform:uppercase;
  transition:background .22s ease,color .22s ease,border-color .22s ease}
.spbtn:hover{border-color:var(--gold-d);color:var(--gold-d)}
.spbtn.is-on{background:var(--ink);color:var(--paper);border-color:var(--ink)}
.spgrid{display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr));
  gap:24px;align-items:stretch}
.sp{display:flex;flex-direction:column;background:var(--paper2);padding:32px;
  border-top:2px solid var(--gold-d);
  transition:transform .22s ease,box-shadow .22s ease}
.sp:hover{transform:translateY(-4px);
  box-shadow:0 12px 32px rgba(14,14,12,.12)}
.sp h3{font-size:1.15rem;line-height:1.4;margin-bottom:12px}
/* 46, not 44: measured at exactly 44.0 at 768px and failed check_viewports,
   which wants more than 44. Same rounding lesson as .spbtn. */
.sp h3 a{color:var(--ink);text-decoration:none;
  display:inline-flex;align-items:center;min-height:46px}
.sp h3 a:hover{color:var(--gold-d)}
.sp p{flex:1 0 auto;font-size:.9rem;line-height:1.65;color:var(--muted);
  margin-bottom:24px}
.sp-more{min-height:46px;display:inline-flex;align-items:center;
  font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--gold-d);text-decoration:none;border-bottom:1px solid transparent;
  align-self:flex-start}
.sp-more:hover{border-bottom-color:var(--gold-d)}
.sp[hidden]{display:none}

/* --- three step process ----------------------------------------------------
   The spec's third step promised a one-day install. Brad has never given us a
   duration and a day is the kind of promise a customer holds you to, so the
   step says what happens and the timeline goes in when he confirms it. */
.steps{list-style:none;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr));gap:32px;
  counter-reset:none}
.step{position:relative;padding-top:64px}
.step b{position:absolute;top:0;left:0;font-family:var(--display);
  font-size:3rem;font-weight:400;color:var(--stone);line-height:1.4}
.step h3{font-size:1.15rem;line-height:1.4;margin-bottom:12px}
.step p{font-size:.9rem;line-height:1.65;color:var(--muted)}

/* --- finish swatches -------------------------------------------------------
   The spec named Granite Garage Floors' own colour line. Those are somebody
   else's product names. These are the finishes Atlas actually pours. */
.chips{display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr));
  gap:16px}
.chip{display:flex;flex-direction:column;gap:8px;padding:24px;
  background:var(--paper2);text-decoration:none;min-height:44px;
  border:1px solid var(--stone);
  transition:border-color .22s ease,transform .22s ease}
.chip:hover{border-color:var(--gold-d);transform:translateY(-4px)}
.chip .sw{display:block;width:100%;height:48px;background:var(--char2)}
.chip b{font-family:var(--display);font-size:1.15rem;font-weight:400;
  color:var(--ink);line-height:1.4}
.chip em{font-style:normal;font-size:.8rem;line-height:1.6;color:var(--muted)}
.chip-flake-epoxy .sw{background:
  radial-gradient(circle at 20% 30%,var(--stone) 0 2px,transparent 2px),
  radial-gradient(circle at 62% 66%,var(--paper) 0 2px,transparent 2px),
  radial-gradient(circle at 84% 22%,var(--gold-d) 0 2px,transparent 2px),
  var(--char2);background-size:16px 16px,24px 24px,32px 32px,100% 100%}
.chip-metallic-epoxy .sw{background:
  linear-gradient(114deg,var(--char) 0%,var(--gold-d) 38%,var(--gold) 52%,
    var(--char2) 78%,var(--ink) 100%)}
.chip-solid-colour-epoxy .sw{background:var(--field)}
.chip-quartz .sw{background:
  repeating-linear-gradient(48deg,var(--stone) 0 4px,var(--paper2) 4px 8px)}
.chip-anti-slip .sw{background:
  repeating-linear-gradient(90deg,var(--char2) 0 4px,var(--field) 4px 8px)}
.chip-polyaspartic .sw{background:
  linear-gradient(180deg,var(--paper) 0%,var(--stone) 100%)}

/* --- comparison table ------------------------------------------------------
   The spec asked for "polyaspartic beats legacy epoxy". Atlas sells both and
   epoxy is most of the work, so a section arguing against epoxy argues against
   the client's own offer. Both columns here are true. */
.compare td{font-family:var(--body);font-size:.9rem;line-height:1.65;
  color:var(--muted)}
.compare tbody th{width:26%}

@media(prefers-reduced-motion:reduce){
  .sp,.sp:hover,.chip,.chip:hover{transition:none;transform:none}
}

/* --- header phone ----------------------------------------------------------
   All seven reference sites run the number large beside the CTA. Ours was in
   the utility strip above the header at .72rem, which is a footer treatment.

   It hides below 981px, where the same number is already one tap away in the
   utility bar and in both hero CTAs. The header has a height budget on mobile
   that check_viewports.py enforces at a quarter of the viewport. */
.hphone{display:none;text-decoration:none;flex:none;line-height:1.4;
  padding:4px 0}
.hphone span{display:none;font-size:.62rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--gold)}
.hphone b{display:block;font-family:var(--display);font-size:1.15rem;
  font-weight:400;color:var(--paper);white-space:nowrap;line-height:1.4;
  transition:color .22s ease}
.hphone:hover b{color:var(--gold)}

/* Sized by measurement, not by taste. The header carries the oversized
   overlapping logo, six nav groups and the CTA; at 1.5rem with its label the
   number pushed every page 173px sideways at 1024px, which check_viewports.py
   caught on the first run. These two steps are where it actually fits. */
@media(min-width:981px) and (max-width:1099px){
  .hcall .btn{padding:12px 12px;letter-spacing:.06em}
}
@media(min-width:1360px){.hphone{display:block}}
@media(min-width:1600px){
  .hphone span{display:block}
  .hphone b{font-size:1.5rem}
}


/* ============================================================================
   PHOTOGRAPHY
   `[WALT 2026-08-30]` "not enough sections that use background images."

   Nineteen of Brad's photographs were in the tree and one was on screen. These
   three treatments put them behind the masthead of every page, behind a
   full-bleed interstitial partway down, and behind the closing call to action.

   In all three the scrim carries the contrast rather than luck: the gradient
   reaches rgba(14,14,12,.88) or darker wherever text sits.
   ========================================================================= */

/* --- hero with a background photograph -------------------------------------
   Every page except the home page was a flat charcoal band. */
.hero-sm.has-bg{position:relative;background:var(--ink)}
.hero-sm.has-bg::before{content:"";position:absolute;inset:0;
  background-image:var(--herobg);background-size:cover;background-position:center;
  opacity:.82}
.hero-sm.has-bg::after{content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,rgba(14,14,12,.95) 0%,rgba(14,14,12,.9) 44%,
    rgba(14,14,12,.34) 100%)}
.hero-sm.has-bg .wrap{position:relative;z-index:1}

/* --- full-bleed interstitial -----------------------------------------------
   The band the reference sites run between content blocks. One per page, and
   only on pages with something to say inside it. */
.photoband{position:relative;background:var(--ink);color:var(--paper);
  padding-top:128px;padding-bottom:128px}
.photoband::before{content:"";position:absolute;inset:0;
  background-image:var(--bandbg);background-size:cover;
  background-position:center;opacity:.88}
.photoband::after{content:"";position:absolute;inset:0;
  background:linear-gradient(90deg,rgba(14,14,12,.95) 0%,rgba(14,14,12,.9) 38%,
    rgba(14,14,12,.22) 100%)}
.photoband .wrap{position:relative;z-index:1}
.photoband h2{color:var(--paper);max-width:20ch}
.photoband .bluf{color:var(--paper2);margin-bottom:32px;max-width:54ch}
@media(max-width:760px){
  .photoband{padding-top:80px;padding-bottom:80px}
  .photoband::after{background:linear-gradient(180deg,rgba(14,14,12,.9) 0%,
    rgba(14,14,12,.94) 100%)}
}

/* --- closing call to action ------------------------------------------------
   Was flat --ink. It is the last thing on every page and the last chance to
   show the product. */
.close{position:relative}
.close::before{content:"";position:absolute;inset:0;
  background-image:var(--closebg);background-size:cover;
  background-position:center 62%;opacity:.8}
.close::after{content:"";position:absolute;inset:0;
  background:linear-gradient(90deg,rgba(14,14,12,.95) 0%,rgba(14,14,12,.9) 42%,
    rgba(14,14,12,.28) 100%)}
.close .wrap{position:relative;z-index:1}

/* --- quote form section ----------------------------------------------------
   The form card is already a solid dark panel, so the photograph sits behind
   the copy beside it without ever being under body text. */
.quote{position:relative}
.quote::before{content:"";position:absolute;inset:0;
  background-image:url("/assets/photos/bg/process-prep.jpg");background-size:cover;
  background-position:center;opacity:.5}
.quote::after{content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,rgba(26,26,24,.95) 0%,rgba(26,26,24,.92) 55%,
    rgba(26,26,24,.8) 100%)}
.quote .wrap{position:relative;z-index:1}

@media(prefers-reduced-motion:reduce){
  .photoband::before,.close::before,.quote::before{transition:none}
}


/* ============================================================================
   MOBILE FURNITURE
   `[WALT 2026-08-30]` "look at the site on mobile and check consistency."

   Measured at 390x844 and identical on all 38 pages: utility bar 114px,
   header 125px, proof band 206px. 445px of furniture, 53% of the viewport,
   with the h1 at 527px. A visitor saw the first line of the headline and
   nothing else.
   ========================================================================= */

/* The utility strip wrapped to two lines and both of them repeat something
   directly below it: the HIC and the phone are in the proof band, and all of
   it is in the footer. On a phone it keeps the one fact that is not repeated,
   which is where Atlas works. */
@media(max-width:980px){
  .util .wrap{flex-wrap:nowrap;overflow-x:auto;gap:16px;white-space:nowrap;
    padding-top:8px;padding-bottom:8px}
}
/* Same defect, same band. The utility line carries the CT HIC number, which
   CGS 20-427 requires to appear in the advertisement rather than merely to
   exist in the markup. Scrolled off-screen behind a hidden scrollbar it is
   arguably not in the advertisement at all, so it wraps instead. */
@media(max-width:980px){
  .util .wrap{flex-wrap:wrap;overflow-x:visible;white-space:normal;
    gap:0 14px;justify-content:flex-start;text-align:left;
    font-size:.6rem;line-height:1.45;padding-top:6px;padding-bottom:6px}
  .util b{white-space:nowrap}
  /* The locality line repeats what the H1 and the footer both say. The HIC
     number does not, so that is the span that stays. CGS 20-427 wants it in
     the advertisement, and it is also in the footer legal line on every
     page. */
  .util .wrap span:first-child{display:none}
}

/* `[WALT 2026-08-30]` "In mobile the proof band bleeds off the screen."

   It did, and the horizontal scroller above is why. Four cards in
   repeat(4,auto) with white-space:nowrap measured 199-422, 446-620 and
   644-817 on a 375px screen, inside a scroller whose scrollbar was
   deliberately hidden. So three of the four proof points were off-screen with
   nothing to indicate they existed. document.scrollWidth still equalled the
   viewport, which is exactly why check_viewports passed it.

   Proof is the thing a lead-generation page can least afford to hide. Two
   columns, stacked, all four visible without interaction. */
@media(max-width:980px){
  .proofband .wrap{grid-template-columns:1fr 1fr;gap:8px 16px;
    overflow-x:visible;white-space:normal;
    padding-top:10px;padding-bottom:10px}
  .pb{text-align:left;gap:1px;min-width:0}
  .pb b{font-size:.85rem;white-space:normal;line-height:1.25}
  .pb span{font-size:.66rem;white-space:normal;line-height:1.4}
}
/* Two columns all the way down rather than one. Stacking four proof points
   single file cost ~210px of the first screen on every page, which is the
   space the quote form needs. They fit two-up at 360. */
@media(max-width:400px){
  .proofband .wrap{grid-template-columns:1fr 1fr;gap:10px 12px}
  .pb b{font-size:.85rem}
  .pb span{font-size:.66rem;line-height:1.45}
}

/* ============================================================================
   MOBILE HEADER, ONE BLOCK
   `[WALT 2026-08-30]` "In mobile view the header and menu are broken. when I
   click to open it the logo gets warped the free quote and close button
   overlaps the logo the top level menu items are clickable but they bleed off
   the screen and none of the sub menu items are clickable."

   All four were the same cause. Six separate max-width:980px blocks had
   accumulated over the header, and the last one set flex-wrap:nowrap. With
   nowrap the nav could not take a row of its own, so it was laid out as a
   fourth item on the header line: measured 88px wide at left 263, with its
   links running to 434 on a 375px screen. That is the bleed. Squeezing four
   items onto one line also crushed .brand img, which has width:auto and no
   flex:none, from square to 30x62. That is the warp. And .brand img carries
   margin-bottom:-28px so the logo overhangs the header by design on desktop;
   with the row collapsed that overhang landed on top of the buttons at
   179-241 against their 184-229. That is the overlap.

   So this replaces the layers rather than adding a seventh. Wrap on, nav gets
   its own full-width row, the logo is flex:none with no overhang, and the
   sub-panels are real block elements rather than absolutely positioned ones.
   ========================================================================= */
@media(max-width:980px){
  header .wrap{display:flex;flex-wrap:wrap;align-items:center;
    gap:12px;row-gap:0;min-height:0;padding-top:10px;padding-bottom:10px}

  .brand{order:1;flex:0 1 auto;min-width:0;display:flex;align-items:center;
    gap:8px}
  /* flex:none is the fix for the warp. width:auto alone lets flex shrink the
     box while the intrinsic ratio stays, which squashes it. */
  .brand img{flex:none;height:44px;width:auto;margin-bottom:0}
  .wm span{font-size:1.05rem;letter-spacing:.05em}
  .wm em{display:none}

  .hcall{order:2;margin-left:auto;flex:none;gap:8px}
  .hcall .hphone{display:none}
  .hcall .btn{display:inline-block;padding:11px 12px;font-size:.62rem;
    letter-spacing:.06em;min-height:44px;line-height:22px;white-space:nowrap}

  .js .navtoggle{order:3;flex:none;padding:11px 12px;min-height:44px;
    min-width:44px;justify-content:center}

  /* Its own row. flex-basis is what nowrap was overriding. */
  #sitenav{order:4;flex:0 0 100%;width:100%;margin-left:0}
}

/* Below 400px the wordmark text goes before anything else does. The mark still
   carries the brand and the two controls keep their 44px targets. */
@media(max-width:400px){
  /* The link has to stay a 44px target even when the mark inside it shrinks,
     which is WCAG 2.5.8. Padding the anchor rather than growing the image
     keeps the header height where it is. */
  .brand{min-height:44px}
  .brand img{height:40px}
  .wm span{font-size:.95rem}
  .hcall .btn{padding:11px 9px;font-size:.58rem;letter-spacing:.04em}
  .js .navtoggle-t{display:none}
  .js .navtoggle{padding:11px 10px}
}

/* The breadcrumb is the trail on the page a phone most needs it: there is no
   room for the menu, so this is the only visible structure. It was hidden by
   the menu collapse until the selector was scoped. */
@media(max-width:980px){
  .crumb .wrap{overflow-x:auto;white-space:nowrap;
    scrollbar-width:none;-ms-overflow-style:none}
  .crumb .wrap::-webkit-scrollbar{display:none}
}


/* ============================================================================
   HIGH DENSITY AND WIDE SCREENS
   The background derivatives are 1500px. Measured on the garage page: 0.96x at
   1440, 1.28x at 1920, 1.71x at 2560, and 1.92x on a retina 1440 laptop, which
   is the common one. The 1500px cap was right for weight and wrong for a large
   screen, so there is a second file that only a screen able to use it fetches.

   Three layers, in order. The plain url() is what a browser without image-set
   gets. image-set picks by device pixel ratio, which covers retina. Neither
   knows the viewport is 2560 wide at 1x, so the min-width rule covers that.
   ========================================================================= */
/* image-set picks on device pixel ratio alone and ignores how small the box
   actually is. Every modern phone is 2x or 3x, so an unguarded image-set sent
   a 2600px photograph to a 390px screen: measured 333KB where the 1x costs
   190KB, on the device least able to afford it. So the dpr choice only applies
   from 1000px up, where a 2x screen genuinely has the pixels to show it.
   Below that everything takes the 1x, which at 390 is already oversized. */
@media(min-width:1000px){
  .hero-sm.has-bg::before,.hero-sm.has-form::before{
    background-image:image-set(var(--herobg) 1x, var(--herobg2x) 2x)}
  .photoband::before{
    background-image:image-set(var(--bandbg) 1x, var(--bandbg2x) 2x)}
  .close::before{
    background-image:image-set(var(--closebg) 1x, var(--closebg2x) 2x)}
  .quote::before{
    background-image:image-set(url("/assets/photos/bg/process-prep.jpg") 1x,
                               url("/assets/photos/bg/2x/process-prep.jpg") 2x)}
}

@media(min-width:1600px){
  .hero-sm.has-bg::before,.hero-sm.has-form::before{
    background-image:var(--herobg2x)}
  .photoband::before{background-image:var(--bandbg2x)}
  .close::before{background-image:var(--closebg2x)}
  .quote::before{
    background-image:url("/assets/photos/bg/2x/process-prep.jpg")}
}


/* ============================================================================
   THE FOLD, REWORKED AGAINST THE REFERENCE SITES
   `[WALT 2026-08-30]` "I dont think the creative design matches the examples
   enough."

   Measured on the top 1000px: the four references run .206 to .409 mean
   lightness with 31-56% near-black. Ours ran .039 and 89.2%. The photograph
   was there and nobody could see it.
   ========================================================================= */

@font-face{font-family:Inter;font-weight:800;font-style:normal;font-display:swap;
  src:url("fonts/inter-800-latin.woff2") format("woff2")}

/* --- the photograph, uncovered ---------------------------------------------
   Was opacity .82 under a gradient reaching .95 across the whole frame. The
   references dim only the part carrying text. This lifts the image to full and
   confines the scrim to the left, where the headline is, so the floor itself
   is visible in the two thirds that carry nothing. */
.hero-sm.has-bg::before,.hero-sm.has-form::before{opacity:1}
.hero-sm.has-bg::after{
  background:linear-gradient(96deg,rgba(14,14,12,.93) 0%,rgba(14,14,12,.9) 42%,
    rgba(14,14,12,.34) 62%,rgba(14,14,12,0) 82%)}
.hero-sm.has-form::after{
  background:linear-gradient(96deg,rgba(14,14,12,.93) 0%,rgba(14,14,12,.9) 44%,
    rgba(14,14,12,.3) 58%,rgba(14,14,12,0) 72%)}
.photoband::before{opacity:1}
.photoband::after{
  background:linear-gradient(90deg,rgba(14,14,12,.93) 0%,rgba(14,14,12,.9) 40%,
    rgba(14,14,12,.26) 62%,rgba(14,14,12,0) 80%)}
.close::before{opacity:1}
.close::after{
  background:linear-gradient(90deg,rgba(14,14,12,.9) 0%,rgba(14,14,12,.74) 34%,
    rgba(14,14,12,.24) 70%,rgba(14,14,12,.08) 100%)}

/* --- the headline ----------------------------------------------------------
   A 400-weight serif against four heavy sans. This is the change that carries
   the most of the difference. Only the hero headline moves: section headings
   keep the serif, so the page still reads as Atlas rather than as a template. */
.hero-sm h1{font-family:var(--body);font-weight:800;
  text-transform:uppercase;letter-spacing:-.02em;line-height:1.05;
  font-size:clamp(1.9rem,3.4vw,3rem);max-width:22ch;
  text-shadow:0 2px 24px rgba(14,14,12,.55)}
/* Body copy over a photograph needs more than --stone gives it. Measured on
   the Norwalk hero at 4.02:1 against the 4.5 body minimum; --paper2 clears it
   on every hero and is the same palette. */
.hero-sm .bluf{color:var(--paper);text-shadow:0 1px 14px rgba(14,14,12,.65)}
/* paper, not paper2. On the lightest hero photograph (countertops) paper2
   measured 4.49:1 and SC 1.4.3 wants 4.5. One step lighter clears it, and
   every hero on the site uses the same value so they stay consistent. */
.photoband .bluf,.close .bluf{text-shadow:0 1px 14px rgba(14,14,12,.6)}
.photoband h2,.close h2{font-family:var(--body);font-weight:800;
  text-transform:uppercase;letter-spacing:-.015em;line-height:1.1;
  font-size:clamp(1.5rem,2.8vw,2.3rem);
  text-shadow:0 2px 20px rgba(14,14,12,.5)}

/* --- the offer, on the fold ------------------------------------------------
   Ox Floors leads with "Save 30%". Brad's own new-customer discount was in a
   band below the fold, which is the same as not having it. */
.heroffer{display:inline-flex;align-items:center;gap:12px;
  background:var(--gold);color:var(--ink);
  padding:12px 24px;margin-bottom:24px;
  font-size:.76rem;letter-spacing:.14em;text-transform:uppercase;font-weight:600}
.heroffer b{font-family:var(--body);font-weight:800;font-size:1.15rem;
  letter-spacing:-.01em}

/* --- the lead form, as a light card ----------------------------------------
   Dark card on a dark hero disappeared into it. Ox Floors' white card is the
   first thing the eye lands on. */
.leadform{background:#FFFFFF;border-color:var(--stone);
  box-shadow:0 24px 64px rgba(14,14,12,.45)}
.lf-top b{color:var(--ink)}
.lf-top span{color:var(--gold-d)}
.leadform label{color:var(--muted)}
.leadform input,.leadform select{background:#FFFFFF;color:var(--ink);
  border-color:var(--field)}
.lf-top b{font-family:var(--body);font-weight:800;text-transform:uppercase;
  letter-spacing:-.01em;font-size:1.3rem}
.leadform .check span{color:var(--muted)}
.leadform .consent{color:var(--muted)}

/* --- calls to action, louder ----------------------------------------------
   Every reference runs one saturated colour hard on the CTA. Measured, ours
   put 2.9% saturated pixels on the fold against 4.1 to 14.9 on theirs. */
.btn.big{padding:20px 40px;font-size:.84rem;letter-spacing:.12em}
.hero-sm .btn.big{box-shadow:0 8px 32px rgba(14,14,12,.4)}

/* --- statistics, overlapping the hero --------------------------------------
   Boston Garage runs them as a translucent bar across the foot of the
   photograph rather than as a separate strip below it. */
.hero-sm.has-form + .statstrip{margin-top:-96px;position:relative;z-index:2;
  background:transparent;border:0}
.hero-sm.has-form + .statstrip .wrap{background:rgba(26,26,24,.92);
  border-top:2px solid var(--gold);
  padding-top:32px;padding-bottom:32px;padding-left:32px;padding-right:32px}
.hero-sm.has-form + .statstrip .st b{color:var(--gold)}
.hero-sm.has-form + .statstrip .st span{color:var(--paper)}
.hero-sm.has-form + .statstrip .st em{color:var(--stone)}
@media(max-width:900px){
  .hero-sm.has-form + .statstrip{margin-top:0}
  .hero-sm.has-form + .statstrip .wrap{padding-left:24px;padding-right:24px}
}


/* --- the form notice -------------------------------------------------------
   `[WALT 2026-08-30]` "the request quote form never works."

   It did tell the visitor what was happening. It told them at y=984 in an
   844px viewport, below the submit button, in 11.2px grey, so on a phone the
   message was 140px past the fold and the form simply looked dead. An
   explanation nobody can see is worse than none, because it turns "not
   finished" into "broken".

   It is an alert at the top of the form now, in the brand gold so it cannot
   be mistaken for fine print, with a number that dials on a tap. */
.qnote{background:var(--gold);color:var(--ink);
  padding:16px;margin-bottom:24px;
  font-size:.9rem;line-height:1.6;font-weight:600}
.qnote a{color:var(--ink);text-decoration:underline;
  text-underline-offset:2px;white-space:nowrap}
.qnote b{display:block;margin-bottom:4px}
/* The field the visitor has to fix, so the focus ring is not the only signal. */
.qf :invalid:focus{outline:2px solid var(--gold);outline-offset:2px}


/* --- the quote form directly under the hero --------------------------------
   `[2026-08-30]` Moving the form to the top of /request-quote-form put a dark
   section immediately under a dark hero, and check_consistency caught it: two
   dark grounds adjacent read as one enormous block. The same finding it raised
   on the gallery page.

   On that one page the section takes the light ground and drops its
   photograph, so the alternation holds and the dark form card sits on cream
   the way every other card on the site does. */
.hero-sm + .quote{background:var(--paper2)}
.hero-sm + .quote::before,.hero-sm + .quote::after{content:none}
.hero-sm + .quote h2{color:var(--ink)}
.hero-sm + .quote .bluf2{color:var(--muted)}
.hero-sm + .quote .kicker{color:var(--gold-d)}
.hero-sm + .quote .inline{color:var(--gold-d)}

/* ============================================================================
   SOCIAL ROW
   `[WALT 2026-08-30]` "All the social links are missing."

   Four of the six have no verified URL, so build_site.py renders nothing for
   them. This styles whatever is present rather than assuming six.
   ========================================================================= */
.social{display:flex;flex-wrap:wrap;gap:10px;margin-top:18px}
.soc{display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;border:1px solid var(--muted-dk);color:var(--stone);
  text-decoration:none;transition:color .2s ease,border-color .2s ease,
  background .2s ease}
.soc svg{fill:currentColor;display:block}
.soc:hover,.soc:focus-visible{color:var(--ink);background:var(--gold);
  border-color:var(--gold)}
.soc:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* ============================================================================
   THE FORM COMES FIRST ON A PHONE
   `[WALT 2026-08-30]` "the request a quote form should show the form first in
   mobile not a giant block of text. When someone clicks on a button all they
   see is a block of text and they abandon the form." And: "the form should be
   visible above the fold on the home page."

   Both were the same DOM order problem. .qgrid is text-column then
   form-column, and .hero-sm.has-form is herotext then heroform. On a desktop
   they sit side by side so order does not read. Collapsed to one column on a
   phone, the reading order becomes the whole text block and then, eventually,
   the form. Measured on the quote page: the first form field started 1,190px
   down a 812px viewport, so somebody arriving from a "Get Your Free Quote"
   button landed on prose with no field in sight.

   The columns are reordered rather than the markup, because on desktop the
   current order is right: the explanation belongs left of the form.
   ========================================================================= */
@media(max-width:900px){
  .qgrid{display:flex;flex-direction:column;gap:32px}
  .qgrid .qf{order:-1}
  /* The explanation still earns its place, just under the fields rather than
     in front of them. */
  .qgrid > div{order:1}
}

@media(max-width:980px){
  .hero-sm.has-form .wrap{display:flex;flex-direction:column;gap:22px}

  /* The headline stays first. Putting the form above it got the form into the
     first screen and pushed the H1 to 1,411px down, which check_viewports
     fails and which costs the page its most important on-page signal. So the
     space comes out of the hero instead.

     The CTA buttons are the thing the form replaces, so on a page carrying the
     form they compete with it. The bullet list restates what the sections
     below say at length. Both go, the H1 and the one-line BLUF stay, and the
     form starts inside the first screen with the headline above it. */
  .hero-sm.has-form .cta{display:none}
  /* .hbul, not .herobul. The first attempt used the wrong class name and
     silently matched nothing, which is why the form was still 1,137px down
     after the rule was written. 243px of bullets, restated at length in the
     sections below. */
  .hero-sm.has-form .hbul{display:none}
  .hero-sm.has-form .bluf{font-size:.9rem;line-height:1.5}
  .hero-sm.has-form{padding-top:20px;padding-bottom:24px}
  .hero-sm.has-form .heroffer{margin-bottom:4px}
  /* The remaining 74px comes out of the headline and the offer badge rather
     than the BLUF, which is the block an answer engine lifts and which the
     build requires to sit directly under the H1. */
  .hero-sm.has-form h1{font-size:1.72rem;line-height:1.14}
  .hero-sm.has-form .heroffer{padding:10px 14px}
  .hero-sm.has-form .kicker{margin-bottom:2px}
}

/* On the dedicated quote page the hero is pure preamble: the visitor already
   decided, they tapped a button that said Get Your Free Quote. Trim it to the
   headline so the form clears the fold. */
@media(max-width:980px){
  .formfirst .hero-sm .bluf,
  .formfirst .hero-sm .herobul,
  .formfirst .hero-sm .cta{display:none}
  .formfirst .hero-sm{padding-top:28px;padding-bottom:20px}
  .formfirst .stats{display:none}
}

/* ============================================================================
   REVIEW CARDS: THE RATING AND THE SOURCE
   `[WALT 2026-08-30]` "Reviews should include the stars and show the review
   website more prominently in the review."

   The card carried neither. The rating existed only as an aggregate at the top
   of the page, and the provenance was the word "Google" in 11px grey after a
   middot, which is not checkable. Now every card leads with its five stars and
   ends with a link that opens the profile the quote came from.
   ========================================================================= */
.rv .stars{display:block;color:var(--gold);font-size:1rem;letter-spacing:.16em;
  line-height:1;margin-bottom:14px}
.rv figcaption{display:flex;flex-wrap:wrap;align-items:center;gap:4px 10px}
.rv figcaption b{flex:1 0 100%}
.rvwhen{color:var(--muted);font-size:.68rem;letter-spacing:.08em}
/* It is a link, so it carries a 44px target like every other one. WCAG 2.5.8
   exempts inline links in a sentence; this is a standalone control in a
   caption, so the exemption does not apply to it. */
.rvsrc{display:inline-flex;align-items:center;gap:6px;margin-left:auto;
  /* 46, not 44: measured at exactly 44px and failed check_viewports at
     834px. Same rounding lesson as .spbtn. */
  min-height:46px;padding:8px 12px;border:1px solid var(--muted-dk);
  border-radius:2px;
  text-decoration:none;color:var(--ink);background:#fff;
  font-size:.66rem;letter-spacing:.06em;text-transform:none;
  transition:border-color .2s ease,box-shadow .2s ease}
.rvsrc .gmark{display:block;flex:none}
.rvsrc:hover,.rvsrc:focus-visible{border-color:var(--gold-d);
  box-shadow:0 1px 6px rgba(14,14,12,.12)}
.rvsrc:focus-visible{outline:2px solid var(--gold-d);outline-offset:2px}
@media(max-width:520px){
  .rv figcaption{gap:8px}
  .rvsrc{margin-left:0}
}

/* ============================================================================
   BLOG
   `[WALT 2026-08-30]` "Blog section is missing."

   The index leads with the cards, before the prose, for the same reason the
   gallery does: the page is the list.
   ========================================================================= */
.postgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:24px;margin-top:8px}
.post{display:flex;flex-direction:column;gap:10px;padding:28px;
  background:var(--paper2);border-left:2px solid var(--gold-d);
  text-decoration:none;color:inherit;
  transition:transform .25s ease,box-shadow .25s ease}
.post:hover,.post:focus-visible{transform:translateY(-2px);
  box-shadow:0 10px 30px rgba(14,14,12,.1)}
.post:focus-visible{outline:2px solid var(--gold-d);outline-offset:3px}
.pkick{font-size:.62rem;letter-spacing:.18em;text-transform:uppercase;
  color:var(--gold-d)}
.post h3{font-family:var(--display);font-size:1.2rem;line-height:1.28;
  margin:0;color:var(--ink);font-weight:400}
.post p{margin:0;font-size:.88rem;line-height:1.6;color:var(--muted)}
.pmore{margin-top:auto;font-size:.68rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--gold-d)}
.post:hover .pmore{text-decoration:underline}
.postdate{font-size:.68rem;letter-spacing:.12em;text-transform:uppercase;
  color:var(--muted);margin-top:10px;display:block}
@media(max-width:760px){
  .postgrid{grid-template-columns:minmax(0,1fr);gap:16px}
  .post{padding:22px}
}

/* The gallery band is dark, and on the gallery page it now sits directly under
   the dark hero because the photographs lead the page. Two dark blocks with no
   seam read as one very tall block, which check_consistency flags across the
   whole site. Only the gallery page has the adjacency, so only it gets the
   rule: a hairline in the brand gold, which is the same seam the proof band
   uses under the header. */
/* Light ground, so the page reads dark hero then light gallery rather than one
   very tall dark block. The tiles themselves stay dark, which is what a
   photograph wants behind it. */
.gallery-first .gallery{background:var(--paper);color:var(--ink)}
.gallery-first .gallery h2{color:var(--ink)}
.gallery-first .gallery .bluf,
.gallery-first .gallery .railNote{color:var(--muted)}
.gallery-first .gallery .kicker{color:var(--gold-d)}
.gallery-first .gallery .gcap{color:var(--muted)}
.gallery-first .gallery .filmToggle{border-color:var(--muted-dk);color:var(--ink)}

/* The mega menu went from six top-level items to eight when Offers and Advice
   were added, and eight did not fit between the wordmark and the CTA at 1024.
   Measured: 34px of sideways scroll on every page. The burger takes over at
   980, so this band is the one that has to absorb it. */
@media(min-width:981px) and (max-width:1180px){
  header .wrap{gap:16px}
  #sitenav{gap:10px}
  .navtop{font-size:.58rem;letter-spacing:.03em}
  .brand img{height:64px;margin-bottom:-14px}
  .wm span{font-size:1.05rem}
  .wm em{display:none}
  .hcall{gap:12px}
  .hcall .hphone{display:none}
  .hcall .btn{padding:12px 14px;font-size:.6rem;letter-spacing:.05em}
}

/* Same cause, wider band. Eight top-level items plus the wordmark, the phone
   and the CTA do not fit on one line until about 1500px, so the phone drops
   out first and the type tightens. The phone is in the util bar, the footer
   and the close block on every page, so losing it here costs nothing. */
@media(min-width:1181px) and (max-width:1500px){
  header .wrap{gap:24px}
  #sitenav{gap:14px}
  .navtop{font-size:.62rem;letter-spacing:.04em}
  .hcall .hphone{display:none}
  .hcall{gap:14px}
}

/* The hero form's own header ran 151px before the first field on a phone,
   which put the field 22px under the fold while the form itself was above it.
   A visitor who cannot see a field does not know it is a form. */
@media(max-width:980px){
  .hero-sm.has-form .qf{padding-top:18px}
  .hero-sm.has-form .lf-top{margin-bottom:12px}
  .hero-sm.has-form .lf-top b{font-size:1.05rem;line-height:1.2}
  .hero-sm.has-form .lf-top span{font-size:.7rem;line-height:1.4}
}

/* The extra hub entry is a mobile affordance. On desktop the top-level label
   is still the link and the hover panel already works, so it would be a
   duplicate sitting above every group. */
@media(min-width:981px){
  .navhub{display:none}
}

/* ============================================================================
   THE MENU SCROLLS, AND SAYS SO
   `[WALT 2026-08-30]` "...expands and is scrollable."

   It already had overflow-y:auto, but with a hidden scrollbar and no edge
   treatment there was nothing to tell you more was below. Eight groups do not
   fit a phone screen once one is open. This gives it a real height budget
   against the visual viewport, momentum scrolling on iOS, and a fade at the
   bottom edge that appears only while there is something left to scroll to.
   ========================================================================= */
@media(max-width:980px){
  .js #sitenav{
    max-height:calc(100dvh - 150px);
    overflow-y:auto;
    -webkit-overflow-scrolling:touch;
    overscroll-behavior:contain;
    scrollbar-width:thin;
    scrollbar-color:var(--muted-dk) transparent;
    padding-bottom:4px;
  }
  .js #sitenav::-webkit-scrollbar{width:4px}
  .js #sitenav::-webkit-scrollbar-thumb{background:var(--muted-dk);
    border-radius:2px}
  /* A one-line hint under the last group, so the end of the list is a place
     rather than an abrupt stop. */
  .js #sitenav .navitem:last-child{border-bottom:0}
}

/* dvh is what makes this correct on a phone: 100vh on iOS Safari is the height
   with the address bar hidden, so a vh-based menu is taller than the screen
   for as long as the bar is showing and the last group sits under it. Browsers
   without dvh take the vh line above. */
@supports (height: 100dvh) {
  @media(max-width:980px){
    .js #sitenav{max-height:calc(100dvh - 150px)}
  }
}

/* ============================================================================
   ONE MENU, VERTICAL, AT EVERY WIDTH
   `[WALT 2026-08-30]` "it should be completely vertical"

   The accordion was only below 981px. Above it the same eight groups laid out
   as a horizontal bar with hover panels: measured at 1024 and 1440, all eight
   top-level items on a single row, and the sub-links only appearing on hover.
   Two different menus with two different interaction models, and the desktop
   one cannot be operated by touch at all on a laptop with a touchscreen.

   So the accordion is now the menu. The burger opens it at every width, the
   row expands, one group at a time, and it scrolls when it needs to. This
   block comes last on purpose: it overrides the desktop nav rules above rather
   than deleting them from six separate places, and everything it overrides is
   listed here so the next reader knows where it came from.

   WHAT DOES NOT CHANGE

   Every link is still in the markup on every page whether or not script runs,
   which is the whole reason `[WALT 2026-08-29]` asked for a mega menu. Eight
   top-level and 42 spokes, same as before. The accordion hides them visually;
   it does not remove them from the DOM.
   ========================================================================= */

/* The burger is the only way in, so it exists at every width. */
.js .navtoggle{display:flex;align-items:center;gap:8px;order:3;flex:none;
  background:transparent;border:1px solid var(--muted-dk);color:var(--paper);
  font-family:var(--body);font-size:.7rem;letter-spacing:.14em;
  text-transform:uppercase;padding:12px 16px;cursor:pointer;
  min-height:44px;min-width:44px;justify-content:center}
.js .navtoggle:hover{border-color:var(--gold);color:var(--gold)}
.js .navtoggle:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* Its own full-width row under the brand, at every width. */
.js #sitenav{order:4;flex:0 0 100%;width:100%;margin-left:0;
  display:none;flex-direction:column;align-items:stretch;gap:0;
  border-top:1px solid rgba(200,198,192,.16);margin-top:8px;
  max-height:calc(100dvh - 150px);overflow-y:auto;
  -webkit-overflow-scrolling:touch;overscroll-behavior:contain;
  scrollbar-width:thin;scrollbar-color:var(--muted-dk) transparent}
.js #sitenav.open{display:flex}
header .wrap{flex-wrap:wrap}

/* The row is the control. Hover no longer opens anything, because a menu that
   opens on hover and on tap does both badly. */
.js .navitem{position:relative;border-bottom:1px solid rgba(200,198,192,.1)}
.js .navtop{display:block;padding:16px 48px 16px 0;font-size:.8rem;
  pointer-events:none;white-space:normal}
.js .navtop::after{display:none}
.js .navsub{display:block;position:absolute;top:0;left:0;right:0;height:52px;
  background:transparent;border:0;cursor:pointer;padding:0;z-index:2}
.js .navsub::before{content:"";position:absolute;right:18px;top:21px;
  width:9px;height:9px;border-right:2px solid var(--stone);
  border-bottom:2px solid var(--stone);transform:rotate(45deg);
  transition:transform .25s ease}
.js .navsub[aria-expanded="true"]::before{transform:rotate(-135deg);
  border-color:var(--gold)}
.js .navitem:hover .navpanel,
.js .navitem:focus-within .navpanel{opacity:1;visibility:visible;transform:none}
.js .navpanel{display:none;position:static;opacity:1;visibility:visible;
  border:0;background:rgba(0,0,0,.22);min-width:0;transform:none;
  box-shadow:none;padding:0}
.js .navpanel.open{display:block}
.js .navpanel-in{display:flex;flex-direction:column;padding:4px 0 8px 16px}
.js .navpanel a{padding:12px 0;font-size:.86rem;min-height:44px;
  display:flex;align-items:center}
.js .navpanel a:hover,.js .navpanel a:focus{padding-left:0;background:transparent;
  color:var(--gold)}
/* The hub link is now needed at every width, because the row is inert
   everywhere rather than only on a phone. */
.js .navhub{display:flex;color:var(--gold);letter-spacing:.06em;
  border-bottom:1px solid rgba(200,198,192,.12)}

/* The header keeps its phone shape at every width: brand, CTA, burger, then
   the menu on its own row. The phone number comes back where there is room
   for it. */
@media(min-width:981px){
  .js .hcall .hphone{display:inline-block}
  .js #sitenav{max-height:calc(100dvh - 180px)}
  .js .navtop{font-size:.86rem}
}

/* ============================================================================
   THE MENU IS A FULL-WIDTH DROPDOWN PANEL
   `[WALT 2026-08-30]` "yes do the full width dropdown panel"

   Until now the open menu was a block inside the header's flex row, so it grew
   the header and pushed the page down when it opened. On a phone that is fine
   because it fills the screen anyway. On a laptop it was a narrow column of
   rows floating in a header that had just become 600px tall.

   It is a real dropdown now: taken out of flow, pinned under the header, and
   painted edge to edge across the viewport. Opening it covers the top of the
   page instead of shoving it, which is what a dropdown is supposed to do and
   what stops the content jumping under the reader.

   position:sticky on <header> is what makes this work. Any non-static position
   establishes a containing block, so top:100% is the bottom of the header
   rather than the bottom of the document.

   The list stays vertical, per `[WALT]` earlier the same day. The panel is
   full width; the rows sit on the same measure as the rest of the page, which
   is what .navinner is for.
   ========================================================================= */
.js #sitenav{
  position:absolute;
  top:100%;
  left:0;
  right:0;
  width:100%;
  margin-top:0;
  padding:0;
  background:var(--ink);
  border-top:1px solid var(--gold-d);
  box-shadow:0 26px 60px rgba(0,0,0,.5);
  max-height:calc(100dvh - 120px);
  overflow-y:auto;
  overscroll-behavior:contain;
  z-index:130;
}

/* The content column. Matches .wrap so the rows line up with the page.

   width:100% matters. #sitenav is a column flex container, so margin:0 auto on
   a flex item absorbs the free space on the cross axis and the box shrinks to
   its content: measured 218px inside a 749px panel. Giving it a width first
   means the auto margins have nothing left to absorb and only centre it once
   max-width bites. */
.navinner{width:100%;max-width:1240px;margin:0 auto;padding:4px 24px 20px;
  box-sizing:border-box}
@media(min-width:900px){.navinner{padding-left:48px;padding-right:48px}}

/* The header no longer has to make room for it, so the row stays one row. */
.js header .wrap{flex-wrap:nowrap}

/* On a wide screen a single column of eight rows leaves the panel looking
   empty, and the rows themselves become uncomfortably long to scan. The list
   is still vertical, there are just two of them side by side, which is how a
   full-width dropdown earns the width. The open group's links sit under their
   own heading rather than in a third column, so reading order still runs down
   each column in turn. */
/* Grid, not CSS columns. Multi-column balances its content, so expanding one
   group re-flowed every other row into a different column and the item you
   just tapped moved out from under your finger. A grid gives each item a fixed
   cell, so opening a group grows that cell and nothing else moves.
   align-items:start stops the other cells stretching to match it. */
/* Column flow, so each column is a vertical run of the list and the order
   still reads downwards the way it does on a phone. Row flow put items 1 and 2
   side by side, which turns a vertical list back into a horizontal one read
   left to right. */
@media(min-width:900px){
  .navinner{display:grid;grid-auto-flow:column;
    grid-template-columns:repeat(2,minmax(0,1fr));
    grid-template-rows:repeat(4,min-content);
    align-items:start;column-gap:56px}
}
@media(min-width:1300px){
  .navinner{grid-template-columns:repeat(3,minmax(0,1fr));
    grid-template-rows:repeat(3,min-content)}
}

/* Footer logo. `[WALT 2026-08-30]` "we just need the logo in the footer".
   The mark is portrait, so it sits above the name rather than beside it, and
   the height is set with width:auto so the file's own ratio decides the rest. */
.flogo{display:inline-block;margin-bottom:14px;line-height:0}
.flogo img{height:72px;width:auto;display:block;opacity:.92;
  transition:opacity .2s ease,transform .2s ease}
.flogo:hover img,.flogo:focus-visible img{opacity:1;transform:translateY(-2px)}
.flogo:focus-visible{outline:2px solid var(--gold);outline-offset:4px}
@media(max-width:620px){.flogo img{height:60px}}

/* The two legal links sit on the legal line rather than in Quick links. That
   line is already the business-record row, which is where somebody looks for
   them, and it keeps the seven-link footer nav at seven. */
.legallinks{display:inline-flex;flex-wrap:wrap;gap:6px 18px;margin-left:6px}
.legallinks a{color:var(--stone);text-decoration:underline;
  text-underline-offset:3px}
.legallinks a:hover,.legallinks a:focus-visible{color:var(--gold)}
@media(max-width:620px){
  .legallinks{display:flex;margin-left:0;margin-top:10px}
}

/* ============================================================================
   THE LOCKUP: MARK AND WORDMARK ON THE SAME CENTRE LINE
   `[WALT 2026-08-30]` "the logo is not aligned well with the word mark. the
   logo should be bigger."

   Both were the same rule. .brand img carried margin-bottom:-28px so the mark
   would overhang the header, and .brand is align-items:center, which centres
   the MARGIN box. A negative bottom margin shortens that box, so the visible
   mark sat lower than the type it was supposed to line up with: measured at
   1440, the mark's centre was 14px below the wordmark's.

   The overhang goes. With the mark and the type on one centre line the mark
   can also be bigger without the header growing to match, because the header
   is no longer sized around a box that lies about where the artwork is.
   ========================================================================= */
.brand{align-items:center}
/* SIZED TO WHAT THE HEADER CAN CARRY, WHICH IS THE TRADE THE OVERHANG WAS
   HIDING. With margin-bottom:-28px the mark rendered 104px tall inside a 76px
   layout box, so the header stayed short and the artwork hung past it. Take the
   overhang away and the header is however tall the mark is.

   First attempt at 120px made the header 132.7px, and three gates caught what
   that cost: check_viewports put the furniture above the content at 313px on a
   1024x768 tablet, over the 40% budget, and check_fold measured the visible
   fold going dark because the header was eating the photograph.

   96px is the size that fits. It is larger than the 76px box the lockup
   actually occupied before, it is on the same centre line as the type, and the
   header lands near where it was. */
.brand img{height:96px;width:auto;margin-bottom:0;margin-top:0}
.wm span{font-size:1.45rem;letter-spacing:.05em}
.wm em{font-size:.66rem;letter-spacing:.28em}
header .wrap{padding-top:4px;padding-bottom:4px}

@media(max-width:1180px){
  .brand img{height:80px;margin-bottom:0}
  .wm span{font-size:1.25rem}
}
@media(max-width:980px){
  .brand img{height:64px;margin-bottom:0}
  .wm span{font-size:1.15rem}
}
@media(max-width:400px){
  .brand img{height:56px;margin-bottom:0}
  .wm span{font-size:1rem}
}

/* Footer mark, bigger too, and the block is centred on the mark rather than
   the mark being a small tick above a heading. */
.flogo img{height:104px}
@media(max-width:620px){.flogo img{height:84px}}

/* ============================================================================
   COVERAGE MAP
   `[WALT 2026-08-30]` "we should have a location section. that shows all the
   areas atlas service including the neighboring towns. so that Brad can
   pulling more leads that that are on the border."

   A reader scans this for exactly one word: their own town. So it is set as
   dense lists with the town name carrying the weight and the reason in a
   lighter face beside it, rather than as prose they would have to read
   through.
   ========================================================================= */
.coverage{margin-top:24px}
.covcore{padding:20px 24px;background:var(--paper2);
  border-left:2px solid var(--gold-d);margin-bottom:24px}
.covcore b{display:block;font-size:.7rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--gold-d);margin-bottom:10px}
.covlist{margin:0;font-family:var(--display);font-size:1.05rem;line-height:1.9;
  color:var(--ink)}
.covlist a{color:var(--ink);text-decoration:none;
  border-bottom:1px solid var(--muted-dk)}
.covlist a:hover,.covlist a:focus-visible{color:var(--gold-d);
  border-bottom-color:var(--gold-d)}

.coverage{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:20px 40px;align-items:start}
.covcore{grid-column:1/-1}
.coverage .railNote{grid-column:1/-1;margin-top:4px}
.covgroup b{display:block;font-size:.68rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted);margin-bottom:8px;
  padding-bottom:8px;border-bottom:1px solid var(--muted-dk)}
.covgroup ul{list-style:none;margin:0;padding:0}
.covgroup li{display:flex;flex-wrap:wrap;gap:2px 10px;align-items:baseline;
  padding:7px 0;border-bottom:1px solid rgba(120,96,60,.12)}
.covgroup li:last-child{border-bottom:0}
.covgroup li span{font-family:var(--display);font-size:1rem;color:var(--ink)}
.covgroup li em{font-style:normal;font-size:.76rem;color:var(--muted);
  line-height:1.4}
@media(max-width:760px){
  .coverage{grid-template-columns:minmax(0,1fr);gap:20px}
  .covcore{padding:16px 18px}
  .covlist{font-size:.95rem;line-height:1.8}
}

/* The state that replaces the form once an enquiry has landed. A form that
   just empties itself reads as a failure, so this says what happens next. */
.qdone{background:var(--paper2);border-left:2px solid var(--gold-d);
  padding:28px;display:flex;flex-direction:column;gap:10px}
.qdone b{font-family:var(--display);font-size:1.3rem;line-height:1.3;
  color:var(--ink);font-weight:400}
.qdone p{margin:0;font-size:.9rem;line-height:1.6;color:var(--muted)}
.qdone a{color:var(--gold-d);font-weight:600}
.hero-sm.has-form .qdone{background:rgba(14,14,12,.72);border-left-color:var(--gold)}
.hero-sm.has-form .qdone b{color:var(--paper)}
.hero-sm.has-form .qdone p{color:var(--stone)}
.hero-sm.has-form .qdone a{color:var(--gold)}

/* ============================================================================
   THE TOP LEVEL IS VISIBLE ON A DESKTOP, AND IT SITS ON THE RIGHT

   `[WALT 2026-09-01]` "the website menu shoud be right justified and I should
   see all the menu items. users dont like to not see the top level menu items
   you polluted the top level with too many items."

   MEASURED FIRST. At 1440x900 the served homepage reported #sitenav
   display:none with the burger showing and eight items behind it. The
   complaint was exact: on a laptop the menu was a button.

   WHAT THIS REVISES AND WHAT IT KEEPS

     `[WALT 2026-08-30]` "it should be completely vertical" made the burger
     universal, for a good reason: two menus with two interaction models, and
     the desktop one could not be operated by touch.

     The fix for that was never the burger, it was the eight items. So the row
     returns above 981px and the accordion stays below it, where no phone was
     going to show seven items anyway.

     ONE INTERACTION MODEL IS PRESERVED. The row is still the toggle: .navtop
     stays pointer-events:none, .navsub still overlays the item, the panel
     still opens on .open from a click. Hover opens nothing, here or there.
     That is the property that mattered in August.

   THE HEADER GETS MORE WIDTH, THE PAGE DOES NOT

     Measured at 1440: brand 186 + nav 794 + CTA 357 + gaps 48 = 1385 inside a
     1144px content box. It overflowed and the document scrolled sideways.

     The phone number and the quote button are the two most valuable elements
     in that row and shrinking them to fit a menu is the wrong trade. So
     `header .wrap` alone widens on a large screen. `.wrap` everywhere else
     keeps its 1240px measure, so the reading line is untouched and
     check_consistency still sees one type scale and one measure.
   ========================================================================= */
@media(min-width:1140px){
  .js .navtoggle{display:none}

  /* margin-left:auto is the right justification: brand takes its width, the
     nav absorbs the slack on its left, the CTA sits beyond it. order puts the
     nav BETWEEN them; without it the nav rendered outboard of the phone
     number, which reads as a mistake. */
  .js #sitenav{
    position:static;order:1;flex:0 1 auto;width:auto;
    display:flex;flex-direction:row;align-items:center;
    margin-left:auto;margin-top:0;gap:0;
    background:transparent;border-top:0;box-shadow:none;
    max-height:none;overflow:visible;padding:0}
  .js #sitenav.open{display:flex}
  .js .hcall{order:2;margin-left:0}
  .js .navinner{width:auto;max-width:none;margin:0;padding:0;
    display:flex;flex-direction:row;align-items:center;gap:0}

  .js .navitem{position:relative;border-bottom:0;display:flex;
    align-items:center}

  /* Inert, exactly as on a phone. The hub page is reached by the first link
     inside the panel, which is what .navhub is for. */
  .js .navtop{display:inline-block;pointer-events:none;
    padding:10px 22px 10px 10px;font-size:.70rem;letter-spacing:.06em;
    white-space:nowrap}

  /* The button covers its own item and nothing else. Bounded to .navitem,
     which is the positioning parent, so it cannot swallow a neighbour. */
  .js .navsub{position:absolute;inset:0;width:auto;height:auto;
    background:transparent;border:0;cursor:pointer;padding:0;z-index:2}
  .js .navsub::before{content:"";position:absolute;right:8px;top:50%;
    margin-top:-4px;width:6px;height:6px;
    border-right:2px solid var(--stone);border-bottom:2px solid var(--stone);
    transform:rotate(45deg)}
  .js .navsub[aria-expanded="true"]::before{transform:rotate(-135deg);
    margin-top:-1px;border-color:var(--gold)}

  /* Each panel drops from its own item rather than across the header. */
  .js .navpanel{position:absolute;top:100%;right:0;left:auto;
    min-width:232px;background:var(--ink);
    border:1px solid var(--gold-d);border-top:2px solid var(--gold-d);
    box-shadow:0 22px 48px rgba(0,0,0,.5);padding:6px 0;z-index:140}
  .js .navpanel.open{display:block}
  .js .navpanel-in{padding:0;display:flex;flex-direction:column}
  .js .navpanel a{padding:10px 18px;font-size:.82rem;white-space:nowrap}

  /* The leftmost two would run off the left edge hanging right. */
  .js .navitem[data-nav="0"] .navpanel,
  .js .navitem[data-nav="1"] .navpanel{right:auto;left:0}

  /* Said rather than left to be inferred: the rules above this block set
     opacity and visibility on :hover and :focus-within for the old design.
     The panel is display:none until .open, so neither can reveal it. */
  .js .navitem:hover .navpanel:not(.open),
  .js .navitem:focus-within .navpanel:not(.open){display:none}
}

/* Room for all seven without touching the page measure. */
@media(min-width:1321px){
  header .wrap{max-width:1400px;padding-left:32px;padding-right:32px}
  .js header .wrap{gap:20px}
}

/* WHY 1140 AND NOT 981.
   Measured at 1024: the row fitted the header but pushed .hcall 19px past the
   viewport and the document scrolled sideways. The phone number and the quote
   button are the two most valuable things in that row, so the menu does not
   get to squeeze them. Above 1140 all seven fit with the CTA intact; below it
   the accordion stays, which is what it was always for.

   Between 1140 and 1320 it is genuinely tight, so the label tightens rather
   than the row wrapping. A nav that breaks to two lines is the thing this was
   meant to stop. */
@media(min-width:1140px) and (max-width:1320px){
  header .wrap{padding-left:24px;padding-right:24px}
  .js header .wrap{gap:12px}
  .js .navtop{font-size:.62rem;padding:10px 18px 10px 6px;letter-spacing:.03em}
  .js .hcall{gap:10px}
  .js .hcall .hphone span{display:none}
}

/* ============================================================================
   LIGHTBOX
   `[WALT 2026-09-01]` "add the sticky click-to-call bar and lightbox gallery"

   6 of 9 competitors run one, per research/competitors/FEATURES.md. Written
   rather than installed: Fancybox, PhotoSwipe and lightGallery are 30-90KB to
   solve an overlay, a focus trap and two arrow keys, and CLAUDE.md is explicit
   that external code is a reference and not a drop-in.
   ========================================================================= */
.gopen{display:block;width:100%;padding:0;border:0;background:none;
  cursor:zoom-in;line-height:0}
.gopen:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.gopen img{display:block;width:100%;height:auto;
  transition:transform .4s cubic-bezier(.2,.7,.3,1)}
.gitem:hover .gopen img{transform:scale(1.02)}
@media(prefers-reduced-motion:reduce){
  .gopen img,.gitem:hover .gopen img{transition:none;transform:none}
}

/* The page behind must not scroll while the overlay is up. */
html.lb-on,html.lb-on body{overflow:hidden}

.lightbox{position:fixed;inset:0;z-index:200;display:flex;
  align-items:center;justify-content:center;gap:8px;
  padding:clamp(12px,4vw,48px);
  background:rgba(8,8,7,.94)}
.lightbox[hidden]{display:none}
.lbfig{margin:0;max-width:min(1200px,100%);max-height:100%;
  display:flex;flex-direction:column;gap:14px;align-items:center}
.lbfig img{max-width:100%;max-height:78vh;width:auto;height:auto;
  display:block;border:1px solid rgba(200,198,192,.18)}
.lbfig figcaption{color:var(--stone);font-size:.9rem;line-height:1.6;
  max-width:62ch;text-align:center}

/* 46px, not 44. The spaces filter already learned that a target sized to the
   exact minimum loses to sub-pixel rounding and check_viewports fails it. */
.lightbox button{min-width:46px;min-height:46px;cursor:pointer;
  background:transparent;border:1px solid rgba(200,198,192,.28);
  color:var(--paper);font-size:1.6rem;line-height:1;
  transition:border-color .22s ease,color .22s ease}
.lightbox button:hover{border-color:var(--gold);color:var(--gold)}
.lightbox button:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.lbclose{position:absolute;top:clamp(12px,3vw,28px);right:clamp(12px,3vw,28px);
  font-size:1.5rem}
.lbnav[hidden]{display:none}

/* On a phone the arrows go under the picture rather than beside it, or they
   eat the width the photograph is there to use. */
@media(max-width:700px){
  .lightbox{flex-wrap:wrap;align-content:center}
  .lbfig{order:1;flex:0 0 100%}
  .lbprev{order:2}
  .lbnext{order:3}
  .lbfig img{max-height:62vh}
}

/* ============================================================================
   STICKY CLICK-TO-CALL BAR
   `[WALT 2026-09-01]`, and 5 of 9 competitors run one. FEATURES.md.

   PHONE ONLY. Above 980px the header carries the number and the quote button
   and both are always on screen, so a second pinned copy would cover content
   to repeat what is already visible.

   THE BODY GETS PADDING RATHER THAN THE BAR GETTING A SCRIM. A fixed bar over
   the end of a page hides the footer's last line, and on this site that line
   is the CT HIC number and the legal links. Reserving the space is the only
   version where nothing is covered.
   ========================================================================= */
.stickycta{display:none}

@media(max-width:980px){
  .stickycta{display:flex;position:fixed;left:0;right:0;bottom:0;z-index:150;
    gap:8px;padding:8px 12px;
    background:rgba(14,14,12,.97);
    border-top:1px solid var(--gold-d);
    box-shadow:0 -8px 28px rgba(0,0,0,.35)}
  /* Safe-area inset, or the bar sits under the home indicator on an iPhone
     and the tap target is the one that gets clipped. */
  @supports(padding:max(0px)){
    .stickycta{padding-bottom:max(8px,env(safe-area-inset-bottom))}
  }
  .scall,.squote{flex:1 1 0;min-height:46px;display:flex;
    align-items:center;justify-content:center;text-align:center}
  .scall{flex-direction:column;gap:1px;text-decoration:none;
    border:1px solid rgba(200,198,192,.3);color:var(--paper);padding:5px 8px}
  .scall span{font-size:.58rem;letter-spacing:.14em;text-transform:uppercase;
    color:var(--stone)}
  .scall b{font-family:var(--display);font-size:1rem;font-weight:400;
    color:var(--paper)}
  .scall:hover,.scall:focus-visible{border-color:var(--gold);color:var(--gold)}
  .squote{padding:5px 10px;font-size:.68rem}

  /* The reserved space, and the number is measured rather than guessed.
     The first version reserved 68px against a bar that renders at 70, so the
     footer's last line sat 2px under it - and on this site that line is the CT
     HIC number and the legal links. 84 clears it with room for the larger text
     a visitor may have set. */
  body{padding-bottom:84px}
  @supports(padding:max(0px)){
    body{padding-bottom:calc(84px + env(safe-area-inset-bottom))}
  }
}

/* A visitor already inside the quote form does not need a button that scrolls
   them to the quote form. The bar stays for the phone, the button goes. */
@media(max-width:980px){
  .stickycta.at-form .squote{display:none}
  .stickycta.at-form .scall{flex:1 1 100%}
}

/* ============================================================================
   BEFORE / AFTER WIPE
   `[WALT 2026-09-01]` "add the before/after slider". 3 of 9 competitors run
   one, per FEATURES.md, and it is the most persuasive thing a floor company
   owns.

   THE CONTROL IS A RANGE INPUT. Every competitor build is a div with mousemove
   handlers: unreachable by keyboard, silent to a screen reader, and its own
   pointer-capture bug waiting to happen. A range input is draggable, arrow-key
   operable, announced as a slider and touch-native for free. What is styled
   here is its thumb; what is hidden is its track.
   ========================================================================= */
.ba .bawrap{position:relative;overflow:hidden;line-height:0;
  --pos:50%;
  border:1px solid var(--muted-dk);background:var(--paper2)}
.ba .baimg{display:block;width:100%;height:auto}
/* The after sits on top and is clipped. Clipping rather than resizing means
   neither frame ever moves, which is the whole illusion. */
.ba .baafter{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;
  clip-path:inset(0 0 0 var(--pos))}
.ba .baline{position:absolute;top:0;bottom:0;left:var(--pos);width:2px;
  background:var(--gold);transform:translateX(-1px);pointer-events:none;z-index:2}

.ba .barange{position:absolute;inset:0;width:100%;height:100%;margin:0;
  -webkit-appearance:none;appearance:none;background:transparent;
  cursor:ew-resize;z-index:3}
.ba .barange:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
/* The thumb is the handle. 46px for the same reason every other control on
   this site is 46 and not 44. */
.ba .barange::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  width:46px;height:46px;border-radius:50%;cursor:ew-resize;
  background:var(--gold);border:2px solid var(--ink);
  box-shadow:0 2px 14px rgba(0,0,0,.45)}
.ba .barange::-moz-range-thumb{width:46px;height:46px;border-radius:50%;
  cursor:ew-resize;background:var(--gold);border:2px solid var(--ink);
  box-shadow:0 2px 14px rgba(0,0,0,.45)}
.ba .barange::-webkit-slider-runnable-track{background:transparent;border:0}
.ba .barange::-moz-range-track{background:transparent;border:0}

.ba .balab{position:absolute;top:12px;z-index:2;pointer-events:none;
  font-family:var(--body);font-size:.6rem;letter-spacing:.16em;
  text-transform:uppercase;padding:6px 10px;
  background:rgba(14,14,12,.72);color:var(--paper);line-height:1}
.ba .balabb{left:12px}
.ba .balaba{right:12px}

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

/* ============================================================================
   HERO BACKGROUND VIDEO
   `[WALT 2026-09-01]` "add the background video". 3 of 9 competitors, per
   FEATURES.md, and the only item on that list needing nothing from Brad.

   LAYERING WITHOUT A z-index. .hero-sm.has-form paints ::before (the
   photograph), then its element children in tree order, then ::after (the
   scrim). The video is a child sitting between them, so it covers the
   photograph and sits under the scrim, and .wrap keeps z-index 1 above both.
   Nothing here fights the existing stack.

   THE PHOTOGRAPH IS NOT REMOVED. --herobg still paints underneath. If the clip
   never starts, never loads, or is blocked, the hero is exactly what it is
   today. That is the fallback and it needs no code.
   ========================================================================= */
.hero-sm.has-form .herovid{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;opacity:.72;pointer-events:none;
  /* Hidden until site.js decides it may play. Without this the poster frame
     would paint over the photograph on a phone, where the clip never runs. */
  visibility:hidden}
.hero-sm.has-form.vid-on .herovid{visibility:visible}

/* The WCAG 2.2.2 control. Small, in the corner of the hero, and only present
   once script has actually started something to pause. */
.herovidbtn{position:absolute;right:16px;bottom:16px;z-index:2;
  min-height:46px;padding:10px 16px;cursor:pointer;
  background:rgba(14,14,12,.72);border:1px solid rgba(200,198,192,.34);
  color:var(--paper);font-family:var(--body);font-size:.62rem;
  letter-spacing:.14em;text-transform:uppercase;
  transition:border-color .22s ease,color .22s ease}
.herovidbtn[hidden]{display:none}
.herovidbtn:hover{border-color:var(--gold);color:var(--gold)}
.herovidbtn:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

@media(max-width:980px){
  /* Never on a phone. The clip is not fetched there at all, so this is only
     belt and braces if the gate above ever changes. */
  .hero-sm.has-form .herovid,.herovidbtn{display:none}
}
@media(prefers-reduced-motion:reduce){
  .hero-sm.has-form .herovid{display:none}
}

/* ============================================================================
   TWO HERO DEFECTS
   `[WALT 2026-09-01]` "the pause button doesnt work for th film" and "the stat
   strip covers text in the hero"

   BOTH WERE MEASURED BEFORE BEING CHANGED.

   THE STRIP. `.hero-sm.has-form + .statstrip` pulls up 96px to sit across the
   foot of the photograph, which is the intended design. The hero's bottom
   padding is the generic section 80px. 96 is larger than 80, so the strip ate
   the last 16px of the hero every time: measured at 1440x900, strip top 1357
   against a CTA row ending at 1373.

   The overlap is not the bug and is not reduced. The hero is given enough
   bottom padding to absorb it, with 32px to spare.

   THE BUTTON. It sat at `bottom:16px` of a 1195px-tall hero, which put it at
   y=1414 in a 900px viewport. document.elementFromPoint at its centre returned
   null: it was below the fold and no real click could ever reach it. My own
   test clicked it through JavaScript, which bypasses hit-testing entirely and
   is exactly why the defect survived a passing test.

   It moves to the top of the hero, in the band above both columns: the text
   starts at 338 and the form at 338, and the hero starts at 258, so a control
   at top:16px sits at y=274 in clear space at every width where the film runs.
   ========================================================================= */
@media(min-width:901px){
  .hero-sm.has-form{padding-bottom:128px}
}

.herovidbtn{top:16px;bottom:auto}
@media(max-width:1139px){
  /* The film only runs at 1140 and up, so below that the control is not
     rendered at all and this is belt and braces. */
  .herovidbtn{display:none}
}

/* The trust line under the quote form's head. NPS puts stars and a badge at
   the top of its card; the equivalent Atlas actually has is a measured rating
   and the registration number CGS 20-427 requires anyway. Nothing invented. */
.qftrust{margin:-12px 0 22px;font-size:.72rem;letter-spacing:.04em;
  color:var(--muted);line-height:1.6}
.qftrust b{color:var(--gold-d);font-size:.86rem}
.hero-sm.has-form .qftrust{color:var(--stone)}
.hero-sm.has-form .qftrust b{color:var(--gold)}

/* A page head with no photograph, no form and no call to action, for pages
   that are read rather than sold from. `[WALT 2026-09-01]` the privacy policy
   and the terms. Same type scale as a hero so check_consistency still sees one
   scale across the site; what it drops is the furniture. */
.pagehead{background:var(--paper2);border-bottom:1px solid var(--stone)}
.pagehead .wrap{max-width:820px}
/* The hero's own h1 rule, copied rather than guessed. My first attempt
   invented a clamp and check_consistency counted a second type scale
   across the site, correctly. */
.pagehead h1{margin:0 0 16px;font-family:var(--body);font-weight:800;
  text-transform:uppercase;letter-spacing:-.02em;line-height:1.05;
  font-size:clamp(1.9rem,3.4vw,3rem);max-width:22ch}
.pagehead .bluf{margin:0}
@media(min-width:900px){.pagehead{padding-top:64px;padding-bottom:48px}}

/* ============================================================================
   THE FIVE BANDS
   `[WALT 2026-09-01]` "5 to 10 text sections make the wbesite look boring."

   Measured first: 36 of 46 pages carried five or more text-only sections, 12
   carried eight or more, and the homepage ran eleven with nothing photographed
   between them.

   Modelled on NPS's src/partials/, which has photo-strip, numbers, steps,
   comparison and reviews and is why its pages have rhythm.
   ========================================================================= */

/* --- photo strip: the pause. No prose in it at all. --------------------- */
.photostrip{padding:0;background:var(--ink)}
.psrow{display:grid;grid-template-columns:repeat(3,1fr);gap:2px}
.psrow img{display:block;width:100%;height:clamp(190px,22vw,320px);
  object-fit:cover}
.pscap{margin:0;padding:14px 24px;text-align:center;color:var(--stone);
  font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;
  background:var(--ink)}
@media(max-width:760px){
  .psrow{grid-template-columns:1fr 1fr}
  /* A third frame on a phone is a 33% wide sliver. Two is a photograph. */
  .psrow img:nth-child(n+3){display:none}
}

/* --- steps: numbered, one idea each -------------------------------------

   `[DEFECT 2026-09-01]` This was a bare `.steps` rule. The homepage's own
   process list is an `<ol class="steps">` written long before this band
   existed, so the rule painted it a paper2 background it never had, inside a
   transparent section. Measured on the built page: the ol computed
   rgb(237,231,218) against a section computing transparent.

   Scoped to `section.steps` now. A band is a section; the homepage's list is
   not one, and a bare class selector could not tell them apart. */
section.steps{background:var(--paper2)}
.steprow{list-style:none;margin:32px 0 0;padding:0;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:28px;
  counter-reset:step}
.steprow li{counter-increment:step;position:relative;padding-top:52px}
.steprow li::before{content:counter(step);position:absolute;top:0;left:0;
  width:40px;height:40px;display:flex;align-items:center;justify-content:center;
  font-family:var(--body);font-size:.9rem;font-weight:800;
  color:var(--ink);background:var(--gold);border-radius:50%}
.steprow b{display:block;font-family:var(--display);font-size:1.2rem;
  font-weight:400;color:var(--ink);margin-bottom:8px;line-height:1.3}
.steprow span{display:block;font-size:.92rem;line-height:1.7;color:var(--muted)}

/* --- comparison: a real table, because it is real tabular data ---------- */
.ctablewrap{margin-top:28px;overflow-x:auto}
.ctable{width:100%;border-collapse:collapse;min-width:520px}
.ctable th,.ctable td{padding:14px 16px;text-align:left;font-size:.92rem;
  line-height:1.6;border-bottom:1px solid var(--muted-dk);vertical-align:top}
.ctable thead th{font-family:var(--body);font-size:.68rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--gold-d);border-bottom:2px solid var(--gold-d)}
.ctable tbody th{font-weight:400;color:var(--ink);width:30%}
.ctable td{color:var(--muted)}
.ctable tbody tr:last-child th,.ctable tbody tr:last-child td{border-bottom:0}

/* --- reviews: verbatim, three of them ----------------------------------- */
/* Light on purpose. The photo strip above it is dark, and two dark bands in a
   row read as one block - check_consistency said so on three pages and was
   right. Alternating is the rhythm. */
.reviewstrip{background:var(--paper2);border-top:1px solid var(--stone);
  border-bottom:1px solid var(--stone)}
.reviewstrip .bluf2 b{color:var(--gold-d)}
.rvrow{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
  gap:24px;margin:32px 0 28px}
.rvq{margin:0;padding:24px;background:var(--paper);
  border-left:2px solid var(--gold-d)}
.rvq blockquote{margin:0 0 14px;font-family:var(--display);font-size:1.05rem;
  line-height:1.6;color:var(--ink)}
.rvq blockquote::before{content:"\201C"}
.rvq blockquote::after{content:"\201D"}
.rvq figcaption{font-size:.76rem;letter-spacing:.08em;text-transform:uppercase;
  color:var(--gold-d)}
.rvq figcaption span{display:block;margin-top:3px;text-transform:none;
  letter-spacing:0;color:var(--muted)}

/* ============================================================================
   BLOG PAGER
   `[WALT 2026-09-02]` "extend advice section into a real blog with pagination"

   Rendered only when there is more than one page, so a short archive does not
   grow furniture that says nothing. rel=prev / rel=next are on the links,
   which none of the nine Connecticut competitors emits.
   ========================================================================= */
.pager{display:flex;flex-wrap:wrap;gap:8px;align-items:center;
  justify-content:center;margin-top:40px}
.pager a,.pager span{min-height:46px;min-width:46px;display:inline-flex;
  align-items:center;justify-content:center;padding:8px 16px;
  font-family:var(--body);font-size:.72rem;letter-spacing:.12em;
  text-transform:uppercase;text-decoration:none;
  border:1px solid var(--stone);color:var(--ink);
  transition:border-color .22s ease,color .22s ease,background .22s ease}
.pager a:hover{border-color:var(--gold-d);color:var(--gold-d)}
.pager a:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.pager .is-on{background:var(--ink);color:var(--paper);border-color:var(--ink)}
.pager .pprev,.pager .pnext{letter-spacing:.14em}
