/* ==========================================================================
   SB Pro Construction & Painting — shared shell
   Built on the system documented in Documents/SITE-BUILD-PROMPT.md.
   The structure carries over; the palette is re-derived from THIS client's
   logo, and the colour roles are deliberately reassigned — see section 2.
   ========================================================================== */

/* --- 1. Fonts ------------------------------------------------------------- */
@font-face{
  font-family:'Archivo';
  src:url('/assets/fonts/archivo-wdth-wght.woff2') format('woff2-variations');
  font-weight:100 900; font-stretch:62% 125%; font-display:swap;
}
@font-face{
  font-family:'Inter';
  src:url('/assets/fonts/inter-latin-var.woff2') format('woff2-variations');
  font-weight:100 900; font-display:swap;
}

/* --- 2. Tokens ------------------------------------------------------------
   The ink is sampled from his logo: a pure saturated red at #E60000-#F00000.
   Pure red is 4.81:1 on white — it FAILS AA for body text — and at scale it
   reads cheap. So the brand red is deepened on the same hue rather than
   replaced. Every ratio below is measured, not estimated.

   ROLE REASSIGNMENT, deliberate: the flooring build used the brand colour for
   headings and near-black for the accent. Red is too loud for that. Here the
   headings are near-black and red is the accent only. A painter's site should
   be the quietest thing in the room so the paint is not.
   ------------------------------------------------------------------------ */
:root{
  --red:#C40D0D;         /* 6.16:1 on white; white on it 6.16:1 — the workhorse */
  --red-deep:#9E0B16;    /* 8.36:1 — hover and pressed */
  --red-logo:#E60000;    /* his literal logo red. Logo and icons ONLY, never text */
  --red-wash:#FBECEC;

  --ink:#16181B;         /* 17.79:1 on white */
  --muted:#5A6068;       /*  6.35:1 */
  --paper:#FFFFFF;
  --sand:#F4F5F6;
  --hair:#DDE0E4;        /* hairline ONLY — 1.3:1. Never a control border. */
  --line:#828B9B;        /* control borders, per WCAG 1.4.11: 3.43:1 on white
                            and 3.15:1 on --sand. The old #8A93A3 passed on
                            white and failed at 2.84:1 on sand. */

  --ground:#141416;      /* near-black. White on it = 18.40:1 */
  --on-dark:#C9CCD1;     /* 11.43:1 on --ground */

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

  --hdr-h:66px;          /* FIXED. never animates — an animating sticky header
                            changes document height every scroll tick and reads
                            as the whole page shaking. */
  --pad:clamp(1.25rem,5vw,2.5rem);
  --max:1180px; --max-prose:66ch;
  --section:clamp(3.5rem,8vw,6.5rem);

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

  /* `only` is the keyword that disables the browser inventing a dark version.
     `light` alone does nothing. Also drives scrollbars, <select> panels,
     checkboxes and the autofill panel — none of which a background can reach. */
  color-scheme:only light;
  accent-color:var(--red);
}

/* --- 3. Reset ------------------------------------------------------------- */
*,*::before,*::after{ box-sizing:border-box }
body,h1,h2,h3,h4,p,ul,ol,li,figure,blockquote,dl,dd{ margin:0 }
ul[class],ol[class]{ list-style:none; padding:0 }
img,svg,video{ max-width:100%; display:block }
button,input,select,textarea{ font:inherit; color:inherit }
html{ -webkit-text-size-adjust:100% }
body{
  background:var(--paper); color:var(--ink);
  font-family:'Inter',system-ui,-apple-system,sans-serif;
  font-size:1.0625rem; line-height:1.62;
  font-synthesis-weight:none; text-rendering:optimizeLegibility;
  overflow-x:hidden;
}

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

p{ max-width:var(--max-prose) }
p + p{ margin-top:.9em }
.lede{ font-size:clamp(1.08rem,2vw,1.28rem); line-height:1.5 }
.muted{ color:var(--muted) }
.small{ font-size:.875rem; line-height:1.5 }
.num{ font-variant-numeric:tabular-nums }

a{ color:var(--red); text-underline-offset:.18em }
@media (hover:hover) and (pointer:fine){ a:hover{ color:var(--red-deep) } }

.eyebrow{
  font-family:'Inter',sans-serif;
  font-size:.78rem; font-weight:700; letter-spacing:.13em;
  text-transform:uppercase; color:var(--red);
  margin:0 0 .85rem;
  max-width:none;   /* see note above — never inherit the 66ch prose cap */
}
/* The seam. 3px of brand red that scales in from the left on reveal — the
   cheapest thing on the site and what makes a section feel authored. */
.seam{
  display:block; width:88px; height:3px; background:var(--red);
  margin:1.05rem 0 1.6rem;
  transform-origin:left center;
  transition:transform var(--dur-reveal) var(--ease);
}
/* The seam is VISIBLE by default and only hides inside a .reveal that has
   not fired yet. It used to default to scaleX(0) and wait for an .is-in
   ancestor — so a seam outside a reveal (the hero's) stayed at zero width
   forever. Same failure class as content stuck at opacity:0: never let the
   resting state be the invisible one. */
.reveal .seam{ transform:scaleX(0) }
.reveal.is-in .seam,.is-in .seam,.seam.is-in{ transform:scaleX(1) }

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

.vh{ position:absolute !important; width:1px; height:1px; padding:0; margin:-1px;
     overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0 }
.skip{ position:absolute; left:-9999px; top:0; z-index:200;
       background:var(--red); color:#fff; padding:.85rem 1.25rem; font-weight:700 }
.skip:focus{ left:0 }

/* --- 5. Layout ------------------------------------------------------------ */
.wrap{
  width:100%; max-width:var(--max); margin-inline:auto;
  /* max(), not a sum: a notched phone's inset can raise the gutter, never
     stack onto it. --pad clamps to 40px, under a landscape inset of ~44px. */
  padding-left:max(var(--pad), env(safe-area-inset-left, 0px));
  padding-right:max(var(--pad), env(safe-area-inset-right, 0px));
}
.section{ padding-block:var(--section) }
.section--sand{ background:var(--sand) }
.section--dark{ background:var(--ground); color:var(--on-dark) }
.section--dark h1,.section--dark h2,.section--dark h3{ color:#fff }
.section--dark .eyebrow{ color:#fff }
.section--dark .seam{ background:var(--red) }
/* :not(.btn) matters — a bare `a` rule is (0,1,1) and outranks .btn--onDark
   at (0,1,0), which paints white text on a white button. */
.section--dark a:not(.btn){ color:#fff }
.center{ text-align:center }
.center p,.center .seam{ margin-inline:auto }
.stack > * + *{ margin-top:1.1rem }
.grid{ display:grid; gap:1.5rem; margin-top:2.25rem }
.grid--2{ grid-template-columns:repeat(auto-fit,minmax(300px,1fr)) }
.grid--3{ grid-template-columns:repeat(auto-fit,minmax(250px,1fr)) }
.grid--4{ grid-template-columns:repeat(auto-fit,minmax(220px,1fr)) }

/* --- 6. Buttons ----------------------------------------------------------- */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:.5rem;
  min-height:48px; padding:.8rem 1.5rem; border:2px solid transparent;
  border-radius:2px; text-decoration:none; cursor:pointer;
  font-family:'Archivo',sans-serif; font-weight:700; font-stretch:96%;
  font-size:.95rem; letter-spacing:.04em; text-transform:uppercase;
  transition:background-color var(--dur-micro) var(--ease),
             color var(--dur-micro) var(--ease),
             border-color var(--dur-micro) var(--ease);
}
.btn--primary{ background:var(--red); color:#fff }
.btn--ghost{ background:transparent; color:var(--ink); border-color:var(--line) }
.btn--onDark{ background:#fff; color:var(--ink) }
.btn--onDarkGhost{ background:transparent; color:#fff; border-color:rgba(255,255,255,.55) }
@media (hover:hover) and (pointer:fine){
  .btn--primary:hover{ background:var(--red-deep) }
  .btn--ghost:hover{ background:var(--ink); color:#fff; border-color:var(--ink) }
  .btn--onDark:hover{ background:var(--sand) }
  .btn--onDarkGhost:hover{ background:#fff; color:var(--ink); border-color:#fff }
}
.btn-row{ display:flex; flex-wrap:wrap; gap:.75rem; margin-top:1.75rem }
.center .btn-row{ justify-content:center }

/* --- 7. Header ------------------------------------------------------------
   Fixed, transparent over the hero, solid once scrolled. The height is FIXED
   and only colour changes.
   ------------------------------------------------------------------------ */
.hdr{
  position:fixed; top:0; left:0; right:0; z-index:100;
  background:transparent; border-bottom:1px solid transparent;
  transition:background-color var(--dur-micro) var(--ease),
             border-color var(--dur-micro) var(--ease);
}
.hdr.is-stuck,.hdr--solid{
  background:rgba(20,20,22,.95);
  border-bottom-color:rgba(255,255,255,.12);
}
.hdr__in{
  max-width:var(--max); margin-inline:auto;
  min-height:calc(var(--hdr-h) - 1px);
  padding:0 max(var(--pad), env(safe-area-inset-right, 0px))
         0 max(var(--pad), env(safe-area-inset-left, 0px));
  display:grid; grid-template-columns:1fr auto 1fr;
  align-items:center; gap:1rem;
}
.hdr__logo{ display:flex; align-items:center; gap:.6rem; text-decoration:none; min-height:44px }
.hdr__mark{ width:36px; height:36px; border-radius:2px; flex:0 0 auto }
.hdr__name{ display:flex; flex-direction:column; line-height:1 }
.hdr__name b{
  font-family:'Archivo',sans-serif; font-weight:800; font-stretch:88%;
  font-size:1.02rem; letter-spacing:.02em; text-transform:uppercase; color:#fff;
}
.hdr__name span{
  font-family:'Inter',sans-serif; font-size:.56rem; font-weight:700;
  letter-spacing:.16em; text-transform:uppercase; color:var(--on-dark); margin-top:.18rem;
}
.nav{ display:flex; align-items:center; gap:1.4rem }
.nav--right{ justify-content:flex-end }
.nav > a{
  position:relative; text-decoration:none; color:var(--on-dark);
  font-family:'Inter',sans-serif; font-size:.8rem; font-weight:700;
  letter-spacing:.1em; text-transform:uppercase; padding:.4rem 0;
}
.nav > a::after{
  content:''; position:absolute; left:0; right:0; bottom:0; height:2px;
  background:var(--red); transform:scaleX(0); transform-origin:left center;
  transition:transform var(--dur-micro) var(--ease);
}
.nav > a[aria-current="page"]{ color:#fff }
.nav > a[aria-current="page"]::after{ transform:scaleX(1) }
@media (hover:hover) and (pointer:fine){
  .nav > a:hover{ color:#fff }
  .nav > a:hover::after{ transform:scaleX(1) }
}
.hdr__tel{
  font-size:.84rem; letter-spacing:.02em; padding:.6rem .9rem;
  gap:.45rem; font-variant-numeric:tabular-nums; min-height:42px;
}
.hdr__tel svg{ width:15px; height:15px; fill:currentColor; flex:0 0 auto }
.hdr__burger{
  display:none; width:46px; height:46px; align-items:center; justify-content:center;
  background:transparent; border:1px solid rgba(255,255,255,.35); border-radius:2px;
  cursor:pointer; justify-self:end;
}
.hdr__burger span,.hdr__burger span::before,.hdr__burger span::after{
  content:''; display:block; width:20px; height:2px; background:#fff;
  transition:transform var(--dur-micro) var(--ease), opacity var(--dur-micro) var(--ease);
}
.hdr__burger span::before{ transform:translateY(-6px) }
.hdr__burger span::after{ transform:translateY(4px) }
.hdr__burger[aria-expanded="true"] span{ background:transparent }
.hdr__burger[aria-expanded="true"] span::before{ transform:rotate(45deg) }
.hdr__burger[aria-expanded="true"] span::after{ transform:translateY(-2px) rotate(-45deg) }

.mobmenu{ display:none }
@media (max-width:1190px){
  .nav--left,.nav--right{ display:none }
  .hdr__burger{ display:flex }
  .mobmenu{
    position:absolute; left:0; right:0; top:100%;
    flex-direction:column; background:var(--ground);
    border-bottom:1px solid rgba(255,255,255,.14);
    padding:.35rem max(var(--pad), env(safe-area-inset-right,0px)) 1.15rem
            max(var(--pad), env(safe-area-inset-left,0px));
    max-height:calc(100svh - var(--hdr-h)); overflow-y:auto;
    /* without this a flick at either end chains into the page behind and
       drags the header through its is-stuck flip mid-gesture */
    overscroll-behavior:contain;
  }
  .mobmenu.is-open{ display:flex }
  .mobmenu nav{ display:flex; flex-direction:column }
  .mobmenu nav a{
    display:block; padding:.95rem 0; text-decoration:none; color:var(--on-dark);
    border-bottom:1px solid rgba(255,255,255,.1);
    font-family:'Archivo',sans-serif; font-weight:700; font-size:1.05rem;
    letter-spacing:.04em; text-transform:uppercase;
  }
  /* The phone menu is the ONLY nav below 1190px, so aria-current has to be
     visible here or it is announced but never shown. */
  .mobmenu nav a[aria-current="page"]{
    color:#fff; box-shadow:inset 3px 0 0 var(--red); padding-left:.75rem;
  }
  .mobmenu__foot{ display:flex; gap:.75rem; align-items:center; margin-top:1.1rem }
  .mobmenu__foot .btn{ flex:1 }
}

/* --- 8. Hero -------------------------------------------------------------
   Full-bleed photograph, ONE overlay, copy over the top. Not three scrims,
   not a canvas: a single layer at inset:0 cannot misplace itself.
   ------------------------------------------------------------------------ */
.hero{
  position:relative; overflow:hidden; isolation:isolate;
  min-height:100vh;     /* fallback: a browser that does not know svh drops */
  min-height:100svh;    /* the next line and would otherwise have NO height */
  display:flex; align-items:center; justify-content:center;
  background:var(--ground); text-align:center;
}
.hero__media{ position:absolute; inset:0; z-index:0; overflow:hidden }
.hero__img{
  position:absolute; inset:0; width:100%; height:100%;
  max-width:none; object-fit:cover; display:block;
  /* The 66px sticky header covers the top ~7% of the hero, so the roofline
     has to land BELOW that or the gable reads as cut off. 62% pushed the
     crop down until the roof peak sat behind the header and the frame filled
     with driveway. A wide, short window crops MORE vertically, so 1280x720 is
     the binding case, not 1440x900: it needs Y <= 17%. 15% clears everything. */
  object-position:50% 15%;
}
.hero__overlay{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  /* Strongest at the top where the header sits and at the foot where the call
     bar sits; eased back through the middle so the work still reads. */
  background:linear-gradient(180deg,
    rgba(10,10,12,.70) 0%, rgba(10,10,12,.44) 24%,
    rgba(10,10,12,.56) 50%, rgba(10,10,12,.68) 78%,
    rgba(10,10,12,.86) 100%);
}
.hero__content{
  position:relative; z-index:3; width:100%; max-width:54rem; margin-inline:auto;
  padding:calc(var(--hdr-h) + clamp(1.5rem,5vw,3rem)) var(--pad) clamp(5rem,11vw,7rem);
}
/* The brand mark carried above the copy, the way the reference site does it.
   It is a knockout: the white page behind the original was made transparent
   and the black half recoloured to white, so it reads on the photograph
   instead of half-vanishing into it. */
.hero__logo{
  width:clamp(215px, 26vw, 320px); height:auto;
  margin:0 auto clamp(1rem,2.2vw,1.6rem);
}
.hero .eyebrow{ color:#fff }
.hero h1{ color:#fff }
/* The headline is white over a photograph, so its contrast depends on whatever
   pixel happens to sit behind a stroke. Measured on this hero: the band behind
   the h1 averages 6.3:1 against white, but the brightest pixels in it — a white
   window frame and a cloud — come out near 2:1 on their own. Lifting the whole
   overlay enough to fix that would take it to about 69% black and dull the house
   the photograph is there to show. A shadow solves it where the problem actually
   is, which is under the glyphs. Same treatment the clip captions use. */
.hero h1,
.hero .eyebrow{ text-shadow:0 2px 16px rgba(8,8,10,.62), 0 1px 3px rgba(8,8,10,.5) }
.hero h1 .l2{ display:block }
.hero .seam{ margin-inline:auto; background:var(--red) }
.hero .btn-row{ justify-content:center; margin-top:2rem }
.hero :focus-visible{ outline-color:#fff }
.hero__scroll{ /* not a link: pointer-events off so it never eats a tap */
  pointer-events:none;
  position:absolute; left:50%; bottom:1.6rem; transform:translateX(-50%);
  z-index:3; display:flex; flex-direction:column; align-items:center; gap:.45rem;
  color:rgba(255,255,255,.65); text-decoration:none;
  font-family:'Inter',sans-serif; font-size:.66rem; font-weight:600;
  letter-spacing:.24em; text-transform:uppercase;
}
.hero__scroll svg{ width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:2 }
/* It shows on phones too. The old reason for hiding it — a 110x42 box under
   the 44px tap target — died when it stopped being a link; it is decoration
   now and cannot be tapped at all.

   With the call bar gone it only has to clear the home indicator. Landscape
   phones still have no room for it between the CTAs and the bottom edge. */
@media (max-width:1190px){
  .hero__scroll{
    bottom:calc(env(safe-area-inset-bottom, 0px) + 1.15rem);
    font-size:.62rem;
  }
  .hero__scroll svg{ width:16px; height:16px }
}
@media (max-height:520px) and (orientation:landscape){
  .hero__scroll{ display:none }
}

/* --- 9. Trust bar --------------------------------------------------------- */
.trustbar{ background:var(--ink); color:#fff; padding-block:1.1rem }
.trustbar__in{
  max-width:var(--max); margin-inline:auto; padding-inline:var(--pad);
  display:flex; align-items:center; justify-content:center;
  gap:clamp(.85rem,2.4vw,2rem); flex-wrap:wrap;
}
.trustbar__item{
  display:flex; align-items:center; gap:.55rem;
  font-family:'Inter',sans-serif; font-size:.8rem; font-weight:700;
  letter-spacing:.07em; text-transform:uppercase;
}
.trustbar__item svg{ width:18px; height:18px; flex:0 0 auto; color:var(--red-logo) }
.trustbar__div{ width:1px; height:22px; background:rgba(255,255,255,.22); flex:0 0 auto }
@media (max-width:820px){ .trustbar__div{ display:none } }

/* --- 10. Tiles -----------------------------------------------------------
   The photograph IS the tile. No frame, no shadow, no label strip underneath
   — that vocabulary is what got called "very old school".
   ------------------------------------------------------------------------ */
.tiles{
  display:grid; gap:1rem; margin-top:2.5rem;
  grid-template-columns:1fr;
  padding:0; list-style:none;
}
/* TWO across, not four. At four the tiles came out 263px wide and read as
   thumbnails; two gives ~575px each, which is the size the photographs
   deserve and roughly what the approved flooring build shipped. */
@media (min-width:640px){ .tiles{ grid-template-columns:repeat(2,1fr) } }
.tile{ margin:0 }
.tile__inner{
  position:relative; display:block; overflow:hidden;
  aspect-ratio:4/5; border-radius:2px; background:var(--sand);
}
.tile img{ width:100%; height:100%; object-fit:cover; display:block }
.tile__inner::after{
  content:''; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(180deg,
    rgba(8,8,10,0) 34%, rgba(8,8,10,.34) 60%, rgba(8,8,10,.86) 100%);
}
.tile__label{
  position:absolute; left:0; right:0; bottom:0; z-index:2;
  padding:1.1rem 1.15rem;
  font-family:'Archivo',sans-serif; font-weight:800; font-stretch:92%;
  font-size:1.02rem; letter-spacing:.02em; text-transform:uppercase; color:#fff;
}
/* No hover zoom: the tiles are photographs, not buttons, and a lift on
   hover promises a click that does not happen. */


/* --- 12. Reviews ---------------------------------------------------------- */
.rating{
  display:flex; align-items:center; justify-content:center; gap:.75rem;
  flex-wrap:wrap; margin-top:.4rem;
}
.rating__score{
  font-family:'Archivo',sans-serif; font-weight:800; font-size:2.6rem;
  line-height:1; color:var(--ink); font-variant-numeric:tabular-nums;
}
.stars{ display:inline-flex; gap:2px }
.stars svg{ width:19px; height:19px; fill:var(--red-logo) }
.rev{
  background:var(--paper); border:1px solid var(--hair); border-radius:2px;
  padding:1.5rem 1.4rem; display:flex; flex-direction:column; gap:.9rem;
}
.section--sand .rev{ background:var(--paper) }
.rev p{ max-width:none }
.rev footer{ font-size:.86rem; color:var(--muted); margin-top:auto }
.rev cite{ font-style:normal; font-weight:700; color:var(--ink) }

/* --- 13. Form ------------------------------------------------------------- */
.form{ max-width:640px; margin-inline:auto; display:grid; gap:1.1rem }
.field{ display:grid; gap:.4rem }
.field label{ font-size:.86rem; font-weight:600 }
.req{ color:var(--red) }
.field input,.field select,.field textarea{
  width:100%; padding:.75rem .85rem; background:#fff; color:var(--ink);
  /* --hair is 1.3:1 and fails WCAG 1.4.11's 3:1 for a control boundary.
     --line is 3.10:1. Hairlines are for rules, not for controls. */
  border:1px solid var(--line); border-radius:2px;
  transition:border-color var(--dur-micro) var(--ease),
             box-shadow var(--dur-micro) var(--ease);
}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:none; border-color:var(--red); box-shadow:0 0 0 3px rgba(196,13,13,.16);
}
.field textarea{ min-height:120px; resize:vertical }
.field-row{ display:grid; gap:1.1rem; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)) }
.checks{ display:grid; gap:.5rem; grid-template-columns:repeat(auto-fit,minmax(165px,1fr));
         padding:0; margin:0; list-style:none }
.checks li{ display:flex; align-items:center; gap:.55rem }
.checks input{ width:20px; height:20px; flex:0 0 auto }
fieldset{ border:0; padding:0; margin:0 }
legend{ font-size:.86rem; font-weight:600; padding:0 0 .55rem }
/* Never display:none — some bots skip hidden fields. */
.hp{ position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden }
.form-status{ font-size:.95rem; padding:.85rem 1rem; border-radius:2px }
.form-status:empty{ padding:0; background:none; border:0 }
.form-status--ok{ background:var(--red-wash); color:var(--ink); border-left:4px solid var(--red) }
.form-status--err{ background:var(--sand); color:var(--ink); border-left:4px solid var(--ink) }
.form-status a{ color:inherit; font-weight:700 }
/* The browser paints the autofill panel itself and offers no property for it. */
input:-webkit-autofill,input:-webkit-autofill:hover,input:-webkit-autofill:focus,
textarea:-webkit-autofill,select:-webkit-autofill{
  -webkit-box-shadow:0 0 0 100px #fff inset !important;
  box-shadow:0 0 0 100px #fff inset !important;
  -webkit-text-fill-color:var(--ink) !important; caret-color:var(--ink);
}

/* --- 14. Footer ----------------------------------------------------------- */
.ftr{ background:var(--ground); color:var(--on-dark); padding-block:clamp(2.5rem,6vw,4rem) }
.ftr h2{ font-size:.82rem; letter-spacing:.14em; color:#fff; margin-bottom:.9rem }
.ftr__grid{ display:grid; gap:2rem; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)) }
.ftr a{ color:var(--on-dark); text-decoration:none }
@media (hover:hover) and (pointer:fine){ .ftr a:hover{ color:#fff; text-decoration:underline } }
.ftr li{ margin-bottom:.45rem; font-size:.92rem }
.ftr__mark{ width:44px; height:44px; border-radius:2px }
.ftr__phone{ font-family:'Archivo',sans-serif; font-weight:800; font-size:1.3rem;
             color:#fff !important; font-variant-numeric:tabular-nums }
.ftr__base{
  margin-top:2.5rem; padding-top:1.5rem; border-top:1px solid rgba(255,255,255,.14);
  display:flex; flex-wrap:wrap; gap:.75rem 1.5rem;
  align-items:center; justify-content:space-between; font-size:.84rem;
}
.ftr__base ul{ display:flex; flex-wrap:wrap; gap:1.1rem }
.ftr :focus-visible{ outline-color:#fff }

/* --- 15. (Call bar removed) -----------------------------------------------
   There was a fixed tap-to-call bar pinned across the bottom on phones. At
   48px plus a 34px home-indicator inset it sat on top of Safari's own
   toolbar and ate a visible slice of every screen, so it is gone — the same
   call Hugo Builders made. The phone number still lives in the menu, in the
   hero, in the estimate section and in the footer.
   Anything below that was sized to clear this bar has been unwound with it.
   ------------------------------------------------------------------------ */
@media print{ .hdr{ display:none } }

/* --- 16. Reveals ---------------------------------------------------------- */
.reveal{
  opacity:0; transform:translateY(14px);
  transition:opacity var(--dur-reveal) var(--ease), transform var(--dur-reveal) var(--ease);
}
.reveal.is-in{ opacity:1; transform:none }
.reveal[data-delay="1"]{ transition-delay:.08s }
.reveal[data-delay="2"]{ transition-delay:.16s }
.reveal[data-delay="3"]{ transition-delay:.24s }
@media (prefers-reduced-motion:reduce){
  .reveal{ opacity:1; transform:none; transition:none }
  .seam{ transform:scaleX(1); transition:none }
  .tile img{ transition:none }
}
/* .reveal starts at opacity:0, so printing before JS runs yields blank sheets. */
@media print{ .reveal{ opacity:1 !important; transform:none !important } }

/* --- 17. Defensive dark-mode restatement --------------------------------- */
@media (prefers-color-scheme:dark){
  :root{ color-scheme:only light }
  body{ background:var(--paper); color:var(--ink) }
  .rev,.field input,.field select,.field textarea{ background:#fff }
}

/* --- 18. Phone pass -------------------------------------------------------
   Applied from the start rather than discovered in an audit.
   ------------------------------------------------------------------------ */
@media (max-width:1190px){
  /* With the call bar gone the CTAs only have to clear the home indicator and
     the scroll cue below them. */
  .hero__content{ padding-bottom:calc(env(safe-area-inset-bottom, 0px) + 3.25rem) }
}
@media (max-height:520px) and (orientation:landscape){
  .hero{ min-height:auto }
  .hero__content{ padding-top:calc(var(--hdr-h) + .85rem) }
  .hero h1{ font-size:clamp(1.9rem,5.2vw,2.6rem) }
  .hero__logo{ width:clamp(150px,17vw,200px); margin-bottom:.7rem }
  .hero .btn-row{ margin-top:1.1rem }
}
@media (max-height:660px) and (orientation:portrait){
  /* Measured at 360x640 and 320x568: the CTAs fell behind the call bar.
     Compact the block rather than pad it — padding pushes them further down. */
  .hero__content{ padding-top:calc(var(--hdr-h) + 1rem) }
  .hero h1{ font-size:clamp(1.95rem,7.6vw,2.5rem) }
  .hero__logo{ width:clamp(170px,42vw,230px); margin-bottom:.9rem }
  .hero .seam{ margin-block:.8rem 1rem }
  .hero .btn-row{ margin-top:1.1rem; gap:.6rem }
}
@media (max-height:600px) and (orientation:portrait){
  .hero h1{ font-size:clamp(2rem,8vw,2.6rem) }
}
@media (max-width:560px){
  /* Section 5 centres running copy, which is right for a short block and
     wrong for a paragraph that sets as eight double-ragged lines at 360px. */
  .section > .wrap .stack p{ text-align:left; margin-inline:0 }
}

/* A 320x568 first-generation SE. At that height the hero content is taller
   than the screen no matter how the padding is arranged. The lede is the part
   that can go — the headline says the same thing and the buttons are the
   point. */
@media (max-height:580px) and (orientation:portrait){
  .hero__logo{ width:clamp(150px,17vw,200px); margin-bottom:.7rem }
  .hero h1{ font-size:clamp(1.8rem,7.2vw,2.2rem) }
  .hero .seam{ margin-block:.7rem .9rem }
}

/* --- 19. Landscape phones, after the hero logo was added -----------------
   Measured with the logo in place: the hero came to 493px in a 390px viewport
   and the CTAs overlapped the call bar by 79-94px across 844x390, 667x375 and
   640x360. Roughly 100px has to come out. The logo gives up 48 of it, the
   headline ~20, and the eyebrow goes entirely — the logo already says who this
   is, so the eyebrow is the redundant line at this height.
   ------------------------------------------------------------------------ */
@media (max-height:520px) and (orientation:landscape){
  .hero__content{ padding-top:calc(var(--hdr-h) + .5rem) }
  .hero__logo{ width:clamp(78px,10vw,112px); margin-bottom:.55rem }
  .hero .eyebrow{ display:none }
  .hero h1{ font-size:clamp(1.5rem,4vw,2rem) }
  .hero .seam{ margin-block:.6rem .8rem }
  .hero .btn-row{ margin-top:.85rem; gap:.55rem }
  .hero .btn{ min-height:44px; padding:.6rem 1.15rem; font-size:.88rem }
}

/* --- 20. THE BEFORE / AFTER — the signature component --------------------
   Two photographs of one job, registered to the same framing, with a draggable
   seam between them. It is deliberately NOT a plugin: no card frame, no drop
   shadow, no circular puck, no "drag me" badge. Those five things are what
   make a before/after read as a widget bolted on, and this client already
   rejected framed-card chrome once.

   Three traps are handled here rather than discovered later:
   1. draggable="false" + -webkit-user-drag:none on both images. A native image
      drag fires pointercancel and destroys the pointer capture, which leaves
      click-to-jump working and DRAG BROKEN ON DESKTOP ONLY. user-select:none
      does not cover it.
   2. touch-action:pan-y — horizontal gestures come to us, vertical page scroll
      still belongs to the phone. touch-action:none would trap the reader.
   3. A real <input type="range"> underneath does the keyboard and screen-reader
      work, so this is operable with no pointer at all and degrades to two
      stacked photographs if the JS never runs.
   ------------------------------------------------------------------------ */
.ba{
  position:relative; margin:2.5rem 0 0; border-radius:2px; overflow:hidden;
  touch-action:pan-y; user-select:none; -webkit-user-select:none;
  background:var(--sand); line-height:0;
}
.ba__img{
  display:block; width:100%; height:auto;
  -webkit-user-drag:none; user-drag:none; pointer-events:none;
}
/* The clipped layer sits on top and is inset from the RIGHT, so it paints on
   the LEFT — which is why it holds the BEFORE, matching the Before label in
   that corner. Getting this pair the wrong way round shows the finished house
   under the word "Before", and it is not obvious from the code alone.
   clip-path is cheaper than animating width and it never reflows. */
.ba__clip{
  position:absolute; inset:0;
  clip-path:inset(0 calc(100% - var(--x,50%)) 0 0);
}
.ba__clip .ba__img{ height:100%; object-fit:cover }

.ba__line{
  position:absolute; top:0; bottom:0; left:var(--x,50%);
  width:2px; margin-left:-1px; background:#fff; z-index:3; pointer-events:none;
  box-shadow:0 0 0 1px rgba(0,0,0,.22);
}
.ba__grip{
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  display:flex; align-items:center; gap:34px; color:#fff;
  filter:drop-shadow(0 1px 3px rgba(0,0,0,.55));
}
.ba__grip svg{ width:15px; height:15px }

.ba__tag{
  position:absolute; bottom:0; z-index:2; padding:.9rem 1.05rem;
  font-family:'Archivo',sans-serif; font-weight:800; font-stretch:92%;
  font-size:.95rem; letter-spacing:.08em; text-transform:uppercase; color:#fff;
  line-height:1; text-shadow:0 1px 6px rgba(0,0,0,.6); pointer-events:none;
}
.ba__tag--before{ left:0 }
.ba__tag--after{ right:0 }

/* The operable control. Transparent, stretched over the whole component, with
   a thumb the width of a thumb. */
.ba__range{
  position:absolute; inset:0; width:100%; height:100%; margin:0; z-index:4;
  -webkit-appearance:none; appearance:none; background:transparent; cursor:ew-resize;
}
.ba__range::-webkit-slider-thumb{
  -webkit-appearance:none; width:44px; height:100vh; background:transparent; cursor:ew-resize;
}
.ba__range::-moz-range-thumb{
  width:44px; height:100vh; border:0; background:transparent; cursor:ew-resize;
}
.ba__range::-moz-range-track{ background:transparent }
.ba__range:focus-visible{ outline:3px solid var(--red); outline-offset:3px }

@media (max-width:560px){
  .ba__tag{ font-size:.82rem; padding:.7rem .8rem }
  .ba__grip{ gap:26px }
}
@media (prefers-reduced-motion:reduce){
  .ba{ scroll-behavior:auto }
}

/* --- 21. The logo paint-on -----------------------------------------------
   The mark contains a paintbrush drawn diagonally across the buildings, so the
   logo reveals along that same diagonal — it reads as the brush painting the
   mark on rather than as a generic fade.

   --wipe DEFAULTS TO 140%, i.e. fully revealed. home.js pulls it back to -10%
   and animates it forward. That ordering matters: if anime.js never loads, if
   the script throws, or if the tab is restored mid-animation, the logo is
   VISIBLE rather than invisible. Never let the resting state be the hidden one.
   ------------------------------------------------------------------------ */
.hero__logo{
  --wipe:140%;
  -webkit-mask-image:linear-gradient(48deg, #000 0 var(--wipe), transparent var(--wipe));
          mask-image:linear-gradient(48deg, #000 0 var(--wipe), transparent var(--wipe));
}
@media (prefers-reduced-motion:reduce){
  .hero__logo{ --wipe:140% !important; -webkit-mask-image:none; mask-image:none }
}

/* Square at two-up — a 575px-wide portrait tile would run ~720px tall and push
   the row off the screen. Portrait stays on the phone, where one tile fills
   the width and height is cheap. */
@media (min-width:640px){
  .tile__inner{ aspect-ratio:1/1 }
}
/* The label is the only text on the tile now that the sub-descriptions are
   gone, so it carries more weight. */
.tile__label{ font-size:1.16rem; padding:1.25rem 1.3rem }
@media (max-width:560px){ .tile__label{ font-size:1.02rem; padding:1.05rem 1.1rem } }

/* --- 22. See us in action -------------------------------------------------
   Vertical clips on the dark ground, so the 9:16 shape reads as intentional
   rather than as letterboxing.

   These AUTOPLAY, which forces two decisions:

   1. MUTED. No browser autoplays audio, full stop. So the sound is offered
      behind a button instead, which is also the only way a person can turn it
      on deliberately.
   2. A VISIBLE PAUSE CONTROL. WCAG 2.2.2 requires a way to stop anything that
      moves for more than five seconds and starts on its own. Two looping
      videos are exactly that.

   The tiles are sized by HEIGHT, not width, above 700px. A 9:16 tile scaled
   from its width gets taller as the screen gets wider, which is backwards —
   at 1280px a width-driven pair stood 782px tall and swallowed the viewport.
   Driving from height keeps them in proportion to the screen they are on.
   ------------------------------------------------------------------------ */
.reels{
  display:flex; flex-wrap:wrap; justify-content:center;
  gap:1.25rem; margin-top:2.5rem; padding:0; list-style:none;
}
.reel{ margin:0; flex:1 1 100%; max-width:400px; margin-inline:auto }
.reel__frame{
  position:relative; overflow:hidden; isolation:isolate;
  aspect-ratio:9/16; width:100%;
  border-radius:2px; background:#000;
}
@media (min-width:700px){
  .reel{ flex:0 0 auto; max-width:none; margin-inline:0 }
  .reel__frame{ height:clamp(380px,64vh,580px); width:auto }
}
.reel__video{ width:100%; height:100%; object-fit:cover; display:block }

/* Scrim + caption + controls. One gradient, painted on the frame rather than
   on a child that could overhang it. */
/* The bar sits at the TOP of the tile, for two measured reasons.

   1. The site has a fixed call bar pinned to the bottom of the viewport. A
      control sitting at the bottom of a tall 9:16 tile can end up behind it,
      and the pause control has to stay reachable — WCAG 2.2.2 is not satisfied
      by a button you cannot get to.
   2. The exterior clip carries the company's own text burned into the picture
      at roughly 70-85% of frame height. A caption at the bottom stacked
      directly beneath it and read as two competing labels.

   Top-anchored, both problems disappear and the footage keeps its own space. */
.reel__bar{
  /* Above .reel__play (z-index 3): the idle overlay covers the whole frame,
     and the sound toggle has to stay reachable underneath it. */
  position:absolute; left:0; right:0; top:0; z-index:4;
  display:flex; align-items:center; gap:.6rem;
  padding:.75rem .75rem 2.6rem;
  background:linear-gradient(180deg,rgba(8,8,10,.80) 0%,rgba(8,8,10,0) 100%);
  pointer-events:none;                 /* the buttons re-enable it themselves */
}
.reel__cap{
  flex:1 1 auto; margin:0; min-width:0;
  font-family:'Archivo',sans-serif; font-weight:700; font-stretch:92%;
  font-size:.82rem; line-height:1.25; letter-spacing:.02em;
  text-transform:uppercase; color:#fff; text-shadow:0 1px 6px rgba(0,0,0,.75);
}
.reel__btn{
  pointer-events:auto; flex:0 0 auto;
  width:44px; height:44px; display:grid; place-items:center;
  border-radius:50%; cursor:pointer;
  /* A fixed dark chip, not a translucent tint: the thing behind it is moving
     video, so contrast has to be guaranteed rather than hoped for. */
  background:rgba(10,10,12,.66);
  border:1px solid rgba(255,255,255,.55);
  color:#fff;
  transition:background var(--dur-micro) var(--ease);
}
.reel__btn svg{ width:19px; height:19px; fill:currentColor; display:block }
@media (hover:hover) and (pointer:fine){
  .reel__btn:hover{ background:rgba(10,10,12,.86) }
}
.reel__btn:focus-visible{ outline:3px solid #fff; outline-offset:2px }

/* Icon swapping is driven off the frame's state, so JS sets one attribute
   and never touches markup. */
.reel__frame[data-state="playing"] .i-play,
.reel__frame:not([data-state="playing"]) .i-pause,
.reel__frame[data-sound="on"] .i-muted,
.reel__frame:not([data-sound="on"]) .i-sound{ display:none }

/* The fallback. Reduced motion, Save-Data, iOS Low Power Mode and a blocked
   play() all land here: poster plus one obvious button, never a dead black
   rectangle. */
.reel__play{
  position:absolute; inset:0; z-index:3; width:100%; height:100%;
  display:none; align-items:center; justify-content:center;
  background:linear-gradient(180deg,rgba(8,8,10,.14) 40%,rgba(8,8,10,.45) 100%);
  border:0; cursor:pointer; color:#fff;
}
.reel__frame[data-state="idle"] .reel__play{ display:flex }
.reel__play svg{
  width:64px; height:64px; fill:#fff;
  filter:drop-shadow(0 2px 10px rgba(0,0,0,.65));
  transition:transform var(--dur-micro) var(--ease);
}
@media (hover:hover) and (pointer:fine){
  .reel__play:hover svg{ transform:scale(1.08) }
}
.reel__play:focus-visible{ outline:3px solid #fff; outline-offset:-6px }

@media (prefers-reduced-motion:reduce){
  .reel__btn,.reel__play svg{ transition:none }
}

/* Visually hidden, still announced. */
.u-sr{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
}

/* A landscape phone is ~390px tall. clamp()'s 380px floor filled the whole
   viewport with one tile, so the heading and the clips could never be on
   screen together. Measured, not guessed. */
@media (max-height:520px) and (orientation:landscape){
  /* width:auto is not optional here. The base rule sets width:100%, and an
     element with BOTH a width and a height ignores aspect-ratio entirely --
     measured at 667x375 the 9:16 tile came out 400x293, a landscape box, and
     object-fit:cover then threw away the top and bottom of the clip. */
  .reel__frame{ height:clamp(230px,78vh,380px); width:auto }
  .reel{ flex:0 0 auto; max-width:none }
  .reel__play svg{ width:46px; height:46px }
}

/* --- 23. Rounded media ----------------------------------------------------
   One token, so every photograph and clip on the page shares a corner.

   The hero is deliberately NOT in this list. It is a full-bleed background
   that runs edge to edge and under the header; rounding it would cut four
   notches out of the top of the page and show the ground colour through them.
   ------------------------------------------------------------------------ */
:root{ --radius:14px }

.tile__inner,
.ba,
.reel__frame,
.rev{ border-radius:var(--radius) }

/* The before/after images sit inside .ba, which already clips them. Rounding
   them again would show a hairline of background in each corner. */
.ba__img{ border-radius:0 }

/* --- 24. Social buttons ---------------------------------------------------
   Their real marks, as vectors, linking to the real profiles.
   ------------------------------------------------------------------------ */
.social{ display:flex; align-items:center; gap:.45rem }
.social__btn{
  width:40px; height:40px; flex:0 0 auto;
  display:grid; place-items:center;
  border-radius:50%; color:#fff; text-decoration:none;
  border:1px solid rgba(255,255,255,.38);
  transition:background var(--dur-micro) var(--ease),
             border-color var(--dur-micro) var(--ease);
}
.social__btn svg{ width:18px; height:18px; fill:currentColor; display:block }
@media (hover:hover) and (pointer:fine){
  .social__btn:hover{ background:rgba(255,255,255,.14); border-color:rgba(255,255,255,.7) }
}
.social__btn:focus-visible{ outline:3px solid #fff; outline-offset:2px }
.mobmenu__foot .social{ justify-content:center; margin-top:.9rem }

/* --- 25. Reviews marquee --------------------------------------------------
   A continuous drift rather than a stepped carousel: the reviews are always
   moving, which is what makes the section feel alive without anyone having to
   press anything.

   The whole effect is one keyframe. The track is laid out at its natural width
   and slid exactly half its length; because the cards repeat in identical
   halves, the moment it snaps back to 0 is indistinguishable from the frame
   before it. No JavaScript is involved in the movement at all — which is why
   it cannot stutter, drift out of sync, or die if a script fails.

   What is added on top of the plain recipe: it stops when nobody is reading
   (hover, focus, off-screen, hidden tab), it never starts under reduced
   motion, and it has a real pause button — WCAG 2.2.2 asks for one for
   anything that moves by itself for more than five seconds.
   ------------------------------------------------------------------------ */
.mq{ margin-top:2.5rem }
.mq__viewport{
  overflow:hidden; width:100%;
  /* Cards dissolve at both edges instead of being guillotined by the section
     boundary, so the row reads as endless rather than as a clipped strip. */
  -webkit-mask-image:linear-gradient(to right,transparent 0,#000 6%,#000 94%,transparent 100%);
          mask-image:linear-gradient(to right,transparent 0,#000 6%,#000 94%,transparent 100%);
}
.mq__track{
  display:flex; gap:1.5rem; width:max-content;
  margin:0; padding:.4rem 0; list-style:none;
  animation:review-drift 80s linear infinite;
  will-change:transform;
}
.mq__item{ display:flex; flex:0 0 clamp(272px,78vw,410px) }
.mq__item > .rev{ width:100%; margin:0 }

/* -50% is NOT one repeat of this track. With 18 items and a 1.5rem gap the
   track measures 18w + 17g, so half of it is 9w + 8.5g — but the first card of
   the identical second half sits at 9w + 9g. The loop therefore landed half a
   gap short of the frame it was supposed to match and snapped 12px sideways
   once every cycle. The extra 0.75rem is that missing half gap. */
@keyframes review-drift{
  from{ transform:translate3d(0,0,0) }
  to  { transform:translate3d(calc(-50% - 0.75rem),0,0) }
}

/* Stop for anything that means somebody is trying to read it.

   Scoped to .mq__viewport, NOT .mq — and that distinction is the whole bug.
   The pause button lives inside .mq, so `.mq:focus-within` was satisfied by
   the button itself: clicking Pause and then Play flipped the label, flipped
   data-playing, and left the strip frozen, because the button that was just
   clicked still held focus. Nothing in the file ever sets animation-play-state
   back to `running`, so the JS state could never win. The control worked
   exactly once, then died until you clicked elsewhere.

   Hover is behind the pointer query for the same reason every other hover rule
   in this file is: touch browsers latch :hover onto the last-tapped element,
   so tapping Pause would have left it stuck too. */
@media (hover:hover) and (pointer:fine){
  .mq__viewport:hover .mq__track{ animation-play-state:paused }
}
.mq__viewport:focus-within .mq__track,
.mq[data-playing="false"] .mq__track{ animation-play-state:paused }

.mq__ui{ display:flex; justify-content:center; margin-top:1.5rem }
.mq__hold{
  display:inline-flex; align-items:center; gap:.5rem;
  min-height:44px; padding:.55rem 1.1rem;
  border-radius:999px; cursor:pointer;
  background:var(--paper); border:1px solid var(--line); color:var(--ink);
  font-family:'Archivo',sans-serif; font-weight:700; font-stretch:92%;
  font-size:.82rem; letter-spacing:.06em; text-transform:uppercase;
  transition:background var(--dur-micro) var(--ease),
             border-color var(--dur-micro) var(--ease), color var(--dur-micro) var(--ease);
}
.mq__hold svg{ width:15px; height:15px; fill:currentColor; display:block }
@media (hover:hover) and (pointer:fine){
  .mq__hold:hover{ background:var(--ink); border-color:var(--ink); color:#fff }
}
.mq__hold:focus-visible{ outline:3px solid var(--red); outline-offset:3px }
.mq[data-playing="true"] .i-play,
.mq:not([data-playing="true"]) .i-pause{ display:none }

/* Reduced motion: no drift at all. The duplicates are decoration, so they go,
   and the three real reviews become an ordinary scrollable row. */
@media (prefers-reduced-motion:reduce){
  .mq__viewport{ overflow-x:auto; -webkit-mask-image:none; mask-image:none }
  .mq__track{ animation:none; width:auto }
  .mq__item[aria-hidden="true"]{ display:none }
  .mq__ui{ display:none }
  .mq__hold{ transition:none }
}
@media print{
  .mq__viewport{ overflow:visible; mask-image:none }
  .mq__track{ animation:none; width:auto; display:block }
  .mq__item[aria-hidden="true"]{ display:none }
  .mq__ui{ display:none }
}

/* --- 26. Header call button ------------------------------------------------
   The button stays red. The number and the handset were coming out GREY, and
   the reason is specificity, not colour choice:

     .nav--right a   sets color:var(--on-dark)   -> (0,1,1)
     .btn--primary   sets color:#fff             -> (0,1,0)

   The phone button is an <a> inside .nav--right, so the nav's grey link colour
   outranked the button's white and won. That is not only wrong to look at —
   #C9CCD1 on #C40D0D measures 3.83:1, under the 4.5:1 WCAG 1.4.3 requires of
   body text. White on that red is 6.17:1.

   Two classes on the same element (0,2,0) settles it without !important, and
   the icon follows because it is fill:currentColor.
   -------------------------------------------------------------------------- */
.hdr__tel.btn--primary,
.hdr__tel.btn--primary:visited{ color:#fff }
@media (hover:hover) and (pointer:fine){
  .hdr__tel.btn--primary:hover{ color:#fff }
}
/* The shared focus ring is --red, which on the near-black stuck header falls
   below the 3:1 WCAG 1.4.11 asks of a focus indicator. */
.hdr__tel:focus-visible{ outline:3px solid #fff; outline-offset:3px }

/* --- 27. Header free-estimate button --------------------------------------
   Sized to sit level with the call button beside it.

   It carries the same colour override as the phone button, and for the same
   reason: .nav--right a (0,1,1) beats .btn--onDarkGhost (0,1,0), so without
   this the label would come out grey against a white-ish border. Two classes
   on one element (0,2,0) wins it back.
   -------------------------------------------------------------------------- */
.hdr__cta{
  font-size:.84rem; letter-spacing:.02em; padding:.6rem .95rem;
  min-height:42px; white-space:nowrap;
}
.hdr__cta.btn--onDarkGhost,
.hdr__cta.btn--onDarkGhost:visited{ color:#fff }
@media (hover:hover) and (pointer:fine){
  .hdr__cta.btn--onDarkGhost:hover{ background:#fff; color:var(--ink); border-color:#fff }
}
.hdr__cta:focus-visible{ outline:3px solid #fff; outline-offset:3px }

/* A phone number that wraps reads as two numbers. The header had room, but
   flex was still allowed to break the line inside the button. */
.hdr__tel{ white-space:nowrap }
.hdr__tel,.hdr__cta{ flex:0 0 auto }

/* --- 28. Header spacing ----------------------------------------------------
   The logo stays dead centre. That is what `1fr auto 1fr` buys, and it is
   worth keeping.

   The right-hand group was sitting 16px from the mark while the left had 161px,
   and the cause was width, not alignment: the bar was capped at --max (1180px),
   the same measure as body copy. Three short links need 267px; phone + estimate
   + socials need 450px. Two equal 1fr columns could only offer 431px each, so
   the right column overflowed its share and took the difference out of the
   left one — which is exactly the lopsided gap you could see.

   A header is not body copy and does not need its measure, so it gets a wider
   cap of its own plus a guaranteed margin either side of the mark. Both groups
   then clear the logo, and the logo stays centred between them.

   Measured after: gap right went from 16px to ~107px at 1400px wide, and to
   ~187px at 1920px. The left gap stays larger simply because three words weigh
   less than two buttons and a pair of icons; that asymmetry is in the content,
   not in the layout.
   -------------------------------------------------------------------------- */
:root{ --max-hdr:1560px }
.hdr__in{ max-width:var(--max-hdr) }
.hdr__logo{ margin-inline:clamp(.5rem, 2.4vw, 2.25rem) }

/* Under the burger breakpoint the bar is only logo + burger, so the extra
   width and the logo margin have nothing to do — line it back up with the page. */
@media (max-width:1190px){
  .hdr__in{
    max-width:var(--max);
    /* Only the logo and the burger survive here — both navs are display:none.
       In the desktop `1fr auto 1fr` grid those two auto-placed into columns 1
       and 2 and left an entire empty 1fr column to the RIGHT of the burger,
       which parked the menu button in the middle of the bar. Two columns
       pushed apart puts the mark hard left and the menu hard right. */
    grid-template-columns:auto auto;
    justify-content:space-between;
  }
  .hdr__logo{ margin-inline:0 }
}

/* --- 29. Scroll cue bounce -------------------------------------------------
   Matched to Hugo Builders: translateY 0 -> 8px -> 0 over 2s, ease-in-out,
   forever.

   It animates the SVG, never the <a>. The link carries transform:translateX(-50%)
   to centre itself, and a transform animation on the same element replaces
   that outright — the cue would jump to the left edge for the whole loop.
   -------------------------------------------------------------------------- */
@keyframes cue-bounce{
  0%,100%{ transform:translateY(0) }
  50%    { transform:translateY(8px) }
}
.hero__scroll svg{ animation:cue-bounce 2s ease-in-out infinite }
@media (prefers-reduced-motion:reduce){
  .hero__scroll svg{ animation:none }
}

/* --- 30. Anchor landings ---------------------------------------------------
   The header is fixed and 66px tall, and goes opaque as soon as you scroll, so
   an in-page anchor put the target's top edge at y=0 — underneath it. Section
   padding is the only cushion, and clamp(3.5rem,8vw,6.5rem) only reaches 66px
   at about 825px wide; below that the eyebrow ("WHAT WE DO", "WHAT PEOPLE
   SAY") landed behind the bar. On a phone the menu is the ONLY navigation
   there is, so every one of its links landed short.
   -------------------------------------------------------------------------- */
:where(section[id], [id]#main){ scroll-margin-top:calc(var(--hdr-h) + .75rem) }

/* --- 31. Focus indicators and contrast on dark ground -----------------------
   The shared ring is `3px solid var(--red)`. On white that is 6.16:1 and fine.
   On the near-black header, the mobile menu, the dark sections and the footer
   it measures about 2.4:1 — under the 3:1 that WCAG 1.4.11 requires of a focus
   indicator, on precisely the surfaces a keyboard user tabs through first.
   -------------------------------------------------------------------------- */
.hdr :focus-visible,
.mobmenu :focus-visible,
.section--dark :focus-visible,
footer :focus-visible,
.ftr :focus-visible{ outline-color:#fff }

/* The required marker was --red on the dark estimate ground: 2.99:1, which is
   the exact failure DESIGN-SYSTEM.md forbids. #FF6B6B on #141416 is 6.7:1 and
   still reads as the brand red. */
.section--dark .req,
footer .req,
.ftr .req{ color:#FF6B6B }

/* The field focus state removed the outline entirely and replaced it with a
   border colour change worth 1.99:1 and a 1.08:1 halo — neither of which is a
   focus indicator by any measure. Put a real ring back; the border change and
   the halo can stay as decoration on top of it. */
.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible{
  outline:3px solid var(--red);
  outline-offset:2px;
}
.section--dark .field input:focus-visible,
.section--dark .field select:focus-visible,
.section--dark .field textarea:focus-visible{ outline-color:#fff }

/* The status message sets its own foreground; make the link follow it rather
   than inherit whatever the surrounding dark section decided. */
.form-status--err a,.form-status--ok a{ color:var(--ink) }

/* .ba clips its overflow to keep the wipe inside its rounded corners, which
   also clipped the slider's focus ring away entirely — a control with no
   visible focus at all. Draw the ring inside the box instead. */
.ba__range:focus-visible{ outline:3px solid #fff; outline-offset:-5px }

/* --- 32. Landscape phone: keep the hero CTAs on screen ---------------------
   The 520px-tall tier handles the shortest phones. Between 521px and about
   660px there was no tier at all, so a 100vh hero pushed its buttons past the
   bottom edge. Smaller now that the call bar no longer takes a slice.
   -------------------------------------------------------------------------- */
@media (max-width:1190px) and (orientation:landscape){
  .hero__content{
    padding-bottom:calc(env(safe-area-inset-bottom, 0px) + 1rem);
  }
}

/* --- 33. Phone hero: video behind, headline out ----------------------------
   The headline is hidden VISUALLY, never removed. display:none would take the
   page's only <h1> out of the accessibility tree and out of the document
   outline on exactly the devices most visitors arrive on.
   -------------------------------------------------------------------------- */
@media (max-width:700px){
  .hero h1{
    position:absolute; width:1px; height:1px; padding:0; margin:-1px;
    overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
  }
  /* The seam sat under the headline. With the headline gone it would have
     drawn a red rule directly under the eyebrow. */
  .hero .seam{ display:none }
  .hero .eyebrow{ margin-bottom:1.6rem }
}

.hero__video{ display:none }
@media (max-width:700px){
  .hero__video{
    display:block; position:absolute; inset:0;
    width:100%; height:100%; max-width:none;
    object-fit:cover; object-position:50% 50%;
    /* Fades in over the photograph once it can play, so there is never a
       black rectangle waiting for a download to finish. */
    opacity:0; transition:opacity 700ms var(--ease);
  }
  .hero__video.is-ready{ opacity:1 }
}
@media (prefers-reduced-motion:reduce){
  .hero__video{ display:none !important }
}

/* No pause control, by request — this matches how Hugo Builders ships the
   same effect. What stands in for it, and costs nothing on screen:
   prefers-reduced-motion removes the clip entirely and leaves the
   photograph, the clip stops the moment the hero scrolls out of view, and
   it stops when the tab is hidden. So the motion is confined to the top of
   the page and anyone who has asked their device for less of it gets a
   still image. Note for the record: WCAG 2.2.2 asks for a way to stop
   motion that starts on its own and runs past five seconds, and without a
   control this section does not meet it. */

/* --- 34. Short standalone pages --------------------------------------------
   A 404 has less content than a viewport, so the body background showed as a
   white band between the footer and the call bar. Push the footer to the
   bottom instead of padding the content to fake a full page.
   -------------------------------------------------------------------------- */
.page-min{ display:flex; flex-direction:column; min-height:100vh; min-height:100svh }
.page-min main{ flex:1 0 auto }
.page-min .ftr{ flex:0 0 auto }

/* --- 35. Inner pages -------------------------------------------------------
   Everything below reuses the homepage's tokens, reveal system and components.
   The only genuinely new pieces are the masthead, the photo gallery and the
   prose column for the legal pages.
   -------------------------------------------------------------------------- */

/* The masthead replaces the hero on inner pages. It is deliberately NOT
   100vh — a full-screen banner on a service page is a wall between the
   visitor and what they came for. */
.pagehead{ padding-block:clamp(5.5rem,11vw,8rem) clamp(3rem,6vw,4.5rem) }
.pagehead h1{ font-size:clamp(2.3rem,6.4vw,4.2rem); line-height:.95 }
.pagehead > .wrap > .reveal > p{ margin-inline:auto; max-width:60ch }

/* Breadcrumb: small, quiet, and the only thing above the masthead heading. */
.crumb{
  font-size:.78rem; letter-spacing:.12em; text-transform:uppercase;
  font-family:'Archivo',sans-serif; font-weight:700;
}
.crumb a{ color:inherit }
.crumb span{ opacity:.55; padding:0 .45rem }

/* --- Photo gallery ---
   Portrait phone photographs, so the grid is built for tall frames rather
   than forcing them into landscape crops that cut people's heads off. */
.gallery{
  display:grid; gap:1.1rem; margin-top:2.5rem; padding:0; list-style:none;
  grid-template-columns:repeat(auto-fill,minmax(min(100%,240px),1fr));
}
.gallery li{ margin:0 }
.gallery figure{ margin:0 }
.gallery__frame{
  position:relative; overflow:hidden; border-radius:var(--radius);
  background:var(--sand); aspect-ratio:3/4;
}
.gallery__frame img{
  width:100%; height:100%; object-fit:cover; display:block;
  transition:transform 600ms var(--ease);
}
@media (hover:hover) and (pointer:fine){
  .gallery__frame:hover img{ transform:scale(1.04) }
}
.gallery figcaption{
  margin-top:.6rem; font-size:.86rem; color:var(--muted); line-height:1.4;
}

/* --- Service page: how the work actually runs --- */
.steps{ counter-reset:step; list-style:none; padding:0; margin:2.5rem 0 0;
        display:grid; gap:1.1rem }
.steps li{
  counter-increment:step; position:relative;
  padding:1.5rem 1.5rem 1.5rem 4.6rem;
  background:var(--paper); border:1px solid var(--hair);
  border-radius:var(--radius);
}
.steps li::before{
  content:counter(step,decimal-leading-zero);
  position:absolute; left:1.4rem; top:1.35rem;
  font-family:'Archivo',sans-serif; font-weight:800; font-stretch:88%;
  font-size:1.5rem; color:var(--red); font-variant-numeric:tabular-nums;
}
.steps h3{ margin:0 0 .35rem; font-size:1.08rem }
.steps p{ margin:0; color:var(--muted) }
.section--dark .steps li{ background:rgba(255,255,255,.045); border-color:rgba(255,255,255,.14) }
.section--dark .steps p{ color:var(--on-dark) }

/* --- Prose column, for the legal pages --- */
.prose{ max-width:70ch; margin-inline:auto }
.prose h2{ margin-top:2.6rem; font-size:clamp(1.3rem,2.6vw,1.7rem) }
.prose h3{ margin-top:1.8rem; font-size:1.05rem }
.prose p,.prose li{ color:var(--ink) }
.prose ul,.prose ol{ margin:1rem 0 1rem 1.15rem; display:grid; gap:.5rem }
.prose li{ padding-left:.2rem }
.prose .updated{
  font-size:.86rem; color:var(--muted);
  padding-bottom:1.4rem; border-bottom:1px solid var(--hair);
}
.prose a{ color:var(--red-deep); text-underline-offset:2px }

/* --- A pair of related-page cards at the foot of every inner page --- */
.next{ display:grid; gap:1.1rem; margin-top:2.5rem;
       grid-template-columns:repeat(auto-fit,minmax(min(100%,260px),1fr)) }
.next a{
  display:block; padding:1.5rem; text-decoration:none;
  background:var(--paper); border:1px solid var(--hair);
  border-radius:var(--radius); color:var(--ink);
  transition:border-color var(--dur-micro) var(--ease),
             transform var(--dur-micro) var(--ease);
}
.next a strong{ display:block; font-family:'Archivo',sans-serif; font-weight:800;
                font-stretch:92%; font-size:1.05rem; margin-bottom:.3rem }
.next a span{ color:var(--muted); font-size:.92rem }
@media (hover:hover) and (pointer:fine){
  .next a:hover{ border-color:var(--line); transform:translateY(-2px) }
}
@media (prefers-reduced-motion:reduce){
  .next a,.gallery__frame img{ transition:none }
}

/* A customer's or the company's own words, set apart from our prose so it is
   obvious which is which. */
.quote{
  margin:1.4rem 0; padding-left:1.1rem;
  border-left:3px solid var(--red);
  font-size:1.05rem; line-height:1.5; color:var(--ink);
}
.section--dark .quote{ color:#fff }


/* An odd number of tiles leaves the last one stranded in the left column. This
   matches ONLY when the final tile also sits at an odd position — that is, only
   when it is actually an orphan — and centres it at the same width as its
   neighbours rather than letting it stretch across both columns. */
@media (min-width:640px){
  .tiles > li:last-child:nth-child(odd){
    grid-column:1 / -1;
    justify-self:center;
    width:calc(50% - .5rem);
  }
}

/* The primary nav carries five links now, not three. At the old 1.4rem gap it
   needed 446px and wrapped to two rows anywhere under ~1285px — which would
   have meant putting a burger menu on 1280px laptops, a very common width.
   Tightening the gap on this nav alone buys back the ~30px needed. The
   secondary nav keeps the wider spacing; it holds buttons, not links. */
.nav--left{ gap:1rem }

/* --- 36. Album gallery -----------------------------------------------------
   129 photographs in the order they appear in the client's album, in every
   orientation. CSS columns rather than grid: a masonry column flow lets each
   photograph keep its own shape instead of being cropped into a uniform box,
   which is the whole point of showing the real pictures.
   -------------------------------------------------------------------------- */
.album{
  column-count:2; column-gap:.9rem;
  margin-top:2.5rem; padding:0; list-style:none;
}
@media (min-width:700px){ .album{ column-count:3; column-gap:1rem } }
@media (min-width:1100px){ .album{ column-count:4 } }
@media (min-width:1600px){ .album{ column-count:5 } }

.album li{
  break-inside:avoid;            /* a photograph must never split across columns */
  page-break-inside:avoid;
  margin:0 0 .9rem;
}
.album img{
  display:block; width:100%; height:auto;
  border-radius:var(--radius); background:var(--sand);
}

/* Video cells in the album. No custom player: a <video> with `controls`,
   `preload="none"` and a poster already shows the still frame and the
   browser's own play button, and downloads nothing until somebody asks. That
   is fewer moving parts than a bespoke overlay and it cannot break. */
.album video{
  display:block; width:100%; height:auto;
  border-radius:var(--radius); background:#000;
}

/* The clips sit in their own row ABOVE the photo masonry.

   They cannot simply be the first items of the masonry: CSS columns flow top
   to bottom and only then across, so "first" would stack all six down the
   left-hand column rather than reading as a band across the top. A grid gives
   them the row. */
/* Five of the six clips are 9:16 and one is 16:9, so a fixed grid left a hole
   the height of a portrait clip beside the landscape one. Columns pack around
   it the way the album below does, and at the widest breakpoint six columns
   means exactly one clip each — a single clean band. */
/* Five clips, all shot vertically. They used to be a CSS-columns masonry
   because a sixth, landscape clip was mixed in; with that one cut they are all
   the same shape, so a plain grid is right and columns would only invent a
   ragged edge. Two up on a phone with the odd one centred, five across in one
   flush row above — the same last-child centring the homepage tiles use.

   aspect-ratio pins them to a single shape: four are 576x1024 and one is
   480x848, a 0.4% difference that would otherwise leave one video two pixels
   short of the row. */
.clips{
  display:grid; gap:.9rem; margin:0; padding:0; list-style:none;
  grid-template-columns:repeat(2,1fr);
}
.clips li{ margin:0 }
.clips li:last-child:nth-child(odd){
  grid-column:1 / -1; justify-self:center; width:calc(50% - .45rem);
}
.clips video{
  display:block; width:100%; height:auto;
  aspect-ratio:9 / 16; object-fit:cover;
  border-radius:var(--radius); background:#000;
}
@media (min-width:900px){
  .clips{ gap:1rem; grid-template-columns:repeat(5,1fr) }
  .clips li:last-child:nth-child(odd){ grid-column:auto; justify-self:stretch; width:auto }
}
/* The photo grid follows straight on, so it does not need its own top margin. */
.clips + .album{ margin-top:.9rem }

/* --- 37. Very narrow phones ------------------------------------------------
   The 404 header is the only one that carries the logo AND the phone button
   with no burger between them. At 320px those two together ran six pixels past
   the right edge, clipping the number. Everything else already fits. */
@media (max-width:380px){
  .hdr__tel{ font-size:.78rem; padding:.55rem .7rem; gap:.35rem }
  .hdr__tel svg{ width:14px; height:14px }
}

/* --- 38. Our Work — five sections ------------------------------------------
   The page stopped being one gallery. Five groups, four different counts, and
   the shapes inside them are not alike: 93 exteriors run to thirteen different
   aspect ratios, the 18 kitchens are all landscape, the 12 bathrooms are all
   portrait, and the garage group is five pictures.

   So the grid under each header changes and the header itself never does —
   same eyebrow, same two-line h2, same seam, same distance down to the first
   photograph. That constant is what lets the layout underneath vary without
   the page coming apart. */


/* A uniform-aspect group does not need masonry, and masonry actively hurts it:
   columns leave a ragged foot, while a plain grid closes flush. 18 kitchens
   divide by 2 and 3; 12 bathrooms by 2, 3 and 4 — neither ever ends on a short
   row at any breakpoint below. */
.shots{
  display:grid; gap:.9rem; margin:0; padding:0; list-style:none;
}
.shots li{ margin:0 }
.shots img{
  display:block; width:100%; height:auto;
  border-radius:var(--radius); background:var(--sand);
}
@media (min-width:700px){ .shots{ gap:1rem } }

.shots--wide{ grid-template-columns:repeat(2,1fr) }
@media (min-width:900px){ .shots--wide{ grid-template-columns:repeat(3,1fr) } }

.shots--tall{ grid-template-columns:repeat(2,1fr) }
@media (min-width:700px){  .shots--tall{ grid-template-columns:repeat(3,1fr) } }
@media (min-width:1100px){ .shots--tall{ grid-template-columns:repeat(4,1fr) } }

/* Four photographs, all portrait, all the same shape. Two up on a phone and
   four up above it — 4 divides by both, so the group is a flush rectangle at
   every width and never ends on a short row.

   This was briefly a 5-item grid with the odd landscape spanning the middle
   two columns. The landscape was a trade-show booth, not work, and the client
   cut it. */
.band{
  display:grid; gap:.9rem; margin:0; padding:0; list-style:none;
  grid-template-columns:repeat(2,1fr);
}
.band li{ margin:0 }
.band img{
  display:block; width:100%; height:auto;
  border-radius:var(--radius); background:var(--sand);
}
@media (min-width:700px){
  .band{ gap:1rem; grid-template-columns:repeat(4,1fr) }
}

/* The heading block sits on the page's own rhythm; the photographs start a
   little closer than a normal section would, so header and grid read as one
   unit rather than two stacked things. */
.section > .wrap > .grouphead{ margin-bottom:clamp(1.75rem,3.5vw,2.75rem) }

/* The homepage before/after is a landscape pair and can run the full measure.
   The garage pair is 700x934 portrait — at full width that is 1,575px tall and
   the handle ends up somewhere off the bottom of the screen. Cap it and centre
   it so the whole comparison is visible at once, which is the only way a slider
   is worth having. */
.ba--tall{ max-width:540px; margin-inline:auto }

/* The interior photographs. Seven are landscape and five portrait, so this is a
   masonry and not a grid — a fixed grid leaves a hole under every short one.

   Column counts match the album and the bathroom grid exactly. They were 1-up
   on a phone and 2-up on desktop, on the argument that coffered ceilings and
   beams need the room; what that actually bought was twelve full-width
   photographs to scroll past and a section visibly out of step with every
   other one on the page. Consistency wins. */
.showpiece{
  columns:2; column-gap:.9rem; margin:0; padding:0; list-style:none;
}
.showpiece li{ break-inside:avoid; page-break-inside:avoid; margin:0 0 .9rem }
.showpiece img{
  display:block; width:100%; height:auto;
  border-radius:var(--radius); background:var(--sand);
}
@media (min-width:700px){
  .showpiece{ columns:3; column-gap:1rem }
  .showpiece li{ margin-bottom:1rem }
}
@media (min-width:1100px){ .showpiece{ columns:4 } }

/* --- 39. The What We Do tiles are links again -----------------------------
   Each one opens the matching section of Our Work. They were plain pictures
   for a while; as links they need a focus ring a keyboard user can actually
   see over a photograph, and a hover that says "clickable" without moving the
   layout — the image scales inside its own clipped box, so nothing reflows. */
a.tile__inner{ display:block; text-decoration:none; color:inherit }
a.tile__inner:focus-visible{ outline:3px solid var(--red); outline-offset:3px }
@media (hover:hover) and (pointer:fine){
  a.tile__inner img{ transition:transform 520ms var(--ease) }
  a.tile__inner:hover img{ transform:scale(1.045) }
}
@media (prefers-reduced-motion:reduce){
  a.tile__inner img,
  a.tile__inner:hover img{ transition:none; transform:none }
}

/* --- 40. Review platform buttons ------------------------------------------
   One link per platform the reviews actually came from, each carrying that
   platform's own mark. Both files are the real thing — Angi's straight off
   their own CDN, Google's the four-colour G — rather than a lookalike drawn
   from memory, which is the sort of detail a visitor half-notices.

   White cards on the sand ground so the coloured marks have somewhere quiet to
   sit, and the border darkens on hover instead of the fill flipping: the logos
   have to stay legible, so the button cannot go dark underneath them. */
.btn-row--brands{ justify-content:center; gap:.9rem; margin-top:2rem }
.btn--brand{
  background:var(--paper); color:var(--ink); border-color:var(--line);
  gap:.6rem; padding-inline:1.15rem;
}
/* Sized by eye, not by number: a wordmark and a single glyph set to the same
   height do not read as the same size. */
.btn--brand img{ width:auto; flex:0 0 auto; display:block }
.btn--brand img[src$="logo-angi.svg"]{ height:1.15em }
.btn--brand img[src$="logo-google.svg"]{ height:1.3em }
@media (hover:hover) and (pointer:fine){
  .btn--brand:hover{ background:var(--paper); color:var(--ink); border-color:var(--ink) }
}

/* --- 41. Commercial reference letters -------------------------------------
   Recommendation letters from HOA boards, owners and property managers. They
   are the strongest thing on the site for the commercial half of the business,
   and they are the only place a scale figure appears — 387 townhomes says
   something no photograph can.

   Masonry, not a grid: the quotes are different lengths and a fixed grid would
   leave a gap under every short one. Sand cards on the paper ground, the exact
   inverse of the review cards, which are paper on sand. */
.letters{
  columns:1; column-gap:1rem; margin:2.5rem 0 0; padding:0; list-style:none;
}
@media (min-width:760px){  .letters{ columns:2 } }
@media (min-width:1180px){ .letters{ columns:3 } }
.letters li{ break-inside:avoid; page-break-inside:avoid; margin:0 0 1rem }
.letter{
  margin:0; background:var(--sand); border-radius:var(--radius);
  padding:1.6rem 1.5rem 1.3rem;
}
.letter p{ margin:0 0 1.15rem; font-size:1.02rem; line-height:1.55 }
.letter footer{
  display:grid; gap:.22rem;
  border-top:1px solid var(--hair); padding-top:.9rem;
}
.letter cite{
  font-style:normal; font-family:'Archivo',sans-serif; font-weight:800;
  font-stretch:92%; font-size:.98rem; color:var(--ink);
}
.letter__role{ font-size:.87rem; line-height:1.35; color:var(--muted) }
/* The one number on the card, and the only red in the section. */
.letter__scale{
  margin-top:.4rem; font-family:'Archivo',sans-serif; font-weight:800;
  font-stretch:92%; font-size:.78rem; letter-spacing:.09em;
  text-transform:uppercase; color:var(--red);
}

/* Side by side rather than stacked. The pair's natural width is 259px + 279px
   + gap = 552px, so they need about 612px of viewport before they fit at full
   size — NOT the 560px a phone breakpoint would suggest. Cutting over at 560
   left a dead band from 561 to about 615 where they silently stacked again,
   which is exactly the width a small tablet or a half-screen window sits at.
   The switch happens at 680, comfortably clear of it.

   min-height stays 48px throughout so the tap target never shrinks with the type. */
@media (max-width:680px){
  .btn-row--brands{ flex-wrap:nowrap; gap:.55rem }
  .btn--brand{ flex:1 1 0; min-width:0; text-align:left }
}
/* Roomy enough for one line: just pull the type in a little. */
@media (min-width:561px) and (max-width:680px){
  .btn--brand{ font-size:.85rem; letter-spacing:.03em; padding:.7rem .85rem }
}
/* Phone: the label wraps to two lines and the logo still follows it. */
@media (max-width:560px){
  .btn--brand{
    padding:.55rem .6rem; gap:.4rem;
    font-size:.74rem; letter-spacing:.02em; line-height:1.2;
  }
  .btn--brand img[src$="logo-angi.svg"]{ height:1.05em }
  .btn--brand img[src$="logo-google.svg"]{ height:1.2em }
}

/* --- 42. Recent projects carousel -----------------------------------------
   The same marquee as the reviews, carrying entrance signs instead of quotes.
   The JS already loops every [data-marquee] on the page, so a second one needs
   no script at all — only its own card and a slower duration to match the
   narrower cards' travel.

   Every card is a fixed 4:3 box with object-fit:cover. The reference the client
   sent had the images at their natural shapes, which left the four captions
   sitting at four different heights. Uniform boxes put every name on the same
   line. The source crops were chosen per photograph so no sign is cut — the
   Harmony one is a tall tower and needed its own window. */
/* .mq__item is display:flex for the review cards, which stretches its child to
   the row height. A stretched <figure> leaves the image with no definite width
   to resolve `width:100%` against, so aspect-ratio was ignored and the browser
   fell back to the height ATTRIBUTE — 480px in a 272px-wide card. Block layout
   plus an explicit height:auto lets the 4:3 box actually apply. */
.mq--projects .mq__item{ display:block; flex:0 0 clamp(200px,58vw,272px) }
.mq--projects .mq__track{ animation-duration:64s }
.project{ margin:0; width:100% }
.project img{
  display:block; width:100%; height:auto;
  aspect-ratio:4/3; object-fit:cover;
  border-radius:var(--radius); background:var(--hair);
}
.project figcaption{
  margin-top:.8rem; text-align:center;
  font-family:'Archivo',sans-serif; font-weight:800; font-stretch:92%;
  font-size:.98rem; line-height:1.25; color:var(--ink);
}
