/* ==========================================================================
   Randy House Home Repairs — structural clone
   01 Tokens/Base  02 Buttons  03 Topbar  04 Navbar  05 Hero
   06 Quality      07 About    08 Services 09 Renovation
   10 Reviews      11 Gallery  12 CTA      13 Footer  14 Lightbox  15 Responsive
   ========================================================================== */

/* ------------------------------------------------ 01 TOKENS / BASE ------ */
:root {
  /* Brand: a light grey field with burgundy on the bands and every
     action. A few deliberate dark blocks (hero, footer) anchor the page. */
  --paper:       #EFEFF1;   /* page ground - light grey */
  --surface:     #FFFFFF;   /* cards and panels */
  --surface-alt: #E6E8EA;   /* alternate light band */
  --line:        #D9DCDF;   /* borders on light */
  --ink:         #191B1D;   /* body text */
  --ink-soft:    #5F676D;   /* muted text */
  --accent:      #6E1E2E;   /* burgundy */
  --accent-dark: #561523;   /* burgundy hover */
  --accent-deep: #4A1220;   /* deepest burgundy - the dark end of the CTA
                               gradient. Kept separate from --accent-dark so
                               that token still means one thing: hover. */
  --accent-lift: #9A3247;   /* burgundy lifted - the light end of the CTA
                               gradient, and that block's button hover, which
                               has to rise off a dark ground rather than sink
                               into it */
  --accent-wash: #F0DFE2;   /* pale burgundy, for type on dark */
  --dark:        #22252A;   /* dark anchor blocks */
  --grey:        #7A828A;

  /* Hi-vis yellow. The ONE colour on this site that is not burgundy, grey or
     white, and it exists for exactly one job: the free-estimate card at the
     foot of the home page. It works precisely because nothing else competes
     with it - use it anywhere else and it stops meaning "click this". */
  --hi-vis:      #FFC629;
  --hi-vis-lift: #FFD456;

  /* Blueprint texture - now dark lines on a light field */
  --grid-fine:   rgba(25,27,29,.045);
  --grid-major:  rgba(25,27,29,.075);

  --max: 1200px;
  --gutter: 40px;

  --font: 'Barlow', system-ui, -apple-system, 'Segoe UI', Arial, sans-serif;
  /* Condensed cut of the same family, for small all-caps labels set with
     wide tracking - the before/after labels on Home, and the dates, quote
     attribution and rule numbers on About. Loaded by those two pages only,
     so the rest are not made to pay for a font they never draw with. */
  --font-display: 'Barlow Condensed', 'Barlow', system-ui, Arial, sans-serif;
}

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

html { scroll-behavior: smooth; scroll-padding-top: 210px; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 32px;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a   { color: inherit; text-decoration: none; }
ul  { margin: 0; padding: 0; list-style: none; }
p   { margin: 0 0 8px; }

h1,h2,h3,h5,h6 { margin: 0; font-weight: 700; }
h1 { font-size: 60px; line-height: 66px; }
h2 { font-size: 48px; line-height: 72px; }
h3 { font-size: 24px; line-height: 28px; }

/* shared 1200px row */
.row {
  width: 100%;
  max-width: var(--max);
  margin: 0 auto;
  display: flex;
  align-items: stretch;
}
.row--block { display: block; }

.eyebrow {
  font-size: 16px;
  line-height: 16px;
  letter-spacing: 4px;
  text-transform: uppercase;
  font-weight: 600;
  margin: 8px 0;
  padding: 2px 0;
}
.eyebrow--center { text-align: center; }
.h2--center { text-align: center; }

.lede { text-align: center; margin: 8px 0; padding: 2px 0; }

/* ------------------------------------------------ 02 BUTTONS ----------- */
.btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60px;
  margin: 10px auto;
  padding: 16px 8px;
  max-width: 100%;
  border: 2px solid transparent;
  border-radius: 6px;
  color: #fff;
  font-size: 16px;
  font-weight: 500;
  line-height: 22px;
  text-align: center;
  cursor: pointer;
  transition: filter .2s ease, background-color .2s ease, color .2s ease;
}
.btn--red     { background: var(--accent);  border-color: var(--accent); }
.btn--red:hover { filter: brightness(.88); }
.btn--outline { background: transparent; border-color: #fff; }
.btn--outline:hover { background: #fff; color: var(--ink); }

.btn--222 { width: 222px; }
.btn--234 { width: 234px; }
.btn--250 { width: 250px; }
.btn--294 { width: 294px; }
.btn--50  { min-height: 50px; }

/* icon buttons: icon sits left of the centred label, like the reference */
.btn--phone { padding-left: 48px; padding-right: 8px; }
.btn--phone::before,
.flipbtn__front--calendar::before {
  content: "";
  position: absolute;
  left: 18px; top: 50%;
  transform: translateY(-50%);
  width: 24px; height: 24px;
  background: #f7f7f7;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
}
.btn--outline.btn--phone::before { background: #fff; }
.btn--outline.btn--phone:hover::before { background: var(--ink); }
.btn--phone::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.6 10.8a15.1 15.1 0 0 0 6.6 6.6l2.2-2.2a1 1 0 0 1 1-.25 11.4 11.4 0 0 0 3.6.58 1 1 0 0 1 1 1V20a1 1 0 0 1-1 1A17 17 0 0 1 3 4a1 1 0 0 1 1-1h3.5a1 1 0 0 1 1 1 11.4 11.4 0 0 0 .58 3.6 1 1 0 0 1-.25 1z'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.6 10.8a15.1 15.1 0 0 0 6.6 6.6l2.2-2.2a1 1 0 0 1 1-.25 11.4 11.4 0 0 0 3.6.58 1 1 0 0 1 1 1V20a1 1 0 0 1-1 1A17 17 0 0 1 3 4a1 1 0 0 1 1-1h3.5a1 1 0 0 1 1 1 11.4 11.4 0 0 0 .58 3.6 1 1 0 0 1-.25 1z'/%3E%3C/svg%3E");
}
.flipbtn__front--calendar::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 2v2h10V2h2v2h2a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h2V2h2zM4 9v10h16V9H4z'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 2v2h10V2h2v2h2a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h2V2h2zM4 9v10h16V9H4z'/%3E%3C/svg%3E");
}

/* ------------------------------------------------ 03 TOPBAR ------------ */
.site-header {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  z-index: 101;
}

.topbar { background: var(--accent); padding: 8px var(--gutter); }
.topbar__row { display: flex; align-items: center; }
.topbar__left  { width: 33.3333%; padding-right: 1.5%; }
.topbar__left p { margin: 0; font-size: 14px; line-height: 22px; }

.topbar__right {
  width: 66.6667%;
  padding-left: 1.5%;
  display: flex;
  align-items: center;
  max-width: var(--max);
  margin: 0 auto;
}
.topbar__mail   { width: 50%; padding-right: 1.5%; }
.topbar__phone  { width: 25%; padding: 0 .75%; }

.topbar__mail p, .topbar__phone p {
  margin: 8px 0 8px auto;
  padding: 2px 0 2px 24px;
  font-size: 14px; line-height: 22px;
  text-align: right;
  background-repeat: no-repeat;
  background-position: 0 50%;
  width: fit-content;
}
.topbar__mail p  { background-image: url("../assets/img/email-w.svg"); }
.topbar__phone p { background-image: url("../assets/img/phone-w.svg"); }
.topbar__mail a:hover, .topbar__phone a:hover { text-decoration: underline; }

.social { display: flex; justify-content: flex-end; align-items: center; gap: 6px; }
.social a {
  width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  transition: transform .2s ease, opacity .2s ease;
  opacity: .95;
}
.social a:hover { transform: translateY(-2px); opacity: 1; }
.social svg { width: 100%; height: 100%; fill: #fff; }

/* ------------------------------------------------ 04 NAVBAR ------------ */
.navbar {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  padding: 16px var(--gutter);
}
.navbar__row { display: flex; align-items: center; }

.navbar__logo { width: 16.6667%; padding-right: 1.5%; }

.navbar__right {
  width: 83.3333%;
  padding-left: 1.5%;
  display: flex;
  align-items: center;
}
.nav { flex: 1 1 auto; min-width: 0; padding-right: 12px; display: flex; justify-content: center; }
.nav ul { display: flex; flex-wrap: wrap; justify-content: center; }
.nav a {
  display: flex; align-items: center;
  height: 48px;
  padding: 10px 8px;
  font-size: 14px; line-height: 28px; letter-spacing: 1.4px;
  white-space: nowrap;
  transition: color .2s ease;
}
@media (min-width: 1600px) {
  .nav a { padding: 10px 12px; letter-spacing: 2px; }
}
.nav a:hover, .nav a.is-active { color: var(--accent); }

.navbar__cta { flex: 0 0 auto; padding-left: 12px; }
.navbar__cta .btn { width: 250px; }

.nav-toggle {
  display: none;
  width: 46px; height: 46px;
  margin-left: auto;
  background: transparent;
  border: 2px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
  padding: 0;
  flex-direction: column; align-items: center; justify-content: center; gap: 5px;
}
.nav-toggle span { width: 22px; height: 2px; background: var(--ink); transition: transform .25s ease, opacity .2s ease; }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* ------------------------------------------------ 05 HERO -------------- */
.hero {
  background: url("../assets/img/AdobeStock_270195962-1920w.jpeg") center/cover no-repeat;
}
.hero__overlay {
  min-height: 850px;
  padding: 0 var(--gutter);
  background:
    linear-gradient(100deg, rgba(46,12,20,.93) 0%, rgba(46,12,20,.82) 44%, rgba(46,12,20,.32) 100%),
    url("../assets/img/kv-cover02-1920w.webp") center/cover no-repeat;
}
.hero__row {
  display: flex;
  max-width: var(--max);
  min-height: 850px;
  margin: 0 auto;
}
.hero__col      { width: 58.3333%; padding-right: 1.5%; }

.hero__col h1 { margin: 8px 0; padding: 2px 0; }
.hero__col > p { margin: 8px 0; padding: 2px 0; }
.hero__actions { display: flex; gap: 24px; margin-top: 8px; }
.hero__actions .btn { margin-left: 0; margin-right: 0; }

/* ------------------------------------------------ 06 QUALITY ----------- */
.quality {
  background:
    radial-gradient(900px 420px at 12% 18%, rgba(110,30,46,.20), transparent 62%),
    repeating-linear-gradient(0deg,  var(--grid-fine) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(90deg, var(--grid-fine) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(0deg,  var(--grid-major) 0 1px, transparent 1px 200px),
    repeating-linear-gradient(90deg, var(--grid-major) 0 1px, transparent 1px 200px),
    var(--paper);
  padding: 60px var(--gutter);
}
/* The photo runs the full height of the column and the slider sits at the
   top of the copy, above the eyebrow. The two used to overlap, with the
   slider pulled up over a collage that had transparent space built into the
   file to receive it; that only worked for that one image. Nothing here is
   tied to a particular photograph. */
.quality .row { align-items: stretch; }
.quality__media { width: 50%; padding-right: 1.5%; }
.quality__photo {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
  border: 4px solid #fff;
  border-radius: 8px;
}

.quality__text { width: 50%; padding-left: 1.5%; }
.quality__text .ba { margin-bottom: 24px; }
.quality__text h2 { margin: 8px 0; padding: 2px 0; }
.quality__text p  { margin: 8px 0; padding: 2px 0; }

/* before / after comparison */
.ba {
  position: relative;
  width: 100%;
  aspect-ratio: 1200 / 904;   /* the native shape of both slide frames */
  border: 4px solid #fff;
  border-radius: 8px;
  overflow: hidden;
  user-select: none;
}
.ba__img { width: 100%; height: 100%; object-fit: cover; pointer-events: none; }
/* The clip covers the whole box and the handle drives its clip-path inset
   from the right, so whatever sits inside it is the half shown on the LEFT.
   That is the before shot; the after shot is the base image showing through
   on the right. Named ba__clip rather than ba__after precisely because it
   holds the before image. Clipping rather than resizing keeps both frames
   registered to each other at any box width - the old width-driven version
   needed a hard-coded pixel width to stop the before shot re-cropping as it
   narrowed. */
.ba__clip {
  position: absolute; inset: 0;
  clip-path: inset(0 50% 0 0);
  pointer-events: none;
}
.ba__handle {
  position: absolute; top: 50%; left: 50%;
  width: 46px; height: 46px;
  margin: -23px 0 0 -23px;
  padding: 0;
  background: transparent;
  border: 4px solid #fff;
  border-radius: 50%;
  cursor: ew-resize;
}
.ba__handle::before, .ba__handle::after {
  content: ""; position: absolute; top: 50%; width: 0; height: 0;
  border-top: 7px solid transparent; border-bottom: 7px solid transparent;
  transform: translateY(-50%);
}
.ba__handle::before { left: 6px;  border-right: 10px solid #fff; }
.ba__handle::after  { right: 6px; border-left:  10px solid #fff; }

/* ---- the slider has to say that it slides ----
   A still frame with a circle on it reads as a caption badge, not a control,
   and most people never touch it. The handle breathes and throws a ring from
   the moment it is on screen until somebody actually uses it. It runs on
   rather than stopping after a few passes because a hint that has already
   given up is a hint that was not there when the reader looked up.

   It pulses in place. An earlier version walked the seam left and right, which
   demonstrated the control perfectly and was too much: the photograph moving
   by itself pulls the eye off the copy beside it, and the point is a hint, not
   a performance. Scale and box-shadow only - the handle must never leave the
   join it sits on, and transform-origin is its own centre, so it cannot.

   These are also the two properties js/main.js never touches. The drag writes
   inline `left` and `clip-path`; a running animation outranks an inline style,
   so anything animating those would fight the drag and win. Keep it to
   transform and box-shadow and the two can never collide.

   .ba is overflow: hidden, but the ring reaches 16px around a 46px handle
   parked at the centre of the frame, so there is nothing near an edge to clip.

   It still stands down the moment anyone reaches for it - pointer down, touch,
   the mouse merely arriving, or focus on the handle. A hint that keeps going
   after the thing has been used is just noise. */
@keyframes baHintPulse {
  0%        { transform: scale(1);    box-shadow: 0 0 0 0    rgba(255, 255, 255, .55); }
  18%       { transform: scale(1.14); box-shadow: 0 0 0 7px  rgba(255, 255, 255, .3); }
  45%       { transform: scale(1); }
  62%, 100% { transform: scale(1);    box-shadow: 0 0 0 16px rgba(255, 255, 255, 0); }
}
.ba--hint .ba__handle { animation: baHintPulse 2.4s ease-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .ba--hint .ba__handle { animation: none; }
}

/* The two words sit inside the frame at the top, Before on the left and
   After on the right, so the slider says what it is without a caption under
   it. Placement is what keeps them honest: "Before" lives INSIDE .ba__clip,
   so it is clipped away with the before photo and can never end up sitting
   over the after shot; "After" is a sibling painted before the clip layer,
   so the opaque before photo covers it once the handle is dragged past it.
   Both are inert to the pointer - the whole box is a drag surface. */
.ba__label {
  position: absolute;
  top: 16px;
  padding: 7px 16px 6px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 16px;
  line-height: 1;
  letter-spacing: .2em;
  text-indent: .2em;           /* re-centres text that the tracking pushed left */
  text-transform: uppercase;
  color: #fff;
  border: 1px solid rgba(255,255,255,.3);
  border-radius: 4px;
  box-shadow: 0 4px 16px rgba(25,27,29,.3);
  -webkit-backdrop-filter: blur(6px) saturate(120%);
  backdrop-filter: blur(6px) saturate(120%);
  pointer-events: none;
}
.ba__label--before { left: 16px;  background: rgba(25,27,29,.62); }
.ba__label--after  { right: 16px; background: rgba(110,30,46,.82); }

@media (max-width: 720px) {
  .ba__label { top: 10px; padding: 5px 11px 4px; font-size: 13px; letter-spacing: .16em; text-indent: .16em; }
  .ba__label--before { left: 10px; }
  .ba__label--after  { right: 10px; }
}

/* ------------------------------------------------ 07 ABOUT ------------- */
/* the source tints this photo blue via a stylesheet rule the inline dump didn't carry */
.about {
  background:
    linear-gradient(rgba(239,239,241,.95), rgba(239,239,241,.95)),
    url("../assets/img/AdobeStock_400588469-1920w.jpeg") center/cover no-repeat;
  padding: 0 var(--gutter) 60px;
}
.about__text    { width: 41.6667%; padding-right: 1.5%; }
.about__cardcol { width: 58.3333%; padding-left: 1.5%; display: flex; align-items: center; }

.about__spacer { height: 60px; }
.about__text h2 { margin: 8px 0; padding: 2px 0; }
.about__text p  { margin: 8px 0; padding: 2px 0; }
.about__text .btn { margin-left: 0; }

.about__card {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 40px;
}
.about__card h2 { margin-bottom: 8px; padding: 2px 0; }

.difference { display: grid; grid-template-columns: 1fr 1fr; gap: 20px 0; margin-top: 8px; }
.difference__item {
  padding: 2px 0 2px 62px;
  min-height: 76px;
  display: flex; align-items: center;
  background-repeat: no-repeat;
  background-position: 0 50%;
  background-size: 48px;
}
.difference__item h3 { font-size: 24px; line-height: 36px; }
.difference__item--medal  { background-image: url("../assets/img/icon_medal.svg"); }
.difference__item--family { background-image: url("../assets/img/icon_family.svg"); }
.difference__item--tools  { background-image: url("../assets/img/icon_tools.svg"); }
.difference__item--craft  { background-image: url("../assets/img/icon_craftsmanship.svg"); }

/* ------------------------------------------------ 08 SERVICES ---------- */
.services {
  background:
    radial-gradient(1000px 500px at 82% 0%, rgba(58,64,70,.55), transparent 62%),
    repeating-linear-gradient(0deg,  var(--grid-fine) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(90deg, var(--grid-fine) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(0deg,  var(--grid-major) 0 1px, transparent 1px 200px),
    repeating-linear-gradient(90deg, var(--grid-major) 0 1px, transparent 1px 200px),
    var(--surface-alt);
  padding: 60px var(--gutter) 80px;
}
.services .eyebrow { line-height: 16px; }
.services h2 { margin: 8px 0; padding: 2px 0; }

.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin-top: 20px;
}
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform .25s ease, box-shadow .25s ease;
}
.card:hover { transform: translateY(-6px); box-shadow: 0 24px 48px rgba(0,0,0,.45); }
/* height:auto is load-bearing. The width and height attributes on these tags
   are presentational hints, so height="617" lands as height:617px - and once
   both dimensions are set, aspect-ratio has nothing left to compute and
   object-fit:cover crops the picture instead of fitting it. The card then
   looks zoomed in. Same trap as .cta__figure img, for the same reason. */
.card > img {
  width: 100%;
  height: auto;
  aspect-ratio: 1920/1280;
  object-fit: cover;
}

/* ---- painting cards: still image with a hover clip layered over it ----
   The still stays in the DOM underneath and the video fades in on top, so
   mouse-out always returns to a crisp frame rather than wherever playback
   happened to stop. A video that fails to load simply never becomes
   visible, which leaves the still doing its job. */
.card__media {
  position: relative;
  width: 100%;
  aspect-ratio: 1920/1280;
  overflow: hidden;
  background: var(--surface-alt);
}
.card__media > img,
.card__media > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.card__media > video {
  opacity: 0;
  transition: opacity .3s ease;
  pointer-events: none;
}
.card__media.is-playing > video { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .card__media > video { display: none; }
}
.card__body {
  padding: 20px 40px 40px;
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  flex: 1;
}
.card__body h3 { margin: 8px 0; padding: 2px 0; }
.card__body p  { margin: 8px 0 auto; padding: 2px 0 12px; }

/* ------------------------------------------------ 09 RENOVATION -------- */
.renovation { display: flex; background: var(--paper); }
.renovation__text {
  width: 50%;
  background:
    linear-gradient(115deg, var(--surface-alt), var(--paper)),
    var(--paper);
  padding: 60px 1.5% 60px max(var(--gutter), calc((100vw - var(--max)) / 2));
}
.renovation__inner h2 { margin-bottom: 8px; padding: 2px 0; }
.renovation__lede { margin: 8px 0; padding: 2px 0; }

/* Icons and supplier logos sit centred against their text block rather
   than pinned to the top. Real logos vary in height, so centring keeps
   each row balanced as they get swapped in one by one. */
.feature { display: flex; align-items: center; margin-top: 12px; }
/* The icon column is a fixed basis, not a percentage. It used to be 16.67%
   of .renovation__text, whose padding grows with the viewport - so the
   column got NARROWER as the screen got wider (126px at 1440 against 196px
   at 961). A landscape supplier logo cannot live in a column that behaves
   like that. The svg icons stay 80px and right-aligned, so they sit where
   they always did; only the empty space to their left changes. .feature is
   used in this one section on index.html and nowhere else. */
.feature__icon { flex: 0 0 190px; padding-right: 1.5%; }
.feature__icon svg { width: 80px; height: 80px; margin: 10px 0 10px auto; display: block; }
.feature__text { flex: 1 1 auto; min-width: 0; padding-left: 1.5%; }
/* the icon column leaves this about 150px at 320px wide, which is narrower
   than "Floorcoverings" sets at this size - let long names break rather than
   push the page sideways */
.feature__text h3 { margin: 8px 0; padding: 2px 0; overflow-wrap: break-word; }
.feature__text p  { margin: 8px 0; padding: 2px 0; }

/* Each supplier's logo and heading links through to materials.html. Site-wide
   a is colour:inherit with no underline, so without a hover state there is
   nothing to tell you these are clickable at all - hence the colour shift on
   the name and the slight fade on the logo.
   The logo link is aria-hidden and out of the tab order deliberately: the
   heading link beside it goes to the same page, and leaving both in reads the
   supplier's name twice to a screen reader and takes two tab stops to get
   past. The logo is a mouse target only; the heading is the real link. */
.feature__namelink { transition: color .18s ease; }
.feature__namelink:hover,
.feature__namelink:focus-visible { color: var(--accent); }
.feature__logolink { display: block; transition: opacity .18s ease; }
.feature__logolink:hover { opacity: .78; }

/* The photo column stands still while the copy scrolls past it.
   Two things are load-bearing here. align-self overrides the flex row's
   default stretch: sticky can only travel if the item is shorter than its
   container, and stretched it is already full height with nowhere to go.
   And sizing it to the viewport rather than to the text column is what keeps
   it sharp - the panel used to ask for ~1780px of image height, so
   remodel.webp (1024px tall) was blown up about 1.7x. Against the viewport
   it asks for roughly 730px instead and is downscaled, which is the whole
   reason for doing this.

   --sticky-top is the sticky header's height. The header is 171px between
   1201 and 1500 and 187px either side of that, and this deliberately takes
   the smaller one: where the header is taller the photo tucks a few pixels
   under its glass, which is invisible, rather than leaving a strip of bare
   paper above itself, which would not be. */
.renovation__image {
  --sticky-top: 171px;
  width: 50%;
  background: url("../assets/img/remodel.webp") center/cover no-repeat;
  position: sticky;
  top: var(--sticky-top);
  align-self: flex-start;
  height: calc(100vh - var(--sticky-top));
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0 20px 30px;
}

/* ---- the wordmark sitting on the photo ----
   Reuses .logo__text / .logo__row / .logo__tools from the header lockup, so
   it picks up every one of that lockup's responsive steps without restating
   them. Flex end/centre rather than absolute positioning on purpose: the
   panel is sticky on desktop but static at 1024, and an absolutely placed
   child would break out to the wrong ancestor the moment that changes.
   The plate is the navbar's own glass recipe at the same values - solid
   fallback first, real blur only where it is supported - so it reads as the
   same material as the header rather than a new one invented here. */
.photomark {
  /* A shade smaller than the header lockup - it is a mark on a photo, not a
     second masthead. zoom rather than transform: it scales the box model, so
     the plate shrinks with its contents and the type is rasterised at the
     size it ends up, where a transform would leave the box full size and can
     soften the text. Browsers without zoom simply show it at full size. */
  zoom: .62;
  padding: 14px 26px 16px;
  border-radius: 8px;
  background: rgba(255, 255, 255, .94);
  box-shadow: 0 1px 18px rgba(25, 27, 29, .07);
}
@supports ((-webkit-backdrop-filter: blur(2px)) or (backdrop-filter: blur(2px))) {
  .photomark {
    background: rgba(255, 255, 255, .68);
    -webkit-backdrop-filter: blur(16px) saturate(150%);
            backdrop-filter: blur(16px) saturate(150%);
  }
}

/* ------------------------------------------------ 11 GALLERY ----------- */
.gallery {
  background:
    radial-gradient(820px 400px at 18% 100%, rgba(110,30,46,.16), transparent 62%),
    repeating-linear-gradient(0deg,  var(--grid-fine) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(90deg, var(--grid-fine) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(0deg,  var(--grid-major) 0 1px, transparent 1px 200px),
    repeating-linear-gradient(90deg, var(--grid-major) 0 1px, transparent 1px 200px),
    var(--surface-alt);
  padding: 60px var(--gutter);
}
.gallery h2 { margin: 8px 0; padding: 2px 0; }

/* Three columns on a wide screen: a note either side of the photo, which is
   what fills the room the 3:4 card leaves over. Grid areas rather than source
   order, because the photo has to come FIRST once the layout stacks - reading
   two paragraphs before reaching the thing they are about would be backwards
   on a phone. */
.workgrid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  grid-template-areas: "lead gallery close";
  align-items: center;
  gap: 30px;
  margin-top: 18px;
}
.workgrid__lead  { grid-area: lead; }
.workgrid__close { grid-area: close; }
.workgrid .flipgal { grid-area: gallery; }

.workgrid h3 {
  margin: 0 0 8px;
  font-size: 21px;
  line-height: 28px;
  color: var(--accent);
}
.workgrid p {
  margin: 0;
  font-size: 15.5px;
  line-height: 27px;
  color: var(--ink-soft);
}

@media (max-width: 1024px) {
  /* photo across the top, the two notes side by side beneath it */
  .workgrid {
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "gallery gallery"
      "lead    close";
    gap: 4px 30px;
  }
}
@media (max-width: 720px) {
  .workgrid {
    grid-template-columns: 1fr;
    grid-template-areas: "gallery" "lead" "close";
    gap: 22px;
  }
}

/* ---------------------------------------------------------------------------
   FLIP GALLERY - a card that splits across the middle and flips like a
   departure board. Ported from a React/Tailwind component; the animation
   itself was never React, it is the Web Animations API turning rotateX on
   four stacked halves, so the port is the same effect with no dependencies.

   Four layers: top and bottom hold the image at rest, otop and obottom are
   the leaves that swing. Each is half the card's height with the background
   sized 100% x 200% - i.e. scaled to the WHOLE card - and pinned to top or
   bottom, so the two halves line up as one photo. Sizing it in percentages
   rather than the original's fixed pixels is what lets the card be fluid.

   Every photo is stored 3:4 and the card is 3:4, so nothing is ever squashed
   to fit; the nine landscape originals were cropped once, at build time.
   --------------------------------------------------------------------------- */
.flipgal {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* fit-content so the nav bar below is exactly as wide as the card, whose
     own width is derived from its height and so is not a number we can
     restate here */
  width: fit-content;
  max-width: 100%;
  /* the CTA figure below overhangs its section by 78px when the layout is
     stacked, and its hard hat reaches up into the middle of this column -
     without the bottom margin the caption and the hat collide by 12px */
  margin: 22px auto 26px;
}
.flipgal__card {
  position: relative;
  /* Driven by height, not width. The photos are 3:4, so a width-driven card
     gets very tall very quickly; sizing off the viewport height instead lets
     it fill the section on a wide screen without running off the bottom of a
     short one. Width follows from the ratio. */
  height: min(82vh, 860px);
  width: auto;
  aspect-ratio: 3 / 4;
  perspective: 900px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--surface-alt);
  box-shadow: 0 18px 40px rgba(25, 27, 29, .22);
}
/* A ground under the layers, because they now start empty: the photos are not
   fetched until the gallery is scrolled to, and without this the card is a
   white hole on the page until then. */
.flipgal__card { background: var(--surface-alt); }
.flipgal__card > .unite {
  position: absolute;
  width: 100%;
  height: 50%;
  overflow: hidden;
  background-size: 100% 200%;
  background-repeat: no-repeat;
  backface-visibility: hidden;
}
.flipgal__top, .flipgal__otop {
  top: 0;
  transform-origin: bottom;
  background-position: top;
}
.flipgal__bottom, .flipgal__obottom {
  bottom: 0;
  transform-origin: top;
  background-position: bottom;
}
.flipgal__bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-top: 16px;
  width: 100%;
}
.flipgal__caption {
  flex: 1 1 auto;
  margin: 0;
  transition: opacity .3s ease;
  text-align: center;
  font-size: 14px;
  line-height: 20px;
  color: var(--ink-soft);
  min-height: 40px;
}
.flipgal__caption strong {
  display: block;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}
.flipgal__btn {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface);
  color: var(--accent);
  cursor: pointer;
  transition: background .18s ease, color .18s ease, border-color .18s ease;
}
.flipgal__btn svg { width: 20px; height: 20px; display: block; }
.flipgal__btn:hover,
.flipgal__btn:focus-visible { background: var(--accent); border-color: var(--accent); color: #fff; }

@media (max-width: 720px) {
  /* width-driven here: on a phone the limit is how wide the screen is, not
     how tall, and height-driven sizing would leave a narrow strip on a short
     handset */
  .flipgal { width: 100%; }
  .flipgal__card { width: min(360px, 100%); height: auto; }
  .flipgal__bar { width: min(360px, 100%); gap: 10px; }
}
.gallery__item { width: 25%; padding: 6px; display: block; }
.gallery__item span {
  display: block;
  padding-top: 100%;
  background-position: 50% 50%;
  background-size: cover;
  background-repeat: no-repeat;
  border-radius: 6px;
  transition: transform .35s ease;
}
.gallery__item:hover span { transform: scale(1.03); }

/* ------------------------------------------------ 12 CTA --------------- */
/* Right to left across the block: deepest at the right, where the white
   heading and paragraph sit and want the contrast, easing a step lighter at
   the left, behind the cut-out photo, so the dark denim has something to
   separate from. Three stops rather than two - a straight accent-to-dark ramp
   crosses too little ground over 1200px to read as anything but a flat wash. */
.cta {
  background: linear-gradient(to left,
              var(--accent-deep) 0%, var(--accent) 52%, var(--accent-lift) 100%);
  padding: 0 var(--gutter);
}
.cta .row { align-items: center; min-height: 416px; }
.cta__figure { width: 33.3333%; padding-right: 1.5%; margin-top: -100px; align-self: flex-end; }
.cta__figure img { width: 100%; height: auto; }  /* height:auto or the tag's
   height attribute survives as a presentational hint and stretches it */
.cta__text { width: 66.6667%; padding-left: 1.5%; }
.cta__text h2 { font-size: 40px; line-height: 60px; margin: 8px 0; padding: 2px 0; }
.cta__text p  { margin: 8px 0; padding: 2px 0; }
/* Centred on the HEADING, not on the column. Centring in the column pushes
   the button to the right of everything it belongs to, because the paragraph
   under the heading runs ~700px while the heading runs 486. Measured at 1025,
   1280 and 1920: the heading is one line of 485.92px at every desktop width,
   since its 40px size is fixed rather than fluid - so the offset that centres
   a 222px button beneath it is a constant (486 - 222) / 2. Re-measure if the
   heading copy or its font-size changes; the stacked layout below 1025px
   overrides this back to auto. */
.cta__text .btn { margin-left: 132px; margin-right: auto; }
/* Burgundy on burgundy has no edge of its own, so the button reads as loose
   text until it is outlined. White at the base 2px, matching the type it
   holds. Scoped to this block on purpose - .btn--red elsewhere sits on the
   light grey ground, where a white border would just disappear.
   The stock .btn--red hover dims the whole element with a brightness
   filter, which would drag the white border down to grey with it; swap it
   for a background change so the edge stays white, and lift rather than
   darken so the fill rises off this block instead of sinking into it. */
/* Deliberately NOT the stock --accent fill, and not --accent-dark either.
   The button has to stay darker than whatever the gradient puts behind it, and
   widening that gradient moved the ground: measured behind the button it now
   runs rgb(109,30,46) to rgb(94,25,40) at 1025px. --accent is level with the
   top of that, and --accent-dark's rgb(86,21,35) closes to eight units of the
   bottom of it - near-matches that read as muddy inside the white outline.
   --accent-deep is the darkest value on the block and the gradient only
   reaches it at the far right edge, nowhere near the button, so the fill sits
   a clear 20 to 37 units below its ground at every desktop width. */
.cta__text .btn--red { border-color: #fff; background: var(--accent-deep); }
.cta__text .btn--red:hover { filter: none; background: var(--accent-lift); }

/* ---- Home: the free-estimate card ----
   The one hi-vis element on the site, and the yellow is now the card itself
   rather than a trim on a white one - this sits on a burgundy band and its job
   is to be the thing you cannot miss. Burgundy, white, yellow, nothing else.

   Offer first, scarcity as a kicker above it. An earlier version led on "I
   take one job at a time", which reads as a limitation before it reads as a
   reason; the constraint works better as a small line establishing that he is
   busy, with the actual offer taking the headline.

   Everything on the kicker is true and already said elsewhere on the site -
   one man, no crew, voicemail because he is on a job. It is not a counter, a
   countdown or a seat tally, and it must not become one.

   The whole card is the link. If anything is ever added under it that is
   itself a link, it has to sit OUTSIDE the card - an anchor cannot contain
   another anchor, and browsers repair that nesting unpredictably. */
.ctacard {
  display: block;
  max-width: 560px;
  background: var(--accent-deep);
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 18px 44px rgba(25, 27, 29, .42);
  transition: transform .22s ease, box-shadow .22s ease;
}
.ctacard:hover {
  transform: translateY(-4px);
  box-shadow: 0 26px 58px rgba(25, 27, 29, .42);
}
.ctacard:focus-visible { outline: 3px solid #fff; outline-offset: 4px; }

/* hi-vis strip across the top of the burgundy */
.ctacard__flash {
  display: block;
  background: var(--hi-vis);
  color: var(--ink);
  padding: 11px 24px 10px;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
}

.ctacard__inner { display: block; padding: 26px 24px 26px; }

/* White, which is why the card is dark. White on the hi-vis yellow measures
   about 1.57:1 - far under the 4.5:1 floor, and under even the 3:1 allowed for
   large text - so a white headline and a yellow card cannot both be had. The
   card went dark and the yellow moved to the strip and the button, where it
   still does the pointing. On this ground white lands at 15.1:1. */
.ctacard__h {
  display: block;
  font-size: 32px;
  line-height: 40px;
  font-weight: 700;
  color: #fff;
  margin-bottom: 10px;
}
.ctacard__p {
  display: block;
  font-size: 16px;
  line-height: 27px;
  color: rgba(255, 255, 255, .86);
  margin-bottom: 20px;
}

/* Hi-vis on the burgundy - the loudest thing on the card, which is right for
   the one element that is the actual action. Ink type on it reads 10.99:1. It
   carries no interaction of its own; the card around it is the link. */
.ctacard__go {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 60px;
  padding: 15px 22px;
  background: var(--hi-vis);
  border-radius: 6px;
  color: var(--ink);
  font-size: 17px;
  font-weight: 700;
  line-height: 24px;
  transition: background-color .2s ease;
  animation: ctaPulse 2.6s ease-out infinite;
}
.ctacard:hover .ctacard__go { background: var(--hi-vis-lift); }
.ctacard__go svg {
  width: 20px; height: 20px; flex: none;
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform .22s ease;
  animation: ctaNudge 2.6s ease-in-out infinite;
}
/* Superseded by ctaNudge while it runs - an animation outranks this in the
   cascade - but it is the arrow's hover state under `reduce`, where the
   animation never starts. The rule below then zeroes it there. */
.ctacard:hover .ctacard__go svg { transform: translateX(4px); }

/* ---- the estimate button beckons ----
   The one action the site is actually asking for, so it is the one thing that
   moves on its own. Both loops run on the same 2.6s so they read as a single
   gesture rather than two animations competing: the halo blooms once at the top
   of the cycle, the arrow beckons twice under it, then both rest for the last
   third. A ring that never rests is nagging, and on a site whose credibility is
   a tradesman talking straight, nagging costs more than it wins.

   The halo is box-shadow spread, so it grows a uniform 16px on every side rather
   than in proportion to a 510px-wide button, and stays inside the card's 24px
   inner padding - .ctacard is `overflow: hidden` and anything wider is cut off
   at the sides. The 3% breath under it is what actually reads: a ring alone, at
   the alpha it has to sit at to not look like a glow bug, was almost invisible
   against the deep burgundy. Colour is --hi-vis, #FFC629, written out because
   box-shadow needs the alpha.

   Nothing here invents urgency. It is emphasis on an offer that is already on
   the card, not a counter, a countdown or a seat tally - see the note above. */
@keyframes ctaPulse {
  0%        { transform: scale(1);    box-shadow: 0 0 0 0    rgba(255, 198, 41, .6); }
  15%       { transform: scale(1.03); box-shadow: 0 0 0 6px  rgba(255, 198, 41, .38); }
  40%       { transform: scale(1); }
  55%, 100% { transform: scale(1);    box-shadow: 0 0 0 16px rgba(255, 198, 41, 0); }
}
@keyframes ctaNudge {
  0%, 12%   { transform: translateX(0); }
  26%       { transform: translateX(6px); }
  40%       { transform: translateX(0); }
  54%       { transform: translateX(6px); }
  68%, 100% { transform: translateX(0); }
}

@media (prefers-reduced-motion: reduce) {
  .ctacard, .ctacard__go, .ctacard__go svg { transition: none; }
  .ctacard:hover { transform: none; }
  .ctacard:hover .ctacard__go svg { transform: none; }
  .ctacard__go, .ctacard__go svg { animation: none; }
}

/* ---- Home: FAQ ----
   Native <details>/<summary> - no JS, keyboard and screen-reader accessible
   for free. The marker is a tiny hammer, drawn from two rotated bars rather
   than an icon file - same move as the tick on .supplier__list and the
   hamburger's three spans, nothing to 404. It swings flat on open; that swing
   is the only motion here, so it's the only thing stood down below. */
.faq { background: var(--paper); padding: 80px var(--gutter); }
.faq__inner { max-width: 800px; margin: 0 auto; }
.faq__inner h2 { margin: 8px 0 40px; }
.faq__list { border-top: 1px solid var(--line); }
.faq__item { border-bottom: 1px solid var(--line); padding: 22px 0; }
.faq__item summary {
  position: relative;
  padding-right: 34px;
  cursor: pointer;
  font-size: 19px;
  font-weight: 600;
  line-height: 28px;
  list-style: none;
}
.faq__item summary::-webkit-details-marker { display: none; }
/* Handle and head share one pivot point (15px, 12px in this local frame) via
   each one's own transform-origin, so rotating both by the same angle swings
   them as a single rigid hammer instead of two bars spinning independently. */
.faq__item summary::before,
.faq__item summary::after {
  content: "";
  position: absolute;
  top: 50%;
  background: var(--accent);
  border-radius: 1px;
  transform: rotate(45deg);
  transition: transform .25s ease;
}
/* handle */
.faq__item summary::before {
  width: 2px;
  height: 12px;
  right: 15px;
  margin-top: -4px;
  transform-origin: 50% 100%;
}
/* head, capping the handle's top end at a right angle */
.faq__item summary::after {
  width: 8px;
  height: 3px;
  right: 12px;
  margin-top: -5px;
  transform-origin: 4px 13px;
}
/* Swung down further, as if it just struck. */
.faq__item[open] summary::before,
.faq__item[open] summary::after { transform: rotate(135deg); }
@media (prefers-reduced-motion: reduce) {
  .faq__item summary::before,
  .faq__item summary::after { transition: none; }
}
.faq__item p { margin: 14px 0 0; color: var(--ink-soft); }
.faq__item p a { text-decoration: underline; }

@media (max-width: 720px) {
  .faq { padding: 56px var(--gutter); }
  .faq__item summary { font-size: 17px; line-height: 26px; }
}

/* ------------------------------------------------ 13 FOOTER ------------ */
.footer__main {
  background:
    repeating-linear-gradient(0deg,  var(--grid-fine) 0 1px, transparent 1px 32px),
    repeating-linear-gradient(90deg, var(--grid-fine) 0 1px, transparent 1px 32px),
    var(--dark);
  padding: 60px var(--gutter);
}
.footer__brand { width: 25%; padding-right: 1.5%; }
.footer__brand > p {
  margin: 0 0 8px;
  padding: 2px 0;
  font-size: 14px; line-height: 28px;
  text-align: center;
  /* Too long for this 25% column on one line, and the default wrap leaves
     "Areas" alone on the second. Balance splits it evenly; browsers that
     don't support it just wrap as before. */
  text-wrap: balance;
}
.social--footer { justify-content: center; margin-top: 10px; }

/* The brand column centres on its own tagline at every width. The lockup is a
   flex row and its two lines stack flush left by design, so neither answers to
   text-align - they need justify-content and align-items. The 18px indent on
   the first line is what offsets that flush-left lockup from the badge; once
   the lines centre on each other it just pushes the wordmark off the middle. */
.footer__brand .logo { justify-content: center; padding: 18px 0; }
.footer__brand .logo__text { align-items: center; }
.footer__brand .logo__text strong { padding-left: 0; }

/* The lockup gets its own size in the footer - the shared one is drawn for the
   header, which has the whole row to spread into.

   The badge sits on its own line, so it can grow as large as it looks right at
   either width. The wordmark cannot: it is what has to fit the column, and on
   desktop that column is 25% of the row - 219px of content at 1025px, against
   204px of wordmark here. Centred, that leaves 7px a side and 1025px is the
   worst case, so this is as large as the desktop wordmark goes without
   widening the column itself.
   Stacked, the column is the full width and there is room to spare. */
@media (min-width: 1025px) {
  .footer__brand .logo__text strong { font-size: 25px; letter-spacing: 4px; }
  .footer__brand .logo__text em     { font-size: 21px; letter-spacing: .6px; }
  .footer__brand .logo__badge       { width: 64px; height: 64px; }
}
@media (max-width: 1024px) {
  .footer__brand .logo__text strong { font-size: 30px; letter-spacing: 5px; }
  .footer__brand .logo__text em     { font-size: 22px; letter-spacing: .8px; }
  .footer__brand .logo__badge       { width: 64px; height: 64px; }
}

.footer__cols { width: 75%; padding-left: 1.5%; display: flex; }
.footer__col h6 { font-size: 18px; line-height: 36px; margin: 8px 0; padding: 2px 0; }

.footer__col--links   { width: 25%;      padding-right: 1.5%; }
.footer__col--contact { width: 41.6667%; padding: 0 .75%; }
.footer__col--hours   { width: 33.3333%; padding-left: 1.5%; }

.footer__col--links a {
  display: flex; align-items: center;
  min-height: 36px;
  padding: 4px 0;
  font-size: 14px; line-height: 28px; letter-spacing: 2px;
  color: var(--grey);
  transition: color .2s ease;
}
.footer__col--links a:hover { color: #fff; }

.footer__phone p, .footer__mail p {
  margin: 8px 0;
  padding: 2px 0 2px 24px;
  font-size: 14px; line-height: 22px;
  background-repeat: no-repeat;
  background-position: 0 50%;
  width: fit-content;
}
.footer__phone p { background-image: url("../assets/img/phone-w.svg"); }
.footer__mail p  { background-image: url("../assets/img/email-w.svg"); }
.footer__phone a:hover, .footer__mail a:hover { text-decoration: underline; }


.hours__row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding-bottom: 10px;
  font-size: 14px;
  line-height: 22px;
}
.hours__row span:first-child { width: 40%; }
.hours__row span:last-child  { width: 59%; text-align: right; }

.footer__bar { background: var(--accent); padding: 10px var(--gutter); }
.footer__bar .row { align-items: center; }
.footer__copy   { width: 66.6667%; padding-right: 1.5%; }
.footer__copy p { margin: 0; font-size: 14px; line-height: 28px; }
.footer__copy a { text-decoration: underline; }
.footer__credit {
  width: 33.3333%; padding-left: 1.5%;
  font-size: 14px; line-height: 28px;
  text-align: right;
}
/* The legal links replaced the second "Serving Memphis, Bartlett" line, which
   already appears in the topbar, in the footer brand block and in the hero on
   most pages - it was the fourth time of asking. */
.footer__credit a { text-decoration: underline; }
.footer__credit a + a { margin-left: 18px; }
@media (max-width: 1024px) {
  .footer__credit { text-align: left; margin-top: 6px; }
  .footer__credit a + a { margin-left: 14px; }
}

/* ------------------------------------------------ 14 LIGHTBOX ---------- */
.lightbox {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0,0,0,.9);
  display: grid; place-items: center;
  padding: 40px;
}
.lightbox[hidden] { display: none; }
.lightbox img { max-width: 90vw; max-height: 85vh; border-radius: 6px; }
.lightbox__close {
  position: absolute; top: 20px; right: 28px;
  background: none; border: 0; color: #fff;
  font-size: 44px; line-height: 1; cursor: pointer;
}

/* ------------------------------------------------ 15 RESPONSIVE -------- */
@media (max-width: 1200px) {
  .renovation__text { padding-left: var(--gutter); }
}

@media (max-width: 1024px) {
  h1 { font-size: 44px; line-height: 52px; }
  h2 { font-size: 36px; line-height: 50px; }

  .topbar__row   { flex-direction: column; gap: 4px; }
  .topbar__left,
  .topbar__right { width: 100%; padding: 0; justify-content: center; }
  .topbar__left p { text-align: center; }
  .topbar__mail, .topbar__phone { width: auto; padding: 0 10px; }
  .topbar__mail p, .topbar__phone p { margin: 0; }

  .navbar__row { flex-wrap: wrap; }
  .navbar__logo { width: auto; max-width: 220px; padding: 0; }
  .nav-toggle { display: flex; }
  .navbar__right {
    width: 100%;
    flex-direction: column;
    align-items: stretch;
    padding: 12px 0 0;
    display: none;
  }
  .navbar__right.is-open { display: flex; }
  .nav, .navbar__cta { flex: none; width: 100%; padding: 0; }
  .nav ul { flex-direction: column; }
  .nav a { border-bottom: 1px solid var(--line); }
  .navbar__cta .btn { width: 100%; }

  .hero__row, .quality .row, .about .row, .cta .row,
  .footer__main .row, .footer__bar .row { flex-direction: column; }

  .hero__col,
  .quality__media, .quality__text,
  .about__text, .about__cardcol,
  .cta__figure, .cta__text,
  .footer__brand, .footer__cols { width: 100%; padding-left: 0; padding-right: 0; }

  /* Stacked, the photo has no column height to fill, and its own 2:3 would
     run a whole screen on its own. Crop it shallower, and hold the crop high
     enough that the man on the ladder does not get cut off the top. */
  .quality__media { margin-bottom: 24px; }
  .quality__photo {
    height: auto;
    aspect-ratio: 4 / 3;
    object-position: center 22%;
  }

  .hero__overlay, .hero__row { min-height: 0; }
  .hero__overlay { padding-bottom: 60px; }


  .about { padding-top: 60px; }
  .about__spacer { display: none; }
  .about__cardcol { margin-top: 30px; }

  .cards { grid-template-columns: repeat(2, 1fr); }

  .renovation { flex-direction: column; }
  .renovation__text, .renovation__image { width: 100%; }
  .renovation__image { min-height: 340px; position: static; height: auto; align-self: auto; }


  /* Stacked, the figure is capped at 300px where the desktop column gives it
     about 382px, so the desktop -100px overhang is scaled to match rather
     than carried over whole: -78px is the same fraction of the image, which
     keeps the hard hat breaking the top edge by the same amount it does on a
     wide screen instead of a much bigger one. */
  .cta__figure { margin-top: -78px; max-width: 300px; align-self: center; }
  .cta__text { padding-bottom: 40px; text-align: center; }
  .cta__text .btn { margin: 10px auto; }
  .ctacard { max-width: 100%; }
  .ctacard__flash { justify-content: flex-start; }

  .footer__cols { flex-wrap: wrap; gap: 20px 0; }
  .footer__col--links, .footer__col--contact, .footer__col--hours { width: 100%; padding: 0; }
  .footer__copy, .footer__credit { width: 100%; padding: 0; text-align: center; }

  /* Stacked, the footer centres. The closing CTA above it is centred at this
     width and the copyright bar below always was, so the bottom of every page
     reads as one block instead of three alignments in a column. Desktop keeps
     its four left-aligned columns. */
  .footer__main { text-align: center; }
  /* Quick links are flex rows, and the phone and mail lines are fit-content
     boxes carrying their icon as left padding, so neither answers to
     text-align. (The lockup's own centring is unscoped, further up.) */
  .footer__col--links a { justify-content: center; }
  .footer__phone p, .footer__mail p { margin-left: auto; margin-right: auto; }
  /* Hours stay a two-column table - you read down the values - so the block
     centres rather than the rows. Natural label widths, because the desktop
     40/59 split wraps once the block is this narrow. */
  .hours { max-width: 250px; margin: 0 auto; }
  .hours__row span:first-child,
  .hours__row span:last-child { width: auto; }
}

@media (max-width: 720px) {
  :root { --gutter: 20px; }

  h1 { font-size: 34px; line-height: 42px; }
  h2 { font-size: 28px; line-height: 38px; }
  body { line-height: 28px; }

  .hero__actions { flex-direction: column; gap: 0; }
  .hero__actions .btn { width: 100%; }

  /* A phone column is narrow enough that 4:3 gets squat. */
  .quality__photo { aspect-ratio: 1 / 1; }

  .about__card { padding: 24px; }
  .difference { grid-template-columns: 1fr; }

  .cards { grid-template-columns: 1fr; }
  .card__body { padding: 20px 24px 30px; }

  .feature__icon { flex-basis: 130px; }
  .feature__icon svg { width: 56px; height: 56px; }
  .feature__logo { height: 56px; }

  .gallery__item { width: 50%; }

  .btn--222, .btn--234, .btn--250, .btn--294 { width: 100%; }
}

/* ==========================================================================
   16 SITE COPY COMPONENTS — added when Randy's real copy was wired in
   ========================================================================== */

/* ---- text logotype (replaces the placeholder logo file) ---- */
.logo { display: flex; align-items: center; gap: 12px; }
.logo__text { display: flex; flex-direction: column; line-height: 1.06; }
.logo__text strong {
  font-size: 21px; font-weight: 700; letter-spacing: .4px; text-transform: uppercase;
}
.logo__text em {
  font-style: normal; font-size: 11px; letter-spacing: 2.6px;
  text-transform: uppercase; color: var(--accent); font-weight: 600;
}

/* ---- trust strip under the hero ---- */
.trust {
  background: var(--surface);
  border-top: 3px solid var(--accent);
  border-bottom: 1px solid var(--line);
  padding: 0 var(--gutter);
}
.trust__row {
  display: flex;
  max-width: var(--max);
  margin: 0 auto;
}
.trust__item {
  flex: 1;
  padding: 22px 20px;
  text-align: center;
  font-size: 17px;
  font-weight: 600;
  line-height: 26px;
  border-right: 1px solid var(--line);
}
.trust__item:last-child { border-right: 0; }

/* ---- compact hero for interior pages ----
   The wash was .88, which left the photograph as a texture you had to look for.
   .72 is the shipped value, .66 the floor below it - and the floor is measured,
   not a taste call. The binding constraint is the eyebrow: #F0DFE2 at 16px/600,
   which WCAG counts as small text and so holds to 4.5:1, not the 3:1 the 48px
   h1 gets. `cover` shows a different slice of the photo at every band shape
   (the middle third of it at desktop's 4.3:1, a tall centre strip at the
   phone's 0.92:1), so this is measured against the brightest area anywhere in
   the file rather than any one crop:

     .72   h1 7.35:1   p 6.14:1   eyebrow 5.73:1   <- here
     .66   h1 5.94:1   p 5.04:1   eyebrow 4.63:1   <- floor
     .64   h1 5.54:1   p 4.73:1   eyebrow 4.32:1   fails on a phone

   Anything heavier than .72 is safe on contrast and only costs the photograph.
   Re-measure before going below it, and re-measure whatever the value if the
   photograph is ever swapped - this one is pale wood, which is why the wash has
   to be as heavy as it is. Home is a different hero with its own gradient and
   is deliberately untouched by this. */
.pagehero {
  background:
    linear-gradient(rgba(46,12,20,.72), rgba(46,12,20,.72)),
    url("../assets/img/AdobeStock_270195962-1920w.jpeg") center/cover no-repeat;
  padding: 70px var(--gutter) 60px;
}
.pagehero__inner { max-width: var(--max); margin: 0 auto; }
.pagehero h1 { font-size: 48px; line-height: 58px; margin: 8px 0; }
.pagehero p { max-width: 68ch; margin: 8px 0 0; }

/* ---- long-form prose blocks (About, Materials) ---- */
.prose { padding: 70px var(--gutter); background: var(--paper); }
.prose__inner { max-width: 780px; margin: 0 auto; }
.prose__inner p { margin: 0 0 22px; }
.prose__inner p:last-child { margin-bottom: 0; }
.prose__inner h2 { font-size: 34px; line-height: 46px; margin: 40px 0 14px; }
.prose__inner h3 {
  font-size: 21px; line-height: 30px; margin: 32px 0 6px; color: var(--accent);
}
.prose--alt { background: var(--surface-alt); }
/* Service Area: the two panels enter from opposite edges and start outside the
   section. clip rather than hidden where understood - hidden would force
   overflow-y to auto and quietly make this a scroll container. */
.area { overflow-x: hidden; overflow-x: clip; }

/* ---- About: a single-sentence lede handing off to the pull quote ---- */
.prose--lead { padding-bottom: 36px; }
.prose__lede { font-size: 19px; line-height: 34px; }

/* ---- About: the father's rule ----
   The lede above breaks off mid-sentence and this finishes it. On the
   burgundy because it is the one line on the page somebody other than Randy
   said, and it should not look like more of his prose. No byline under it:
   the lede has already said whose rule it is. */
.pullquote { background: var(--accent); padding: 54px var(--gutter); }
.pullquote__inner { max-width: 900px; margin: 0 auto; text-align: center; }
.pullquote blockquote {
  margin: 0;
  font-size: 30px;
  line-height: 44px;
  font-weight: 600;
}
.pullquote blockquote::before {
  content: "\201C";
  display: block;
  margin-bottom: -14px;
  font-size: 86px;
  line-height: 1;
  color: rgba(255, 255, 255, .26);
}

/* ---- About: the story as a timeline ----
   Two columns: a heading that holds still while the entries scroll past it,
   and the entries on a rule down the left of their own column. The aside can
   only stick while there is a second column for it to stick beside - below
   1024 it goes static, or it would sit on top of the entries it introduces.
   The offset has to clear the header, which section 21 makes sticky on
   interior pages too - measured at 187px for 1025-1100, 171px for 1200-1440
   and 187px again at 1920, where the wider logo steps back up. 200px clears
   the tallest of those with a little air left over. */
.story { background: var(--surface); padding: 70px var(--gutter); }
.story__inner {
  max-width: var(--max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: 0 80px;
  align-items: start;
}
.story__aside { position: sticky; top: 200px; }
.story__aside h2 { font-size: 38px; line-height: 50px; margin: 8px 0 12px; }
.story__aside p  { margin: 0; color: var(--ink-soft); }

.story__list {
  margin: 0;
  padding: 0 0 0 34px;
  list-style: none;
  border-left: 2px solid var(--line);
}
.story__item { position: relative; padding-bottom: 46px; }
.story__item:last-child { padding-bottom: 0; }
/* The track holds two rules stacked on the same 2px: the list's own border,
   which is the faint full-length one, and .story__progress over it, which
   GSAP scrubs from nothing to full height as the section scrolls. It starts
   at scaleY(0), so if GSAP never loads the burgundy simply never appears and
   the grey rule is what you get - the entries themselves are readable either
   way, which is the point. */
.story__track { position: relative; }
.story__progress {
  position: absolute;
  left: 0;
  top: 0;
  width: 2px;
  height: 100%;
  background: var(--accent);
  transform: scaleY(0);
  transform-origin: 50% 0;
}
/* A real element rather than a pseudo, because GSAP cannot target ::before.
   It sits ON the rule rather than beside it: back out through the list's 34px
   of padding, past the centre of its 2px border, then half the dot's own width
   again. The ring is the section's own background, so the rule appears to pass
   behind the dot instead of touching it. */
.story__dot {
  position: absolute;
  left: -42.5px;
  top: 7px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 5px var(--surface);
}
.story__when {
  display: block;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .2em;
  text-indent: .2em;
  text-transform: uppercase;
  color: var(--accent);
}
.story__item h3 { font-size: 24px; line-height: 32px; margin: 2px 0 8px; }
.story__item p  { margin: 0; }

/* ---- About: how I run a job ----
   The fire-service paragraph was four rules written as running prose.
   Numbering them is the whole change - the sentences are his, untouched. */
/* The rules start their travel outside the section's own box, so it has to
   contain them or the page picks up a horizontal scrollbar on every one. clip
   rather than hidden where it is understood: hidden would force overflow-y to
   compute to auto and quietly make this section a scroll container, which clip
   does not. The hidden declaration first is the fallback for browsers that do
   not know clip. */
.creed {
  background: var(--paper);
  padding: 70px var(--gutter);
  overflow-x: hidden;
  overflow-x: clip;
}
.creed__inner { max-width: 900px; margin: 0 auto; }
.creed__inner h2 { font-size: 34px; line-height: 46px; margin: 8px 0 4px; }
.creed__lede { margin: 0; color: var(--ink-soft); }
.creed__list {
  counter-reset: creed;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 28px 44px;
  margin: 34px 0 0;
  padding: 0;
  list-style: none;
}
.creed__list li {
  counter-increment: creed;
  position: relative;
  padding-left: 60px;
  font-size: 18px;
  line-height: 30px;
}
/* The numeral is its own element so GSAP can drift it independently of the
   sentence beside it - that difference in rate is the parallax. It still draws
   its digits from the list counter rather than hard-coded markup, so the
   numbering stays tied to the list's real order. aria-hidden in the markup: an
   <ol> is already numbered for a screen reader and does not need it read
   twice. */
.creed__num {
  position: absolute;
  left: 0;
  top: -2px;
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 700;
  line-height: 32px;
  letter-spacing: .04em;
  color: var(--accent);
}
.creed__num::before { content: counter(creed, decimal-leading-zero); }
.creed__text { display: block; }
.creed__close { margin: 32px 0 0; font-weight: 600; }

/* ---- Services: jump nav ----
   Home deep-links into all six of this page's anchors, so a reader can arrive
   in the middle of it with no idea what else is here. This row says what the
   page holds and moves between them. Deliberately not sticky: the site header
   already sticks, and a second bar under it eats a third of a phone screen.
   The anchors land correctly off the existing html{scroll-padding-top:210px}. */
.svcnav { background: var(--surface); border-bottom: 1px solid var(--line); padding: 18px var(--gutter); }
.svcnav__row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  max-width: var(--max);
  margin: 0 auto;
  padding: 0;
  list-style: none;
}
.svcnav__row a {
  display: block;
  padding: 8px 16px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 15px;
  font-weight: 600;
  line-height: 22px;
  color: var(--ink);
  transition: background-color .2s ease, border-color .2s ease, color .2s ease;
}
.svcnav__row a:hover { background: var(--accent); border-color: var(--accent); color: #fff; }

/* ---- Services: one block per service ----
   Two columns, image and text swapping sides down the page. Built on Relume's
   layout18 shape - heading, description, then the detail as a checked list
   rather than buried in a paragraph. Every one of these six was already
   written as scope, then process, then materials, then outcome; this only
   sets that structure rather than inventing one, and the sentences are
   unchanged. */
.svc { padding: 66px var(--gutter); background: var(--paper); }
.svc--alt { background: var(--surface); }
.svc__inner {
  max-width: var(--max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 56px;
  align-items: center;
}
/* Source order is always text-then-image, so the text leads on a phone where
   the grid collapses to one column. The flip is a grid-column swap, which
   leaves that reading order alone - reversing the DOM instead would put the
   photo above its own heading on every second block. */
.svc--flip .svc__text  { grid-column: 2; grid-row: 1; }
.svc--flip .svc__media { grid-column: 1; grid-row: 1; }

.svc__text h2 { font-size: 34px; line-height: 44px; margin: 0 0 10px; }
.svc__lede { font-size: 19px; line-height: 32px; font-weight: 600; margin: 0 0 12px; }
.svc__intro { margin: 0 0 14px; color: var(--ink-soft); }

/* Shared with .supplier__list on Materials - the two lists do the same job on
   two pages and should not drift apart. */
.svc__steps,
.supplier__list { margin: 0 0 16px; padding: 0; list-style: none; }
.svc__steps li,
.supplier__list li { position: relative; padding: 0 0 8px 34px; line-height: 28px; }
.svc__steps li:last-child,
.supplier__list li:last-child { padding-bottom: 0; }
/* The tick is drawn from two borders on a rotated box rather than loaded as an
   icon - six services times five steps is thirty of these, and none of them
   should cost a request. */
.svc__steps li::before,
.supplier__list li::before {
  content: "";
  position: absolute;
  left: 3px;
  top: 8px;
  width: 13px;
  height: 7px;
  border-left: 2.5px solid var(--accent);
  border-bottom: 2.5px solid var(--accent);
  transform: rotate(-45deg);
}

.svc__note {
  margin: 0 0 14px;
  padding-left: 18px;
  border-left: 3px solid var(--accent);
  color: var(--ink-soft);
}
.svc__out { margin: 0; font-weight: 600; }

.svc__media img {
  width: 100%;
  height: 100%;
  max-height: 460px;
  object-fit: cover;
  border-radius: 10px;
  border: 4px solid var(--surface);
  box-shadow: 0 14px 34px rgba(25, 27, 29, .14);
}
.svc--alt .svc__media img { border-color: var(--paper); }

/* ---- numbered steps (how the estimate works) ---- */
/* ---- Contact: how the estimate works ----
   Each step is a card with its number in a burgundy disc, drawn from a CSS
   counter. list-style:none matters and was missing: the global reset only
   covers ul, so this ol was painting its own native marker as well as the
   disc and every step read "1 1", "2 2", "3 3". */
.steps {
  counter-reset: st;
  list-style: none;
  margin: 26px 0 0;
  padding: 0;
}
.steps__item {
  counter-increment: st;
  position: relative;
  margin-bottom: 16px;
}
.steps__item:last-child { margin-bottom: 0; }
.steps__body {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 22px 26px 24px 84px;
}
.steps__item::before {
  content: counter(st);
  position: absolute;
  left: 26px;
  top: 24px;
  z-index: 1;
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  background: var(--accent);
  border-radius: 50%;
  color: #fff;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
}
.steps h3 { font-size: 20px; line-height: 28px; margin: 0 0 2px; }
.steps p { margin: 0; }

/* ---- the estimate confirmation (thank-you.html) ----
   Reuses .pagehero and .howto/.steps rather than growing a layout of its own.
   These are the same three steps the visitor read on Contact a minute before
   they submitted, in the same words, and arriving at something that looks like
   a different site is the opposite of reassuring. Only the tick and the card
   under the steps are new here. */
.pagehero__mark {
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  margin-bottom: 18px;
  background: #fff;
  border-radius: 50%;
}
.pagehero__mark svg {
  width: 28px;
  height: 28px;
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The one thing a person wants after submitting a form is a way to skip the
   wait, so it is a card and not a footnote. Burgundy edge, not hi-vis - the
   yellow belongs to the estimate card on Home and nowhere else. */
.sooner {
  margin-top: 34px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent);
  border-radius: 10px;
  padding: 26px 30px 30px;
}
.sooner h2 { font-size: 26px; line-height: 36px; margin: 0 0 6px; }
.sooner p { margin: 0; }
.sooner .btn { margin: 18px 0 0; }
.sooner__hours {
  margin-top: 16px;
  font-size: 15px;
  line-height: 26px;
  color: var(--ink-soft);
}

/* ---- Contact: the three ways to reach him ---- */
.reach { background: var(--paper); padding: 60px var(--gutter) 10px; }

/* ---- Contact: the embedded estimate form ----
   The iframe carries its height inline, as the provider's snippet does, but as
   a pixel value rather than their height:100%. 100% resolves against the
   parent, and a parent in normal flow has no height of its own, so the frame
   collapsed. A real number means the form is usable even if the provider's
   resizing script never loads; when it does load it overrides this inline
   anyway.

   Everything here is about making the frame stop looking like a frame. The
   section is plain white because the form's own background is white, so with
   no border, no radius and no shadow on the embed there is no visible seam
   where one document ends and the other starts. Nothing inside the iframe can
   be reached from this stylesheet - it is another origin - so the form's own
   type and controls have to be styled in the provider's form builder. The CSS
   for that is kept in docs/ghl-form-styles.css. */
.estimate { background: var(--surface); padding: 56px var(--gutter) 66px; }
.estimate__inner { max-width: 760px; margin: 0 auto; }
.estimate__inner > h2 { font-size: 34px; line-height: 46px; margin: 8px 0 10px; }
.estimate__lede { margin: 0 0 26px; font-size: 18px; line-height: 32px; }
.estimate__lede a { text-decoration: underline; }
/* No border, radius, overflow or shadow on purpose - any of them draws an edge
   around the form and announces that it came from somewhere else. */
.estimate__embed iframe { display: block; width: 100%; border: 0; }

/* ---- Contact: the estimate walkthrough ---- */
.howto { background: var(--paper); padding: 40px var(--gutter) 66px; }
.howto__inner { max-width: 860px; margin: 0 auto; }
.howto__inner > h2 { font-size: 34px; line-height: 46px; margin: 8px 0 0; }
.howto__hours { margin-top: 40px; }
.howto__hours h2 { font-size: 26px; line-height: 36px; margin: 0 0 6px; }
.howto__hours p { margin: 0; }

/* Cards and steps start their travel outside the section. clip rather than
   hidden where understood - hidden would force overflow-y to auto and quietly
   make these scroll containers. */
.reach, .howto { overflow-x: hidden; overflow-x: clip; }

/* ---- Service Area ----
   Built on Relume's contact27 shape: a left-aligned intro block capped well
   short of the full width, then a two-column grid below it. The towns used to
   be twenty-one bordered chips wrapping in a 780px column, which read as a tag
   cloud rather than a list of places. Two panels, each an actual list, is what
   the content always was. */
.area { background: var(--paper); padding: 66px var(--gutter); }
.area__inner { max-width: var(--max); margin: 0 auto; }
.area__intro { max-width: 620px; margin-bottom: 44px; }
.area__intro h2 { font-size: 38px; line-height: 50px; margin: 8px 0 12px; }
.area__intro p { margin: 0 0 20px; font-size: 18px; line-height: 32px; }
/* The paragraph tells them to call, so give them the thing to call with. */
.area__intro .btn { margin-left: 0; }

.area__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 30px;
  align-items: start;
}
.area__panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 28px 30px 30px;
}
.area__panel h2 {
  font-size: 22px;
  line-height: 30px;
  margin: 0 0 6px;
  padding-bottom: 14px;
  border-bottom: 2px solid var(--accent);
}

/* Two columns inside each panel so twelve places do not become a twelve-line
   ladder. Grid rather than CSS columns because the rules under each name have
   to line up across the pair, which column flow will not guarantee. */
.area__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 26px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.area__list li {
  position: relative;
  padding: 11px 0 11px 20px;
  border-bottom: 1px solid var(--line);
  font-size: 16px;
  line-height: 24px;
}
/* A small burgundy tick of a marker, drawn rather than loaded. */
.area__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 19px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}

/* ---- Materials: the argument, set as a statement ----
   This paragraph is the whole thesis of the page - cheap material makes you
   buy the labour twice - and it was previously the first of a stack of
   identical paragraphs. Sized up so it reads as the premise everything below
   it is evidence for. */
.thesis { background: var(--surface); padding: 60px var(--gutter); }
.thesis__inner { max-width: 700px; margin: 0 auto; }   /* ~75 characters at 23px */
.thesis__inner p {
  margin: 0;
  font-size: 23px;
  line-height: 38px;
  font-weight: 500;
}

/* ---- Materials: the warranties ----
   Built on Relume's stats-section shape. Every figure here was already in the
   supplier copy below, buried mid-paragraph - 50 years on SmartTrim, 20 and 10
   on the NEVEROT window, lifetime on Duration. Nothing is claimed here that
   the paragraphs do not already say; they are just no longer hidden. Burgundy
   because this is the page's evidence and it should read as a single block. */
.stats { background: var(--accent); padding: 60px var(--gutter); }
.stats__inner { max-width: var(--max); margin: 0 auto; text-align: center; }
.stats__inner h2 { font-size: 34px; line-height: 46px; margin: 8px 0 0; }
.stats__row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 30px;
  margin-top: 34px;
  text-align: left;
}
.stat { padding-left: 20px; border-left: 2px solid rgba(255, 255, 255, .3); }
.stat__num {
  margin: 0;
  font-family: var(--font-display);
  font-size: 66px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: .01em;
  /* The digits are swapped out every frame while the count runs, and each
     glyph is a different width - without a fixed advance the number jitters
     sideways as it climbs. */
  font-variant-numeric: tabular-nums;
}
.stat__num--word { font-size: 46px; line-height: 1.4; }
.stat__label {
  margin: 8px 0 6px;
  font-size: 17px;
  line-height: 26px;
  font-weight: 700;
}
.stat__sub { margin: 0; font-size: 15px; line-height: 25px; }

/* ---- Materials: who I buy from ----
   A media row rather than the old full-width stack: the mark holds its own
   column at a fixed width and the text runs beside it. These paragraphs range
   from one sentence to six, which is exactly why this is a row and not a card
   grid - cards in a row equalise to the tallest and leave the short ones
   mostly empty. */
.suppliers { background: var(--paper); padding: 66px var(--gutter); }
/* 800 rather than 1000, which is a line-length decision, not a taste one.
   Measured at the old width, these paragraphs ran 86 characters a line at
   717px and about 97 on a desktop; comfortable is 60 to 75, past which the eye
   loses its place coming back for the next line. Narrowing the whole block
   rather than capping the paragraph keeps the mark and the text in proportion
   instead of leaving a ragged gap down the right. */
.suppliers__inner { max-width: 800px; margin: 0 auto; }
.suppliers__inner > h2 { font-size: 34px; line-height: 46px; margin: 8px 0 10px; }

.supplier {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: 34px;
  align-items: start;
  border-top: 1px solid var(--line);
  padding: 30px 0;
}
.supplier:last-of-type { border-bottom: 1px solid var(--line); }
.supplier__mark {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 76px;
  padding: 14px 18px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.supplier h3 { margin: 0 0 4px; font-size: 22px; line-height: 30px; }
.supplier p { margin: 0 0 12px; }
.supplier p:last-child { margin-bottom: 0; }
/* The line that says what this supplier actually IS to Randy. Every one of the
   five already opened with it - "Where I get lumber", "Two lines, two
   different jobs" - buried at the head of a block of body copy. */
.supplier__lede { font-size: 18px; line-height: 30px; font-weight: 600; }
.supplier__out { font-weight: 600; }

/* ---- Materials: the disclosure ----
   Was a trailing paragraph carrying an inline style attribute. It is the only
   line on the page about Randy's own interest rather than the product, so it
   gets its own block. */
.disclosure { background: var(--surface); padding: 34px var(--gutter); border-top: 1px solid var(--line); }
.disclosure__inner { max-width: 700px; margin: 0 auto; }
.disclosure__inner p {
  margin: 0;
  padding-left: 18px;
  border-left: 3px solid var(--accent);
  font-weight: 600;
}

/* ---- All five suppliers carry a real mark now; the generic svg icons that
   stood in for them are gone, which is why nothing in the materials list
   hits the .feature__icon svg rule any more. Every one is a landscape
   lockup rather than an 80px square, so they are sized by width in both
   places rather than dropped into the icon box.
   alt is empty on purpose - the company name sits in the heading right
   beside it, and reading it twice helps nobody. */
/* Every supplier logo is fitted into the same 170x80 box, which is the width
   the icon column allows and the height the svg icons use. object-fit keeps
   each one's aspect ratio inside that box, so logos of very different shapes
   still produce rows of equal height: Tom Eubanks at 2.1:1 fills the width and
   hits 80px tall, 84 Lumber and Sherwin-Williams at about 3.5:1 fill the
   width at 49px and centre in the leftover space. Right-aligned to match
   where the svg icons used to sit.

   ProSource is the exception on source size: the only file they publish is
   195x52, so it is the one logo that gets upscaled rather than downscaled -
   to 240px in the supplier row. It is a flat two-colour wordmark, so the
   stretch is barely visible, but there is no sharper original to swap in. */
.feature__logo {
  display: block;
  width: 170px;
  max-width: 100%;
  height: 80px;
  object-fit: contain;
  object-position: right center;
  margin: 10px 0 10px auto;
}
.supplier__logo {
  display: block;
  width: 100%;
  max-width: 240px;
  height: auto;
  margin: 0 0 14px;
}

/* ---- contact detail cards ---- */
.contactgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 20px;
  max-width: 860px;
  margin: 0 auto;
}
.contactcard {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 28px;
}
.contactcard h3 { margin: 0 0 8px; }
/* The email address is one unbroken 242px string and the card's content box is
   217px at desktop, where the grid puts three of these across 860px - measured,
   it hung 26px out of the card. anywhere rather than break-word so it also
   holds the grid track down to the card width instead of letting min-content
   push the column wider. */
.contactcard p { margin: 0; overflow-wrap: anywhere; }
.contactcard a { text-decoration: underline; }

/* ---- honest empty-state note (used where real content is still needed) ---- */
.needs {
  max-width: var(--max);
  margin: 0 auto;
  border: 1px dashed var(--line);
  border-radius: 8px;
  padding: 22px 26px;
  color: var(--ink-soft);
  font-size: 15px;
  line-height: 26px;
}
.needs strong { color: var(--ink); }

@media (max-width: 1024px) {
  .trust__row { flex-wrap: wrap; }
  /* The strip is white with grey rules, not a dark band - a white border here
     was invisible and the stacked items ran together with no divider at all. */
  .trust__item {
    flex: 1 1 50%;
    border-bottom: 1px solid var(--line);
  }
  .trust__item:nth-child(2n) { border-right: 0; }
  /* Bottom row of the 2-up: the section's own border-bottom closes it. */
  .trust__item:nth-last-child(-n+2) { border-bottom: 0; }
  .pagehero h1 { font-size: 38px; line-height: 48px; }

  /* One column, and the aside stops sticking with it - a sticky heading in a
     single-column flow would pin itself over the entries it introduces. */
  .story__inner { grid-template-columns: 1fr; gap: 34px; }
  .story__aside { position: static; }
  .story__aside h2 { font-size: 32px; line-height: 42px; }
  .creed__list { grid-template-columns: 1fr; gap: 24px; }

  /* One column, and the flip has to be undone with it - a grid-column: 2 left
     standing in a single-column grid would push that block into a phantom
     second track. */
  .svc__inner { grid-template-columns: 1fr; gap: 30px; }
  .area__grid { grid-template-columns: 1fr; gap: 22px; }
  .area__intro h2 { font-size: 32px; line-height: 42px; }
  .stats__row { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px; }
  .supplier { grid-template-columns: 180px minmax(0, 1fr); gap: 24px; }
  .svc--flip .svc__text,
  .svc--flip .svc__media { grid-column: 1; grid-row: auto; }
  .svc__text h2 { font-size: 28px; line-height: 38px; }
}

@media (max-width: 720px) {
  /* One per row now, so every item but the last needs its rule back - including
     the two the 2-up rule above cleared as its bottom row. */
  .trust__item { flex: 1 1 100%; border-right: 0; border-bottom: 1px solid var(--line); }
  .trust__item:nth-last-child(-n+2) { border-bottom: 1px solid var(--line); }
  .trust__item:last-child { border-bottom: 0; }
  .pagehero h1 { font-size: 30px; line-height: 40px; }
  .prose__inner h2 { font-size: 26px; line-height: 36px; }
  .story__aside h2 { font-size: 26px; line-height: 36px; }
  .svc { padding: 44px var(--gutter); }
  .svc__text h2 { font-size: 26px; line-height: 36px; }
  .svc__lede { font-size: 17px; line-height: 30px; }
  .svcnav { padding: 14px var(--gutter); }
  .svcnav__row { justify-content: flex-start; gap: 8px; }
  .svcnav__row a { padding: 7px 13px; font-size: 14px; }
  .stats__row { grid-template-columns: 1fr; gap: 24px; }
  .reach { padding: 40px var(--gutter) 8px; }
  .estimate { padding: 40px var(--gutter); }
  .estimate__inner > h2 { font-size: 26px; line-height: 36px; }
  .estimate__lede { font-size: 17px; line-height: 30px; }
  .howto { padding: 30px var(--gutter) 44px; }
  .howto__inner > h2 { font-size: 26px; line-height: 36px; }
  .steps__body { padding: 20px 20px 22px 74px; }
  .steps__item::before { left: 20px; top: 22px; width: 38px; height: 38px; font-size: 20px; }
  .area { padding: 44px var(--gutter); }
  .area__intro h2 { font-size: 26px; line-height: 36px; }
  .area__intro p { font-size: 17px; line-height: 30px; }
  .area__panel { padding: 22px 20px 24px; }
  /* One column on a phone: two 16px names side by side in half of a 295px
     column wraps the longer ones onto a second line. */
  .area__list { grid-template-columns: 1fr; }
  .stat__num { font-size: 54px; }
  .stat__num--word { font-size: 38px; }
  .thesis__inner p { font-size: 19px; line-height: 32px; }
  /* The mark stops holding its own column and sits above the text, left
     aligned with it, rather than shrinking to a stripe. */
  .supplier { grid-template-columns: 1fr; gap: 16px; }
  .supplier__mark { justify-content: flex-start; max-width: 230px; }
  .suppliers__inner > h2 { font-size: 26px; line-height: 36px; }
  .creed__inner h2 { font-size: 26px; line-height: 36px; }
  .pullquote { padding: 40px var(--gutter); }
  .pullquote blockquote { font-size: 22px; line-height: 34px; }
  .pullquote blockquote::before { font-size: 64px; margin-bottom: -10px; }
}

/* Interior pages have no tall hero for the header to overlay, so it sits in flow. */
.site-header--inner {
  position: static;
  background: var(--surface);
}

/* The CTA figure overhangs its section by 100px. On Home it hangs into the
   gallery's slack space; where it directly follows the trust strip it would
   collide with that text, so give it room. */
.trust + .cta { padding-top: 110px; }

/* About drops the CTA photo, so there is no second column for the text to sit
   beside and nothing overhanging the section to make room for. The text takes
   the full row and centres, which also puts the button back on the block's own
   axis - the 132px offset that centres it under the heading in the two-column
   layout would throw it off-centre here. */
.cta--nofigure .cta__text { width: 100%; padding-left: 0; text-align: center; }
.cta--nofigure .cta__text .btn { margin-left: auto; margin-right: auto; }
.trust + .cta--nofigure { padding-top: 0; }


/* ==========================================================================
   17 LIGHT THEME — the page is light grey; these are the deliberate dark
   and burgundy blocks, where type has to flip back to white.
   ========================================================================== */

/* burgundy / dark islands */
.hero, .pagehero, .cta, .footer, .pullquote, .stats { color: #fff; }
.stats .eyebrow          { color: var(--accent-wash); }
.stat__sub               { color: rgba(255,255,255,.82); }

.hero .eyebrow,
.pagehero .eyebrow       { color: var(--accent-wash); }
.hero__col > p,
.pagehero p              { color: rgba(255,255,255,.88); }
.cta__text p             { color: rgba(255,255,255,.90); }

/* footer sits on the dark anchor block */
.footer h6               { color: #fff; }
.footer__brand > p       { color: rgba(255,255,255,.70); }
/* The wordmark's default #2B2B2B is all but invisible on this block - about
   1.05:1 against it. The em already had its inverted colour; strong did not. */
.footer .logo__text strong { color: #fff; }
.footer .logo__text em   { color: var(--accent-wash); }
.footer__col--links a    { color: rgba(255,255,255,.66); }
.footer__col--links a:hover { color: #fff; }
.footer__phone p,
.footer__mail p          { color: rgba(255,255,255,.80); }
.hours__row              { color: rgba(255,255,255,.66); }
.hours__row span:first-child { color: rgba(255,255,255,.90); }
.footer__bar             { color: #fff; }
.footer .needs           { border-color: rgba(255,255,255,.28); color: rgba(255,255,255,.72); }

/* light-page chrome */
.nav a                   { color: var(--ink); }
.nav a:hover,
.nav a.is-active         { color: var(--accent); }
.logo__text em           { color: var(--accent); }
.feature__icon svg       { fill: var(--accent); }
.prose__inner h3         { color: var(--accent); }
.trust__item             { color: var(--ink); }
.difference__item h3     { color: var(--ink); }
.contactcard h3          { color: var(--accent); }
.contactcard             { color: var(--ink); }
.lede                    { color: var(--ink-soft); }

/* the before/after slider sits on light ground now — give it a lift */
.ba { box-shadow: 0 14px 34px rgba(25,27,29,.18); }
.card:hover { box-shadow: 0 18px 38px rgba(25,27,29,.14); }

/* The phone/email SVGs are 150x150 intrinsic; without an explicit size they
   render far larger than the 24px text indent and cover the first letter. */
.topbar__mail p, .topbar__phone p,
.footer__phone p, .footer__mail p {
  background-size: 15px 15px;
}

/* ---- logo lockup: memorial badge sits between the two lines of the wordmark ---- */
.logo__text { align-items: center; gap: 4px; }
.logo__badge { display: block; width: 52px; height: 52px; flex: none; }
.logo__badge img { width: 100%; height: 100%; display: block; }

@media (max-width: 720px) {
  .logo__badge { width: 42px; height: 42px; }
}

/* ==========================================================================
   18 HERO REDESIGN — matched to the Canva mockup
   (topbar 59px / navbar 126px / hero 668px measured at 1920x919)
   ========================================================================== */

/* The header no longer floats over the hero; it sits above it in flow. */
.site-header { position: static; }

/* ---- top bar: wider spacing, outline icons, no social row ---- */
.topbar__row {
  justify-content: space-between;
  max-width: none;
}
.topbar__left  { width: auto; padding: 0; }
.topbar__left p { font-size: 17px; line-height: 26px; }
.topbar__right {
  width: auto;
  max-width: none;
  margin: 0;
  padding: 0 3% 0 0;
  gap: 64px;
}
.topbar__mail, .topbar__phone { width: auto; padding: 0; }
.topbar__mail p, .topbar__phone p {
  display: flex; align-items: center; gap: 12px;
  margin: 0; padding: 0;
  background-image: none;
  font-size: 16px; line-height: 26px;
  text-align: left;
}
.topbar__ico { flex: none; width: 26px; height: 26px; display: block; }
.topbar__ico svg { width: 100%; height: 100%; fill: none; stroke: #fff; stroke-width: 1.6; }

/* ---- logo lockup: wordmark only, badge moves into the hero ---- */
.navbar { padding: 18px var(--gutter); }
.navbar__logo { width: auto; padding-right: 40px; }
.logo__text { align-items: center; gap: 2px; }
.logo__text strong {
  font-size: 30px; font-weight: 500; letter-spacing: 7px;
  color: #2B2B2B; line-height: 1.1;
}
.logo__text em {
  font-size: 24px; font-weight: 700; letter-spacing: 1.5px;
  color: var(--accent); line-height: 1.1;
}

/* ---- nav: burgundy, larger ---- */
.nav a {
  font-size: 17px; font-weight: 600; letter-spacing: 1px;
  color: var(--accent);
  text-transform: none;
  padding: 10px 16px;
}
.nav a:hover, .nav a.is-active { color: #45101C; }

/* ---- hero ---- */
.hero { position: relative; background: #340E16; overflow: hidden; }
.hero__overlay {
  position: relative;
  min-height: 640px;
  display: flex;
  align-items: center;
  padding: 48px var(--gutter);
  background:
    var(--hero1-wash),
    url("../assets/hero1.webp") center/cover no-repeat;
}
.hero__row { min-height: 0; width: 100%; max-width: var(--max); margin: 0 auto; position: relative; z-index: 2; }
.hero__col { width: 100%; padding-right: 0; }

.hero__badge {
  position: absolute;
  left: 36px;
  top: 24px;
  width: 202px;
  height: 202px;
  z-index: 3;
  pointer-events: none;
}
.hero__badge img { width: 100%; height: 100%; display: block; }

.hero h1 {
  font-size: 56px;
  line-height: 68px;
  font-weight: 700;
  max-width: 17ch;
  margin: 0 0 18px;
}
.hero .eyebrow {
  color: #fff;
  font-weight: 700;
  font-size: 21px;
  letter-spacing: 5px;
  margin: 0 0 16px;
}
.hero__col > p {
  font-size: 17px;
  line-height: 28px;
  max-width: 62ch;
  margin: 0 0 30px;
  color: rgba(255,255,255,.92);
}
.hero__actions { gap: 20px; margin: 0; }

/* two tilted red bars on the right, as measured off the mockup */
.hero__chevron {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 430px;
  overflow: hidden;
  pointer-events: none;
  z-index: 1;
}
.hero__chevron::before,
.hero__chevron::after {
  content: "";
  position: absolute;
  top: -14%; bottom: -14%;
  width: 64px;
  background: #A61C1B;
  opacity: .92;
  transform: rotate(21.5deg);
}
.hero__chevron::before { right: 40px; }
.hero__chevron::after  { right: 166px; }

@media (max-width: 1200px) {
  .hero__badge { width: 150px; height: 150px; left: 24px; }
  .hero h1 { font-size: 44px; line-height: 54px; }
  .topbar__right { gap: 32px; padding-right: 0; }
}

@media (max-width: 1024px) {
  .navbar { padding: 16px var(--gutter); }
  .navbar__logo { padding-right: 0; }
  .logo__text strong { font-size: 24px; letter-spacing: 5px; }
  .logo__text em { font-size: 19px; }
  .topbar__row { flex-direction: column; gap: 6px; }
  .topbar__right { justify-content: center; padding: 0; }
  .hero__chevron { width: 240px; opacity: .8; }
  .hero__badge { width: 120px; height: 120px; top: 18px; }
  .hero__overlay { min-height: 0; padding: 150px var(--gutter) 60px; }
}

@media (max-width: 720px) {
  .hero h1 { font-size: 32px; line-height: 42px; }
  .hero__col > p { font-size: 16px; line-height: 26px; }
  .hero__badge { width: 92px; height: 92px; left: 16px; top: 14px; }
  .hero__overlay { padding-top: 124px; }
  .hero__chevron { display: none; }
  .topbar__mail p, .topbar__phone p { font-size: 14px; }
}

/* Keep the hero copy clear of the badge at every width: use the natural
   container margin when there is one, otherwise reserve the badge's width. */
.hero__overlay { padding-left: max(calc((100% - 1200px) / 2), 268px); padding-right: var(--gutter); }
.hero__row { margin: 0; }
.logo__text strong { white-space: nowrap; }

@media (max-width: 1024px) {
  .hero__overlay { padding-left: var(--gutter); }
}

/* Band heights tuned to the mockup: topbar 59, navbar 126, hero 668 */
.topbar { padding: 16px var(--gutter); }
.navbar { padding: 22px var(--gutter); }
.hero__overlay { min-height: 668px; }

@media (max-width: 1024px) {
  .topbar { padding: 10px var(--gutter); }
  .hero__overlay { min-height: 0; }
}

/* Top bar is a burgundy band — its type is white. (Missed when the site
   was inverted to the light theme; it was inheriting dark body text.) */
.topbar { color: #fff; }
.topbar a { color: #fff; }

/* Halftone dot field in the hero's lower-left, per the mockup: a diagonal
   band densest at the corner, fading up and to the right. */
.hero__dots {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 380px;
  height: 400px;
  pointer-events: none;
  z-index: 1;
  background-image: radial-gradient(circle, rgba(255,255,255,.20) 2.1px, transparent 2.5px);
  background-size: 22px 22px;
  -webkit-mask-image: linear-gradient(40deg, #000 0%, rgba(0,0,0,.6) 42%, transparent 76%);
          mask-image: linear-gradient(40deg, #000 0%, rgba(0,0,0,.6) 42%, transparent 76%);
}

@media (max-width: 1024px) {
  .hero__dots { width: 240px; height: 250px; }
}
@media (max-width: 720px) {
  .hero__dots { display: none; }
}

/* The mockup's larger nav type only fits above ~1500px. Tighten the lockup
   and links between the mobile breakpoint and there so it stays one line. */
@media (max-width: 1500px) {
  .navbar__logo { padding-right: 24px; }
  .logo__text strong { font-size: 26px; letter-spacing: 5px; }
  .logo__text em     { font-size: 20px; letter-spacing: 1.2px; }
  .nav a { font-size: 15px; letter-spacing: .6px; padding: 10px 10px; }
  .navbar__cta .btn { width: 214px; }
}

/* ==========================================================================
   19 HEADER CTA — flip button (no icon; flips on hover, still a plain link)
   ========================================================================== */
.flipbtn {
  display: block;
  width: 250px;
  max-width: 100%;
  height: 60px;
  margin: 10px 0;
  perspective: 900px;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.flipbtn__inner {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform .55s cubic-bezier(.2, .7, .3, 1);
}
.flipbtn:hover .flipbtn__inner,
.flipbtn:focus-visible .flipbtn__inner { transform: rotateX(180deg); }

.flipbtn__face {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  font-family: var(--font);
  font-size: 16px;
  font-weight: 500;
  line-height: 22px;
  text-align: center;
  color: #fff;
  -webkit-backface-visibility: hidden;
          backface-visibility: hidden;
}
.flipbtn__front { background: var(--accent); }
.flipbtn__back  { background: #45101C; transform: rotateX(180deg); }

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

@media (max-width: 1500px) { .flipbtn { width: 214px; } }
@media (max-width: 1024px) { .flipbtn { width: 100%; margin: 16px 0 0; } }

/* Keep the flip out of the way for anyone who has asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .flipbtn__inner { transition: none; }
  .flipbtn:hover .flipbtn__inner,
  .flipbtn:focus-visible .flipbtn__inner { transform: none; }
  .flipbtn:hover .flipbtn__front { background: var(--accent-dark); }
}

/* ---- the same flip on the hero CTA ----
   Identical mechanism to the header button, but the header's own sizing does
   not travel with it: that one narrows to 214px at 1500 and goes full width
   at 1024 because it drops into the mobile menu there. Out in the hero the
   button holds 250px until the actions stack at 720. The calendar icon rides
   on the front face, from the mask rule in section 02. */
.flipbtn--hero { width: 250px; margin: 10px 0; }
.flipbtn__front--calendar { padding-left: 48px; padding-right: 8px; }

@media (max-width: 1500px) { .flipbtn--hero { width: 250px; } }
@media (max-width: 1024px) { .flipbtn--hero { width: 250px; margin: 10px 0; } }
@media (max-width: 720px)  { .flipbtn--hero { width: 100%;  margin: 10px 0; } }

/* ---- the same flip on the materials button ----
   Same mechanism a third time. Two things have to be restated rather than
   inherited: the header button's width steps (214px at 1500, full width at
   1024, where it drops into the mobile menu) would otherwise drag this one
   along, and the indent that lines it up with the feature text column has to
   survive them.

   That indent is the icon column's own two values - its 190px basis plus its
   1.5% padding - plus a 16px optical nudge, so the button and the column stay
   in step if either is ever retuned. The margin and the width cap both read
   --btn-indent, because without the cap the 294px button hangs past the right
   edge of its column between 1025 and 1115px, where the two-column layout has
   just resumed and the text side is at its narrowest - 41px over at 1025. In
   that band it gives up a little width instead of poking out.

   At 720 the indent goes entirely and the button spans the column, the way
   every other button does at that width. */
.flipbtn--materials {
  --btn-indent: calc(190px + 1.5% + 16px);
  width: 294px;
  margin: 20px 0 10px var(--btn-indent);
  max-width: calc(100% - var(--btn-indent));
}
@media (max-width: 1500px) { .flipbtn--materials { width: 294px; } }
@media (max-width: 1024px) { .flipbtn--materials { width: 294px;
                             margin: 20px 0 10px var(--btn-indent); } }
@media (max-width: 720px)  { .flipbtn--materials { width: 100%;
                             margin: 20px 0 10px; max-width: 100%; } }

/* ==========================================================================
   20 HERO — exact match to the mockup
   ========================================================================== */

/* Halftone pattern lifted from the mockup itself (background-subtracted),
   so the dot grid, sizing and wedge shapes are the designed ones. */
.hero__dots {
  width: 460px;
  height: 668px;
  background: url("../assets/img/hero-dots.png") left bottom / 460px 668px no-repeat;
  -webkit-mask-image: none;
          mask-image: none;
}

/* Headline breaks after "Repairs &"; subline stays on one line.
   Measured off the mockup: line 1 = 827px, line 2 = 561px, subline = 851px. */
.hero h1 { max-width: none; }
.hero__col > p { max-width: 900px; }

@media (max-width: 1024px) {
  .hero__dots { width: 300px; height: 436px; background-size: 300px 436px; }
  .hero h1 br { display: none; }
}
@media (max-width: 720px) {
  .hero__dots { display: none; }
}

/* Type scaled to the mockup's measured line widths (h1 827/561, sub 851) */
.hero h1 { font-size: 61px; line-height: 68px; }
.hero__col > p { font-size: 17.5px; }

@media (max-width: 1200px) { .hero h1 { font-size: 46px; line-height: 56px; } }
@media (max-width: 720px)  { .hero h1 { font-size: 32px; line-height: 42px; }
                             .hero__col > p { font-size: 16px; } }

/* ---- desktop nav dock magnification (transform driven from js/main.js) ---- */
@media (min-width: 1025px) {
  .nav ul { align-items: center; }
  .nav a {
    transform-origin: center bottom;
    transition: transform .16s cubic-bezier(.2, .8, .3, 1), color .2s ease;
    will-change: transform;
  }
}
@media (prefers-reduced-motion: reduce) {
  .nav a { transition: color .2s ease; transform: none !important; }
}

/* ---- mobile menu: burgundy press feedback ----
   Full-bleed burgundy bar while a row is held down, so a tap reads as a
   deliberate press before the page changes. Desktop is unaffected: the
   .is-pressed class only has styling inside this breakpoint. */
@media (max-width: 1024px) {
  .nav ul { width: 100%; }
  .nav li { width: 100%; }
  .nav a {
    width: 100%;
    justify-content: flex-start;
    padding: 14px 16px;
    margin: 0 -16px;
    width: calc(100% + 32px);
    border-radius: 4px;
    transition: background-color .12s ease, color .12s ease;
    -webkit-tap-highlight-color: transparent;
  }
  .nav a.is-pressed,
  .nav a:active {
    background: var(--accent);
    color: #fff;
  }
  /* the row you are on keeps a quiet burgundy edge */
  .nav a.is-active {
    box-shadow: inset 3px 0 0 var(--accent);
    color: var(--accent);
  }
  .nav a.is-active.is-pressed { color: #fff; }

  /* the estimate button gets the same press state */
  .flipbtn:active .flipbtn__front { background: #45101C; }
}

/* ==========================================================================
   21 STICKY GLASS HEADER — both breakpoints
   ========================================================================== */
.site-header,
.site-header--inner {
  position: sticky;
  top: 0;
  z-index: 200;
  background: transparent;
}

/* The contact strip stays flat burgundy at every scroll position - no
   translucency, no blur. It is a solid band, not glass, so it never picks up
   whatever happens to be sliding underneath it. */
.topbar { background: var(--accent); }

/* The navbar below it does keep the glass. Fallback first: a near-solid
   band, so the type stays readable on any browser that cannot blur what is
   behind it. */
.navbar {
  background: rgba(255, 255, 255, .94);
  border-bottom: 1px solid rgba(25, 27, 29, .10);
}

/* Real glass where the browser supports it - navbar only. */
@supports ((-webkit-backdrop-filter: blur(2px)) or (backdrop-filter: blur(2px))) {
  .navbar {
    background: rgba(255, 255, 255, .68);
    -webkit-backdrop-filter: blur(16px) saturate(150%);
            backdrop-filter: blur(16px) saturate(150%);
  }
}

/* A held header needs to sit visibly above the page once content slides under it. */
.navbar { box-shadow: 0 1px 18px rgba(25, 27, 29, .07); }

@media (max-width: 1024px) {
  /* an open menu must never grow taller than the screen */
  .navbar__right.is-open {
    max-height: calc(100vh - 150px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* An open mobile menu needs a firmer backing than the bar itself — at 68%
   the page scrolling underneath shows between the rows and reads as noise. */
@media (max-width: 1024px) {
  .navbar__right.is-open {
    margin: 0 calc(var(--gutter) * -1);
    width: calc(100% + (var(--gutter) * 2));
    padding: 10px var(--gutter) 18px;
    background: rgba(255, 255, 255, .90);
  }
}
@supports ((-webkit-backdrop-filter: blur(2px)) or (backdrop-filter: blur(2px))) {
  @media (max-width: 1024px) {
    .navbar__right.is-open {
      background: rgba(255, 255, 255, .82);
      -webkit-backdrop-filter: blur(20px) saturate(160%);
              backdrop-filter: blur(20px) saturate(160%);
    }
  }
}

/* ==========================================================================
   22 HEADER — burgundy contact strip, glass navbar once scrolled
   ========================================================================== */
.site-header .topbar,
.site-header .navbar {
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
  transition: background-color .28s ease, box-shadow .28s ease;
}

/* The contact strip is deliberately excluded from the glass treatment: flat
   burgundy at the top of the page and flat burgundy once scrolled, with no
   is-stuck variant of its own. Leave it out of the rules below. */
.site-header .topbar { background: var(--accent); }
.site-header .navbar { background: var(--surface); box-shadow: none; }

/* scrolled — no-blur fallback first */
.site-header.is-stuck .navbar {
  background: rgba(255, 255, 255, .94);
  box-shadow: 0 1px 18px rgba(25, 27, 29, .07);
}

/* scrolled — real glass where supported */
@supports ((-webkit-backdrop-filter: blur(2px)) or (backdrop-filter: blur(2px))) {
  .site-header.is-stuck .navbar {
    background: rgba(255, 255, 255, .68);
    -webkit-backdrop-filter: blur(16px) saturate(150%);
            backdrop-filter: blur(16px) saturate(150%);
  }
}

/* the open mobile menu follows the same rule */
@media (max-width: 1024px) {
  .navbar__right.is-open {
    background: var(--surface);
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
  }
  .site-header.is-stuck .navbar__right.is-open { background: rgba(255, 255, 255, .90); }
}
@supports ((-webkit-backdrop-filter: blur(2px)) or (backdrop-filter: blur(2px))) {
  @media (max-width: 1024px) {
    .site-header.is-stuck .navbar__right.is-open {
      background: rgba(255, 255, 255, .82);
      -webkit-backdrop-filter: blur(20px) saturate(160%);
              backdrop-filter: blur(20px) saturate(160%);
    }
  }
}

/* ---- logo: hammer + wrench beside HOME REPAIRS (from the hero2 mockup) ---- */
.logo__row { display: flex; align-items: center; gap: 8px; }
.logo__tools { display: block; flex: none; width: 85px; height: auto; }

@media (max-width: 1500px) { .logo__tools { width: 70px; } }
@media (max-width: 1024px) { .logo__tools { width: 60px; } }
@media (max-width: 720px)  { .logo__tools { width: 48px; gap: 6px; } }

/* HOME REPAIRS must not break across lines once the tools icon shares the row */
.logo__text em { white-space: nowrap; }

/* ==========================================================================
   23 LOGO LOCKUP — proportions taken off hero2.png
   RANDY HOUSE 318w / HOME REPAIRS 169w / icon 84w, second row flush left,
   first row indented 18px. (1.88:1, not the near-even ratio it had before.)
   ========================================================================== */
.logo__text { align-items: flex-start; gap: 0; }
.logo__text strong {
  font-size: 36px;
  letter-spacing: 8px;
  font-weight: 500;
  padding-left: 18px;
  line-height: 1.06;
}
.logo__text em {
  font-size: 24px;
  letter-spacing: .8px;
  font-weight: 700;
  line-height: 1.06;
}
.logo__row { gap: 4px; }
.logo__tools { width: 84px; }

@media (max-width: 1500px) {
  .logo__text strong { font-size: 30px; letter-spacing: 6.5px; padding-left: 15px; }
  .logo__text em     { font-size: 20px; letter-spacing: .7px; }
  .logo__tools       { width: 70px; }
}
@media (max-width: 1024px) {
  .logo__text strong { font-size: 27px; letter-spacing: 5.5px; padding-left: 13px; }
  .logo__text em     { font-size: 18px; }
  .logo__tools       { width: 62px; }
}
@media (max-width: 720px) {
  .logo__text strong { font-size: 21px; letter-spacing: 4px; padding-left: 10px; }
  .logo__text em     { font-size: 14px; }
  .logo__tools       { width: 48px; }
}

/* icon and wordmark sit flush in the mockup (icon ends 149, text starts 150) */
.logo__row { gap: 0; }
/* navbar back to the mockup's 126px now the lockup is taller */
.navbar { padding: 16px var(--gutter); }
@media (max-width: 1024px) { .navbar { padding: 14px var(--gutter); } }

/* Full tool art is 98x58 and, in the mockup, the hammer handle passes ABOVE
   "HOME REPAIRS" — the two overlap by 14px horizontally (tool ends x161,
   text starts x150). The negative margin reproduces that overlap. */
.logo__tools { width: 98px; margin-right: -14px; }
@media (max-width: 1500px) { .logo__tools { width: 82px; margin-right: -12px; } }
@media (max-width: 1024px) { .logo__tools { width: 74px; margin-right: -10px; } }
@media (max-width: 720px)  { .logo__tools { width: 57px; margin-right: -8px;  } }

/* icon re-extracted at 97px after defringing */
.logo__tools { width: 97px; }
@media (max-width: 1500px) { .logo__tools { width: 81px; } }
@media (max-width: 1024px) { .logo__tools { width: 73px; } }
@media (max-width: 720px)  { .logo__tools { width: 56px; } }

/* ==========================================================================
   24 TOP BAR ON SMALL SCREENS
   The email is 250px of a 375px screen, which squeezed the phone into a
   65px column and broke it across three lines. Phone is the action worth
   keeping up there; the address still lives in the footer, on the contact
   page and in the contact cards.
   ========================================================================== */
.topbar__phone p { white-space: nowrap; }

@media (max-width: 720px) {
  .topbar__mail { display: none; }
  .topbar__right { gap: 0; justify-content: center; width: 100%; }
  .topbar__phone { width: auto; }
  .topbar__phone p { font-size: 15px; }
  .topbar { padding: 8px var(--gutter); }
  .topbar__left p { font-size: 14px; line-height: 22px; }
}

/* phone sits to the right on small screens */
@media (max-width: 720px) {
  .topbar__right { justify-content: flex-end; }
  .topbar__phone p { margin-right: 0; }
}

/* ==========================================================================
   25 HERO BUTTONS — hover on desktop
   Scoped to the hero so the card / CTA buttons elsewhere keep their own
   quieter treatment. Pointer-gated so touch devices don't get a stuck state.
   ========================================================================== */
@media (min-width: 1025px) and (hover: hover) and (pointer: fine) {
  .hero__actions .btn {
    transition: transform .22s cubic-bezier(.2, .8, .3, 1),
                box-shadow .22s ease,
                background-color .22s ease,
                border-color .22s ease,
                color .22s ease;
    will-change: transform;
  }
  .hero__actions .btn:hover,
  .hero__actions .btn:focus-visible { transform: translateY(-3px); }

  /* filled: deepen and lift */
  .hero__actions .btn--red:hover,
  .hero__actions .btn--red:focus-visible {
    filter: none;
    background: var(--accent-dark);
    border-color: var(--accent-dark);
    box-shadow: 0 14px 28px rgba(0, 0, 0, .38);
  }

  /* outline: fill white, type and icon go burgundy */
  .hero__actions .btn--outline:hover,
  .hero__actions .btn--outline:focus-visible {
    background: #fff;
    border-color: #fff;
    color: var(--accent);
    box-shadow: 0 14px 28px rgba(0, 0, 0, .32);
  }
  .hero__actions .btn--outline.btn--phone:hover::before,
  .hero__actions .btn--outline.btn--phone:focus-visible::before {
    background: var(--accent);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero__actions .btn { transition: background-color .2s ease, color .2s ease; }
  .hero__actions .btn:hover,
  .hero__actions .btn:focus-visible { transform: none; box-shadow: none; }
}

/* RANDY HOUSE a touch heavier (500 -> 600) */
.logo__text strong { font-weight: 600; }

/* ---- hero crossfade ----
   The overlay keeps hero1 in its own background; ::before carries hero2 under a
   lighter version of the same gradient and fades in on top, so the two photos
   dissolve into each other. 10s on hero1, 10s on hero2, 1.6s either way.

   hero2 is 1.78:1 against a hero band nearer 2.4:1, so `cover` had to crop it
   hard. Above 1024px it is sized `contain` instead - the whole frame, roofline
   through workbench - over a blurred copy of itself that fills the leftover
   strip. The join is feathered in the image rather than in CSS: hero2-feathered
   carries an alpha ramp (120px left and right, 70px top and bottom) baked into
   its own edges, so the softening tracks the photo wherever `contain` puts it.
   A CSS mask can only feather the element's edges, which is a different place at
   every viewport width - and on a shorter band the leftover strip moves to the
   top and bottom, so the ramp covers all four sides.
   At 1024px and under the band is too tall for a contained photo to look like
   anything but a stamp, so it stays on plain `cover`, pinned to the top edge to
   spare the roof peak, with no fill layer to feather into.

   Nothing here loads until js/main.js has preloaded the files and set
   .hero--swap, which it does after window load - hero2 must not compete with
   hero1, the LCP image, for a phone's bandwidth. The animation is gated on the
   same class so the first dissolve always starts from a decoded frame however
   late the page finishes. No JS, no second photo: the hero simply stays hero1. */
.hero {
  --hero1-wash: linear-gradient(95deg,
    rgba(52,14,22,.975) 0%,
    rgba(52,14,22,.94)  30%,
    rgba(52,14,22,.60)  60%,
    rgba(52,14,22,.22) 100%);
  --hero2-wash: linear-gradient(95deg,
    rgba(52,14,22,.90) 0%,
    rgba(52,14,22,.84) 30%,
    rgba(52,14,22,.46) 60%,
    rgba(52,14,22,.12) 100%);
}

/* A phone was being sent 1380px and 1672px photographs for a ~390px screen.
   1000px wide covers it at the usual pixel ratios and halves the bytes. */
@media (max-width: 720px) {
  .hero__overlay {
    background:
      var(--hero1-wash),
      url("../assets/hero1-1000w.webp") center/cover no-repeat;
  }
}

.hero__overlay::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
}
/* will-change is the difference between a dissolve and a jump on a phone.
   Without it the fade is a main-thread repaint of a full-bleed gradient over a
   scaled photograph, every frame; the layer is also rastered only once opacity
   leaves 0, so the first frames drop and the second photo appears to snap in
   part-way through. Promoting it gives the compositor the layer up front - it
   is rastered during the 10s hold and the animation then only moves an alpha.
   The class is added after both files decode, so the layer is never held for a
   hero that is not going to swap. */
.hero--swap .hero__overlay::before {
  animation: heroSwap 23.2s ease-in-out infinite;
  will-change: opacity;
}
/* On a phone the band is about 0.60:1 against a 1.78:1 photograph, so `cover`
   was showing a third of its width - the man on the ladder cut in half, no
   sunset, and a 1000px file upscaled past 1:1 to do it. hero2-mobile carries a
   42% centre crop on a canvas already cut to the band's shape, with its bottom
   edge feathered into the hero ground in the file rather than in CSS, so the
   join travels with the image instead of sitting at a fixed percentage of a
   band whose height moves. Sharper and lighter than the file it replaced. */
@media (max-width: 720px) {
  .hero--swap .hero__overlay::before {
    background:
      var(--hero2-wash),
      url("../assets/hero2-mobile.webp") center top/cover no-repeat;
  }
}
@media (min-width: 721px) and (max-width: 1024px) {
  .hero--swap .hero__overlay::before {
    background:
      var(--hero2-wash),
      url("../assets/hero2.webp") center top/cover no-repeat;
  }
}
@media (min-width: 1025px) {
  .hero--swap .hero__overlay::before {
    background:
      var(--hero2-wash),
      url("../assets/hero2-feathered.webp") center/contain no-repeat,
      url("../assets/hero2-blur.webp") center/cover no-repeat;
  }
}

@keyframes heroSwap {
  0%,     43.10% { opacity: 0; }   /* hero1 holds for 10s */
  50.00%, 93.10% { opacity: 1; }   /* hero2 holds for 10s */
  100%           { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .hero--swap .hero__overlay::before { animation: none; }
}
