:root {
  /* Brand maroon, sampled from the McRay Roofing & Exteriors logo */
  --maroon: #870412;
  --maroon-dark: #5C0210;
  --charcoal: #1E1517;
  /* Neutral near-black for the footer, and ONLY the footer. --charcoal above
     is warm on purpose -- it is 30/21/23, a maroon-tinted black, which is
     right for body type and for the video hero's fallback ground because it
     ties them to the brand red. Across a whole footer that same tint reads as
     a muddy brown rather than as black, which is the "cheap" Caleb called out.
     This is R=G=B, so it has no hue at all. #000 is one value away if it ever
     wants to be flatter still. */
  --footer-black: #121212;
  --slate: #4A3C3E;
  --gray: #99A1AF;
  --gray-light: #f6f2f1;
  --off-white: #FAF9F7;
  --white: #ffffff;

  /* Keep the copper roof-tile gradient — it already matches the logo's metal roof mark */
  --roof-1: #8B4416;
  --roof-2: #A75A2C;
  --roof-3: #DA984F;
  --roof-4: #FBA86B;
  --roof-gradient: linear-gradient(135deg, var(--roof-1), var(--roof-2) 40%, var(--roof-3) 70%, var(--roof-4));
  --maroon-gradient: linear-gradient(135deg, var(--maroon) 0%, var(--maroon-dark) 100%);

  /* Glossy variant of the brand red, for the primary button. Runs light at
     the top-left to dark at the bottom-right so the fill has a direction to
     it — a flat maroon reads as a coloured rectangle, this reads as a lit
     object. The lighter stop is the logo maroon opened up, not a new hue. */
  --maroon-gloss: linear-gradient(135deg, #A50F1F 0%, #8C0513 42%, #6B0210 100%);

  --accent-red: #D6453D;
  --accent-red-dark: #85020F;

  /* Legacy alias kept so nothing that still references --navy breaks */
  --navy: var(--charcoal);

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* ---- Visual-polish tokens (added for the premium/luxury pass) ----
     Shadows are tinted from --charcoal rather than flat black, and the
     "brand" shadow is tinted from --maroon, so depth reads warm and
     consistent with the burgundy/copper palette instead of generic gray. */
  --shadow-sm: 0 2px 10px rgba(30, 21, 23, 0.05);
  --shadow-md: 0 10px 28px rgba(30, 21, 23, 0.10);
  --shadow-lg: 0 22px 50px rgba(30, 21, 23, 0.16);
  --shadow-brand: 0 14px 32px rgba(135, 4, 18, 0.18);
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --transition-fast: 180ms var(--ease);
  --transition-med: 260ms var(--ease);
}

* { box-sizing: border-box; }
html {
  scroll-behavior: smooth;
  /* scroll-snap-type lived here to give the old hero animation an
     end-of-sequence "landing". The animation is gone (2026-08-21) and
     nothing declares scroll-snap-align any more, so keeping it would only
     risk grabbing the scroll for no reason. */
}
body {
  margin: 0;
  font-family: var(--font);
  color: var(--navy);
  background: var(--white);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Baseline heading rhythm — tighter line-height and a slight negative
   tracking than body copy reads as more "designed" than the browser
   default, without needing a second display typeface. Per-context rules
   below (.section-title, .page-hero h1, .prose h2/h3, etc.) already set
   their own font-size/margin and take precedence via specificity; this
   just gives every heading sitewide the same weight/leading/tracking. */
h1, h2, h3, h4, h5, h6 {
  font-weight: 700;
  line-height: 1.18;
  letter-spacing: -0.015em;
  color: var(--charcoal);
}

.container {
  max-width: 1140px;
  margin: 0 auto;
  padding: 0 24px;
}

a { text-decoration: none; color: inherit; transition: color var(--transition-fast); }
ul { list-style: none; margin: 0; padding: 0; }
img, svg { max-width: 100%; display: block; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-block;
  padding: 13px 24px;
  border-radius: 10px;
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.01em;
  border: 2px solid transparent;
  cursor: pointer;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast),
    background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}
.btn-lg { padding: 16px 30px; font-size: 1rem; }
/* The one primary button on the site: header quote CTA, homepage hero CTA,
   and the contact form's submit. Glossy brand red.

   The gloss is two things, not a texture. The gradient gives the fill a
   light source, and the inset hairline along the top edge is the bright line
   that light leaves on a lacquered surface. The outer shadow is tinted from
   --maroon rather than black so the glow underneath stays in the brand
   instead of reading as generic drop-shadow grey. The sweeping highlight
   band on hover comes free from .btn::after further down. */
.btn-primary {
  background: var(--maroon-gloss);
  color: var(--white);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.26),
              0 4px 14px rgba(135, 4, 18, 0.38);
}
.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.32),
              0 10px 26px rgba(135, 4, 18, 0.46);
}
.btn-primary:active {
  transform: translateY(0);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.20),
              0 4px 12px rgba(135, 4, 18, 0.38);
}
.btn-outline {
  border-color: var(--navy);
  color: var(--navy);
  background: transparent;
}
.btn-outline:hover { background: var(--navy); color: var(--white); transform: translateY(-2px); box-shadow: var(--shadow-sm); }
.btn-outline:active { transform: translateY(0); }
.btn-outline-light {
  border-color: var(--white);
  color: var(--white);
  background: transparent;
}
.btn-outline-light:hover { background: var(--white); color: var(--navy); transform: translateY(-2px); box-shadow: var(--shadow-md); }
.btn-outline-light:active { transform: translateY(0); }
.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(135, 4, 18, 0.25);
}

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  /* Fallback for anything without backdrop-filter: near-opaque, because the
     bar sits over a dark video hero and translucent-without-blur would put
     moving footage straight behind the dark nav type. */
  background: rgba(255, 255, 255, 0.94);
  border-bottom: 1px solid rgba(30, 21, 23, 0.08);
  transition: box-shadow var(--transition-med), border-color var(--transition-med);
}

/* Glass, the way the Tethered Crew and RetailMark bars read: let the page
   through, blur it, and push the saturation back up so the blur does not wash
   the colour out of whatever is underneath.

   These are measured values, not taste calls: the nav type is --charcoal over
   whatever happens to be scrolling underneath. Re-measure with every nav item
   sampled at a dozen scroll positions against the darkest frame of the hero
   video, and re-measure again whenever a dark section is ADDED to the page --
   the worst case moves. It has already moved twice: 0.60/22px measured
   7.31:1 on 2026-09-07 and 6.84:1 the next day, because the near-black trophy
   shelf and the county map now pass under the bar and are darker than
   anything that did before.

   0.56/34px measures 6.12:1 -- comfortably clear of WCAG AA (4.5:1) with a
   36% margin, and past AAA (7:1), which this page can no longer reach at any
   alpha a person would call glass.

   MOST OF THE GLASS IS THE BLUR, NOT THE ALPHA. Blur costs no contrast at all
   -- it averages the background, which pulls the dark extremes up -- so it is
   the free half of the effect and the first dial to reach for. Alpha is the
   expensive half. Saturation is what stops the blur washing the colour out of
   whatever is underneath.

   The floor is roughly 0.50/38px at 5.11:1. Below that this stops being a
   legibility question and starts being a broken one. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header {
    background: rgba(255, 255, 255, 0.56);
    -webkit-backdrop-filter: blur(34px) saturate(2);
    backdrop-filter: blur(34px) saturate(2);
  }
}
/* .scrolled is toggled by script.js once the page has scrolled a little —
   gives the sticky header a bit of lift/separation from the page instead
   of just a flat 1px border once content is moving underneath it. */
.site-header.scrolled {
  box-shadow: 0 4px 24px rgba(30, 21, 23, 0.10);
  border-bottom-color: transparent;   /* the shadow separates it now, not a line */
}
.header-inner {
  display: flex;
  align-items: center;
  gap: 24px;
  padding-top: 9.45px;
  padding-bottom: 9.45px;
  /* Wider than the site's 1140px content column on purpose — nav items
     (now 5 dropdowns + 4 plain links) need more room than a reading
     column, and a nav bar spanning a bit wider than the content below
     it is a normal pattern. Keep this in sync with the 1320/1321
     breakpoint pair below if more nav items are ever added. */
  max-width: 1320px;
}
.logo {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 0.95rem;
  white-space: nowrap;
  flex-shrink: 0;
  transition: opacity var(--transition-fast);
}
.logo:hover { opacity: 0.82; }
.logo-img { height: 42px; width: auto; }

/* THE HEADER IS NOT THE HOMEPAGE CONTENT COLUMN. .header-inner asks for
   1320px, but `body.home .container` is (0,2,1) and `.header-inner` is only
   (0,1,0), so on the homepage the bar was being squeezed to the 1180px
   reading column -- and once the logo lockup opens on scroll, the contents
   measured 1185px against 1132px of room. It had been running 53px
   over-subscribed on every homepage scroll.

   Three classes here, which beats (0,2,1) outright. Interior pages were
   always fine; this only ever bit the homepage. */
.site-header .container.header-inner { max-width: 1320px; }

/* ---------- Header social ----------
   Sits between the phone number and the CTA. 18px rather than the footer's
   22px, and a tight gap, because the bar has about 87px of slack at the
   1321px breakpoint and three icons have to fit inside it with the nav
   still laid out. Re-measure if a nav item is ever added. */
.header-social {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}
.header-social__link {
  display: inline-flex;
  color: var(--slate);
  transition: color var(--transition-fast), transform var(--transition-fast);
}
.header-social__link svg { display: block; fill: currentColor; }
.header-social__link:hover { color: var(--maroon); transform: translateY(-1px); }
.header-social__link:focus-visible {
  outline: 2px solid var(--maroon);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Below the nav breakpoint the bar is logo + phone + button + burger, and
   there is no room for icons. They are still reachable in the footer. */
@media (max-width: 1320px) {
  .header-social { display: none; }
}

/* ---------- Header logo: roof, opening out into the full lockup ----------
   js/motion.js swaps .logo-img for the same four clipped pieces the intro
   uses, and toggles .logo-shown on the header once you scroll past the hero
   mark. The gesture is deliberately the intro's, so the logo behaves the same
   way wherever it assembles.

   OPEN IS THE DEFAULT, and the .hero-lockup guard is what enforces it. That
   class is set only once the swap has actually succeeded on a page that has a
   hero mark, so every interior page, every page with JS off, and anyone on
   prefers-reduced-motion gets an ordinary full logo. A failure here can never
   leave a header showing an unexplained triangle.

   --logo-h drives both states, so the 34px mobile size needs no second set of
   numbers. 720/193 is the artwork's aspect; 0.242 is the roof wedge's width
   as a fraction of the whole, straight off the measured cut lines
   (85.03% - 60.83%). That factor and the -60.83% shift below are one set --
   the wedge has to land exactly in the collapsed window, or the roof sits
   off-centre with a sliver of R or Y beside it. */
.logo { --logo-h: 42px; }

.logo-lockup {
  position: relative;
  height: var(--logo-h);
  width: calc(var(--logo-h) * 720 / 193);
  /* flex: none is load bearing. .logo is a flex row, so with the default
     flex-shrink the lockup obediently shrinks to whatever width the link has
     -- which scales the artwork down instead of clipping it, rendering a tiny
     whole logo rather than a full-size roof. The lockup keeps its width and
     lets the link's overflow do the hiding. */
  flex: none;
}

/* THE ROOF IS THE ONLY THING THAT MOVES.

   This transform used to sit on .logo-lockup, which meant translating and
   scaling it dragged McR, the Y and the strapline along with the roof -- they
   slid and shrank into place instead of simply appearing. It belongs on the
   roof's own window.

   That window carries the clip-path, so transforming it moves the clipped
   wedge as one object. Putting it on the <img> inside instead would hold the
   wedge-shaped hole still while the artwork slid behind it, cropping a
   different part of the logo rather than moving the roof -- the same trap the
   intro already documents.

   Origin is the wedge's left edge and the roof ink's vertical centre, so the
   scale grows the roof about itself rather than dragging it in from the
   corner of the lockup. Scoped to .logo-lockup because the intro sets its own
   origin on the same class for a different move. */
.logo-lockup .il-part--a {
  transform-origin: 60.83% 40.16%;
  transform: translate(0, 0) scale(1);
  transition: transform 520ms var(--ease);
  will-change: transform;
}

/* The window opens FASTER than the pieces fade, and that pairing is load
   bearing. The pieces sit at their finished positions now, so anything still
   outside this box is clipped -- a slow window would reveal them edge-first
   as a wipe rather than a fade. At 340ms against a 480ms fade the box is
   essentially open before the fade is half done, so what you see is type
   appearing in place. */
.hero-lockup .site-header .logo {
  overflow: hidden;
  width: calc(var(--logo-h) * 720 / 193);
  transition: width 340ms var(--ease);
}

/* Collapsed: the window is just the roof wedge, the lockup slides left so the
   wedge lands in it, and it scales up so the roof reads as a mark rather than
   a detail. Opening runs all three back at once, which is what makes McR
   appear to the roof's left and Y to its right.

   The three numbers are one set, all derived from the measured artwork:

     -60.83%  the wedge's left edge, so it lands at the window's left edge
      1.36    the roof ink is only 52.3% of the lockup's height (rows 27-128
              of 193), so at scale 1 it is a 22px mark in a 42px bar and
              reads as undersized. 1.36 takes it to ~30px.
      +9.84%  the roof's ink centre sits at 40.16% of the box, not 50%,
              because the empty strapline band is below it. Without this the
              mark rides visibly high against the nav type.

     0.3291   the collapsed window: the wedge is 24.20% of the width
              (85.03% - 60.83%), times the 1.36 scale.

   Change the scale and 0.3291 has to move with it. */
.hero-lockup .site-header:not(.logo-shown) .logo {
  width: calc(var(--logo-h) * 720 / 193 * 0.3291);
}
.hero-lockup .site-header:not(.logo-shown) .il-part--a {
  transform: translate(-60.83%, 9.84%) scale(1.36);
}
/* Everything except the roof is faded out while collapsed, and the RIGHT
   piece is the one that actually needs it. The window is a rectangle but the
   roof's right edge is a DIAGONAL, so the triangle above that diagonal falls
   inside the rectangle -- and that triangle contains the top of the Y's arm.
   It showed as a maroon splinter floating off the roof's shoulder. No width
   fixes it: pulling the window in to the diagonal's top would slice the tip
   off the roof's right leg, which reaches further right at the bottom than
   the Y's arm does at the top.

   The strapline needs it for the same reason -- it runs the full width, so
   the shift drags its middle under the roof, inside the window.

   The left piece is already off the window's left edge and needs none of
   this, but it is faded with the others so the rule is "only the roof shows",
   which survives someone nudging a cut line later.

   They fade at their finished size and position -- nothing but the roof
   moves -- which is the whole point. */
.logo-lockup .il-part img { transition: opacity 480ms var(--ease); }
.hero-lockup .site-header:not(.logo-shown) .il-part--left img,
.hero-lockup .site-header:not(.logo-shown) .il-part--right img,
.hero-lockup .site-header:not(.logo-shown) .il-part--tag img {
  opacity: 0;
}
.main-nav {
  display: flex;
  align-items: center;
  /* 18, not 22. The social icons took the bar's spare width down to 1px of
     slack at the 1321px breakpoint, which is not a margin, it is a
     coincidence -- one different font metric and the nav wraps. Four pixels
     off each of the eight gaps buys 32px back and is invisible at this size.
     If a nav item is ever added, measure again: logo + nav + cta + 2 gaps
     against .header-inner's content box. */
  gap: 18px;
  margin-left: auto;
  font-weight: 500;
  font-size: 0.95rem;
}
.main-nav > a { white-space: nowrap; }
.main-nav a:hover { color: var(--roof-2); }

/* Underline-grow affordance on the top-level nav links — a thin bar that
   scales in from the left on hover/focus rather than an instant color
   swap, so the nav feels considered rather than templated. Scoped to the
   direct links + dropdown labels only (not the panel links inside an open
   dropdown) to keep the flyout itself simple. */
.main-nav > a, .nav-dropdown-label {
  position: relative;
}
.main-nav > a::after, .nav-dropdown-label::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -5px;
  height: 2px;
  background: var(--roof-2);
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--transition-fast);
}
.main-nav > a:hover::after, .main-nav > a:focus-visible::after,
.nav-dropdown-label:hover::after, .nav-dropdown-label:focus-visible::after { transform: scaleX(1); }

.nav-dropdown { position: relative; display: inline-flex; align-items: center; gap: 2px; }
.nav-dropdown-label {
  font-weight: 500;
  font-size: 0.95rem;
  color: inherit;
  white-space: nowrap;
  flex-shrink: 0;
}
.nav-dropdown-label:hover { color: var(--roof-2); }
.nav-dropdown-toggle {
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 2px;
  color: inherit;
  display: flex;
  align-items: center;
  font-size: 0.65rem;
  line-height: 1;
  flex-shrink: 0;
}
.nav-dropdown-toggle:hover { color: var(--roof-2); }
.nav-dropdown-toggle svg { width: 10px; height: 10px; flex-shrink: 0; transition: transform 0.15s ease; }
.nav-dropdown.open .nav-dropdown-toggle svg { transform: rotate(180deg); }

/* Open state is driven entirely by JS toggling the .open class (hover
   with a short close-delay on pointer devices, tap on touch/mobile,
   click-outside-to-close) rather than CSS :hover — a plain hover
   dropdown closes the instant the cursor crosses the visual gap between
   the nav label and the floating panel below it, which made the menu
   impossible to actually use. */
.dropdown-panel {
  display: none;
  flex-direction: column;
  gap: 10px;
  padding: 10px 0 4px 14px;
}
.nav-dropdown.open .dropdown-panel { display: flex; }

@media (min-width: 1321px) {
  .nav-dropdown.open .dropdown-panel {
    position: absolute;
    top: calc(100% + 14px);
    left: 0;
    background: var(--white);
    border: 1px solid #eee;
    border-radius: 10px;
    box-shadow: 0 12px 28px rgba(16, 24, 40, 0.12);
    padding: 12px 16px;
    min-width: 220px;
    gap: 6px;
    z-index: 50;
  }
  .dropdown-panel a { font-size: 0.9rem; white-space: nowrap; }
}

.header-cta {
  display: flex;
  align-items: center;
  gap: 16px;
  white-space: nowrap;
}
.phone-link { font-weight: 700; }
.phone-link:hover { color: var(--roof-2); }
.nav-toggle {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 6px;
  margin-left: auto;
}
.nav-toggle span {
  display: block;
  width: 24px;
  height: 2px;
  background: var(--navy);
  margin: 5px 0;
}

/* ---------- Shared page-hero subhead ----------
   Sole survivor of the removed hero-animation block: .hero-sub is what
   the .page-hero subhead uses on 38 other pages, so despite the name it
   was never homepage-only. */
.hero-sub {
  color: var(--slate);
  font-size: 0.95rem;
  max-width: 60ch;
  margin: 0;
}


/* ---------- Shared: eyebrow + CTA button row ----------
   These three rules physically sat inside the old
   "Hero / Scroll Animation" section of this file, but they are NOT
   hero-specific -- .eyebrow is on 39 pages and .cta-buttons on 36.
   Removing the hero block took them out with it and silently
   un-styled every section eyebrow on the site (caught in a
   screenshot, 2026-08-21). Restored here, outside that block, so
   the same mistake cannot repeat. */
.eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--roof-1);
  margin: 0 0 4px;
}
.eyebrow.center { text-align: center; }
.cta-buttons { display: flex; gap: 14px; flex-wrap: wrap; justify-content: center; }

/* ---------- Sections ---------- */
.section { padding: 100px 0; }
.section-alt { background: var(--gray-light); }
.section-title {
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  margin: 0 0 44px;
}
.section-title.center { text-align: center; }
@media (max-width: 760px) {
  .section { padding: 68px 0; }
  .section-title { margin: 0 0 32px; }
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 24px;
}
.card {
  background: var(--white);
  border: 1px solid #eee;
  border-radius: 16px;
  padding: 30px 26px;
  box-shadow: var(--shadow-sm);
  transition: transform var(--transition-med), box-shadow var(--transition-med), border-color var(--transition-med),
    opacity var(--transition-med), translate var(--transition-med);
}
.card:hover { transform: translateY(-5px); box-shadow: var(--shadow-md); border-color: var(--roof-3); }
.card-icon {
  width: 46px;
  height: 46px;
  margin-bottom: 16px;
  border-radius: 10px;
  background: var(--roof-gradient);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--transition-med);
}
.card-icon svg { width: 24px; height: 24px; stroke: var(--white); }
.card:hover .card-icon { transform: scale(1.08) rotate(-2deg); }
.card h3 { margin: 0 0 10px; font-size: 1.12rem; }
.card p { margin: 0; color: var(--slate); font-size: 0.95rem; }

/* ---------- Recent Projects gallery ---------- */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 22px;
}
.gallery-item {
  position: relative;
  margin: 0;
  aspect-ratio: 4 / 3;
  border-radius: 14px;
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition-med), opacity var(--transition-med), translate var(--transition-med);
}
.gallery-item:hover { box-shadow: var(--shadow-lg); }
.gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.45s var(--ease);
}
.gallery-item:hover img { transform: scale(1.06); }
.gallery-item figcaption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 28px 16px 12px;
  background: linear-gradient(to top, rgba(30, 21, 23, 0.85), rgba(30, 21, 23, 0));
  color: var(--white);
  font-size: 0.88rem;
  font-weight: 600;
}

/* ---------- Featured real-work photo (material/service detail pages) ---------- */
.feature-photo {
  max-width: 640px;
  margin: 0 auto;
}
.feature-photo-frame {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: 16px;
  overflow: hidden;
  box-shadow: var(--shadow-md);
  transition: box-shadow var(--transition-med), opacity var(--transition-med), translate var(--transition-med);
}
.feature-photo-frame:hover { box-shadow: var(--shadow-lg); }
.feature-photo-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.45s var(--ease);
}
.feature-photo-frame:hover img { transform: scale(1.04); }
.feature-photo figcaption {
  text-align: center;
  color: var(--slate);
  font-size: 0.88rem;
  margin-top: 14px;
}

.materials-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 14px;
}
.material-pill {
  background: var(--white);
  border: 1px solid #e2e2e2;
  border-radius: 999px;
  padding: 10px 20px;
  font-weight: 600;
  font-size: 0.92rem;
}
a.material-pill {
  text-decoration: none;
  color: inherit;
  transition: background var(--transition-fast), color var(--transition-fast),
    border-color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-fast);
}
a.material-pill:hover {
  background: var(--roof-1);
  color: var(--white);
  border-color: var(--roof-1);
  transform: translateY(-2px);
  box-shadow: var(--shadow-sm);
}

.cert-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 24px;
}
.cert-badge {
  text-align: center;
  padding: 32px 22px;
  border-radius: 16px;
  border: 2px solid var(--roof-3);
  background: var(--off-white);
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.cert-badge strong { display: block; font-size: 1.25rem; color: var(--maroon); }
.cert-badge span { color: var(--slate); font-size: 0.9rem; }
.cert-logo { height: 56px; width: auto; margin-bottom: 4px; transition: transform var(--transition-med); }
.cert-logo-gaf { height: 76px; }
.cert-logo-certainteed { height: 68px; }
a.cert-badge {
  text-decoration: none;
  color: inherit;
  transition: transform var(--transition-med), box-shadow var(--transition-med), border-color var(--transition-med),
    opacity var(--transition-med), translate var(--transition-med);
}
a.cert-badge:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow-md);
  border-color: var(--roof-1);
}
a.cert-badge:hover .cert-logo { transform: scale(1.06); }

/* ---------- Certification Spotlight (flagship awards) ---------- */
.cert-spotlight-grid {
  display: grid;
  /* min(340px, 100%) instead of a bare 340px — the bare value can't shrink
     below itself, so it overflowed the container (and caused sitewide
     horizontal scroll) on any phone narrower than ~390px. */
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  gap: 28px;
  margin-bottom: 52px;
}
.cert-spotlight {
  position: relative;
  border-radius: 22px;
  padding: 42px 38px;
  color: var(--white);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
}
a.cert-spotlight {
  display: block;
  text-decoration: none;
  transition: transform var(--transition-med), box-shadow var(--transition-med),
    opacity var(--transition-med), translate var(--transition-med);
}
a.cert-spotlight:hover {
  transform: translateY(-6px);
  box-shadow: 0 28px 56px rgba(16, 24, 40, 0.32);
}
.cert-spotlight::before {
  content: "";
  position: absolute;
  top: -60px;
  right: -60px;
  width: 220px;
  height: 220px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
}
.cert-spotlight-gaf { background: var(--maroon-gradient); }
.cert-spotlight-brava { background: linear-gradient(135deg, var(--charcoal) 0%, #3a2a2d 100%); }
.cert-spotlight-pill {
  position: relative;
  display: inline-block;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.32);
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  margin-bottom: 20px;
}
.cert-spotlight-badge-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 18px;
}
.cert-spotlight-logo {
  height: 60px;
  width: auto;
  background: var(--white);
  border-radius: 12px;
  padding: 8px;
  flex-shrink: 0;
}
.cert-spotlight-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--roof-4);
  margin: 0 0 4px;
}
.cert-spotlight h3 {
  position: relative;
  font-size: clamp(1.3rem, 2.3vw, 1.7rem);
  margin: 0;
  color: var(--white);
  line-height: 1.25;
}
.cert-spotlight p {
  position: relative;
  color: rgba(255, 255, 255, 0.82);
  font-size: 0.96rem;
  line-height: 1.6;
  margin: 0 0 20px;
}
.cert-spotlight-stats {
  position: relative;
  display: flex;
  gap: 32px;
  padding-top: 18px;
  border-top: 1px solid rgba(255, 255, 255, 0.18);
}
.cert-spotlight-stat-num {
  font-size: clamp(1.9rem, 4vw, 2.4rem);
  font-weight: 800;
  line-height: 1;
  color: var(--roof-4);
  margin-bottom: 6px;
}
.cert-spotlight-stat-label {
  font-size: 0.82rem;
  color: rgba(255, 255, 255, 0.72);
  max-width: 16ch;
  line-height: 1.35;
  margin: 0;
}

.areas-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px 18px;
  font-weight: 600;
  margin-bottom: 20px;
}
.areas-grid span, .areas-grid a {
  background: var(--white);
  padding: 8px 16px;
  border-radius: 8px;
  border: 1px solid #e2e2e2;
}
.areas-grid a {
  text-decoration: none;
  color: inherit;
  transition: background var(--transition-fast), color var(--transition-fast),
    border-color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-fast);
}
.areas-grid a:hover {
  background: var(--roof-1);
  color: var(--white);
  border-color: var(--roof-1);
  transform: translateY(-2px);
  box-shadow: var(--shadow-sm);
}
.areas-note { text-align: center; color: var(--slate); }
.areas-note a { color: var(--roof-1); font-weight: 600; transition: color var(--transition-fast); }
.areas-note a:hover { color: var(--roof-2); }

/* ---------- CTA Banner ---------- */
.cta-banner {
  background: var(--maroon-gradient);
  color: var(--white);
  padding: 68px 0;
  position: relative;
  overflow: hidden;
}
/* Faint radial glow instead of a flat gradient fill — same maroon
   family, just a little more dimensional so the banner doesn't read as a
   solid color block between two lighter sections. */
.cta-banner::before {
  content: "";
  position: absolute;
  top: -40%;
  right: -10%;
  width: 60%;
  height: 200%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.08), transparent 70%);
  pointer-events: none;
}
.cta-banner-inner {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.cta-banner h2 { margin: 0 0 8px; font-size: 1.65rem; color: var(--white); }
.cta-banner p { margin: 0; color: rgba(255, 255, 255, 0.85); }

/* ---------- Contact ---------- */
.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 50px;
  align-items: start;
}
.contact-list li { margin-bottom: 14px; }
.contact-list a { color: var(--roof-1); font-weight: 600; }
.contact-form {
  display: grid;
  /* minmax(0, 1fr), not the default auto: an auto column sizes to its widest
     child, so anything intrinsically wide -- a dragged textarea, a long
     unbroken string -- widens the column and drags every sibling field out
     past the card with it. This caps the column at the card. */
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
  background: var(--gray-light);
  padding: 28px;
  border-radius: 14px;
}
.contact-form label {
  display: grid;
  gap: 6px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--slate);
}
.contact-form input, .contact-form textarea {
  font-family: var(--font);
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid #d5d5d5;
  font-size: 0.95rem;
  /* The grid column sizes to its widest child, so a field that can grow wider
     than the card drags every sibling out with it. Belt and braces against
     that, independent of the resize rule below. */
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
}
/* NO DRAG HANDLE. Browsers default a textarea to resize:both, and dragging
   this one wider stretched the form's grid column: measured at 1440px, pulling
   the box to 900px left the card at 523px and pushed every input 377px past
   its right edge. resize:none removes the handle outright (Caleb, 2026-09-08).
   `resize: vertical` would keep a usable version of it -- taller only, which
   cannot break the layout -- if the message box should still be expandable. */
.contact-form textarea {
  resize: none;
}
.contact-form button { justify-self: start; }

/* ---------- Footer ---------- */
.site-footer {
  /* Not --charcoal: see the note on --footer-black. The warm tint that suits
     body type turns brown over an area this size. */
  background: var(--footer-black);
  color: rgba(255, 255, 255, 0.72);
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.2fr 1fr 1fr 1fr 1fr;
  gap: 32px;
  padding: 44px 0 30px;
}
.footer-col { display: flex; flex-direction: column; gap: 10px; font-size: 0.9rem; }
.footer-col h4 {
  margin: 0 0 4px;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--roof-4);
}
.footer-col a:hover { color: var(--white); }
.footer-brand .footer-logo { font-weight: 600; font-size: 1.05rem; color: var(--white); }
.footer-brand .footer-logo strong { color: var(--roof-4); }
/* The footer sits on --charcoal, and logo-horizontal.png is the maroon
   lockup, so at 30px it all but disappears into the background. There is no
   white version of the asset, so one is made here rather than adding a second
   file that has to be kept in step with the first.

   brightness(0) crushes every colour to black while leaving the alpha channel
   alone, then invert(1) flips that to white -- so the maroon type AND the gold
   roof both come out solid white and the transparent background stays
   transparent. Order matters: invert first and the maroon becomes cyan.

   This is the only place the logo sits on a dark ground. The header and the
   intro use the same file untouched. */
.footer-logo-img {
  height: 30px;
  width: auto;
  align-self: flex-start;
  filter: brightness(0) invert(1);
}
.footer-brand p { color: rgba(255, 255, 255, 0.55); font-size: 0.85rem; line-height: 1.6; margin: 10px 0 0; }
.footer-bottom {
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  padding: 18px 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.8rem;
  color: rgba(255, 255, 255, 0.5);
}
@media (max-width: 760px) {
  .footer-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 480px) {
  .footer-grid { grid-template-columns: 1fr; }
}

/* ---------- Responsive ---------- */
@media (max-width: 900px) {
  .contact-grid { grid-template-columns: 1fr; }
}

@media (max-width: 1320px) {
  .main-nav, .header-cta .phone-link { display: none; }
  .nav-toggle { display: block; }
  .header-cta .btn { padding: 10px 16px; font-size: 0.85rem; }
  .cta-banner-inner { flex-direction: column; align-items: flex-start; }

  .main-nav.open {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--white);
    padding: 20px 24px;
    gap: 16px;
    border-bottom: 1px solid #eee;
    max-height: calc(100vh - 100%);
    overflow-y: auto;
  }
}

@media (max-width: 480px) {
  /* Below 480px the full-text quote button got hidden with nothing put back
     in its place — combined with the phone number already being hidden at
     the 1320px breakpoint above, phones in portrait (most mobile traffic)
     had zero call-to-action in the header, just a logo and hamburger.
     Trade the button for the phone number instead of dropping both: shrink
     the gap/logo so it still fits next to the hamburger. */
  .header-inner { gap: 12px; }
  .logo-img { height: 34px; }
  .logo { --logo-h: 34px; }
  .header-cta .btn { display: none; }
  .header-cta .phone-link { display: inline-block; font-size: 0.82rem; white-space: nowrap; }

  .cta-buttons { gap: 8px; }
  .cta-buttons .btn { padding: 8px 14px; font-size: 0.8rem; }
  .btn-lg { padding: 8px 14px; font-size: 0.8rem; }
}

/* ---------- Interior Page Hero ---------- */
/* Brand maroon, not the copper gradient it used to be (Caleb, 2026-09-08:
   the gold default read as orange, which is not in the logo). The gold is
   kept as a stripe along the bottom edge instead -- the same move as the
   hero swoop on the homepage, where gold works as an accent and not as the
   field colour.

   position:relative is for that stripe's pseudo-element. .page-hero--video
   sets its own position and isolation and is unaffected; the stripe rides
   under the video heroes too, which is what keeps all 36 pages consistent. */
.page-hero {
  position: relative;
  background: var(--maroon-gradient);
  color: var(--white);
  padding: 60px 0 52px;
  text-align: center;
}
.page-hero::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 4px;
  /* The logo's own copper ramp rather than a flat gold, so the stripe has
     the same direction the roof mark does. */
  background: linear-gradient(90deg, var(--roof-1), var(--roof-3) 55%, var(--roof-4));
  /* Above the video and scrim, which sit at z-index -2 and -1. */
  z-index: 1;
}
.page-hero .eyebrow { color: rgba(255, 255, 255, 0.85); }
.page-hero h1 {
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  line-height: 1.2;
  margin: 0 0 14px;
  color: var(--white);
}
.page-hero p.page-hero-sub {
  max-width: 62ch;
  margin: 0 auto 22px;
  color: rgba(255, 255, 255, 0.92);
  font-size: 1.02rem;
}
.page-hero .cta-buttons { justify-content: center; }
.page-hero .btn-outline { border-color: var(--white); color: var(--white); }
.page-hero .btn-outline:hover { background: var(--white); color: var(--navy); }

.breadcrumbs {
  font-size: 0.82rem;
  color: rgba(255, 255, 255, 0.75);
  margin: 0 0 16px;
}
.breadcrumbs a { color: rgba(255, 255, 255, 0.85); }
.breadcrumbs a:hover { color: var(--white); text-decoration: underline; }
.breadcrumbs .sep { margin: 0 6px; opacity: 0.6; }

@media (max-width: 480px) {
  .page-hero { padding: 40px 0 34px; }
}

/* ---------- Prose / content blocks ---------- */
.prose { max-width: 760px; margin: 0 auto; }
.prose p { color: var(--slate); margin: 0 0 16px; font-size: 1rem; }
.prose h2 { margin: 0 0 16px; }
.prose h3 { margin: 30px 0 10px; font-size: 1.2rem; }
.prose ul { margin: 0 0 16px; padding-left: 20px; list-style: disc; }
.prose li { margin-bottom: 8px; color: var(--slate); }

/* ---------- Cross-link chips (service <-> location) ---------- */
.link-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}
.link-chip {
  background: var(--white);
  border: 1px solid #e2e2e2;
  border-radius: 999px;
  padding: 9px 18px;
  font-weight: 600;
  font-size: 0.88rem;
  color: var(--roof-1);
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.link-chip:hover { background: var(--roof-1); color: var(--white); border-color: var(--roof-1); }

/* ---------- FAQ ---------- */
.faq-list {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 760px;
  margin: 0 auto;
}
.faq-item {
  background: var(--white);
  border: 1px solid #eee;
  border-radius: 12px;
  padding: 18px 22px;
}
.faq-item h3 { margin: 0 0 8px; font-size: 1.02rem; }
.faq-item p { margin: 0; color: var(--slate); font-size: 0.92rem; }

/* ---------- Location map embed ---------- */
.map-embed {
  border: 0;
  width: 100%;
  height: 320px;
  border-radius: 14px;
  display: block;
}

/* ---------- Home: condensed contact blurb ---------- */
.contact-blurb { text-align: center; max-width: 640px; margin: 0 auto; }
.contact-blurb-text { color: var(--slate); margin: 0 0 22px; }
.contact-list-center {
  display: flex;
  gap: 24px;
  justify-content: center;
  margin: 0 0 26px;
  flex-wrap: wrap;
  color: var(--slate);
}
.contact-list-center a { color: var(--roof-1); font-weight: 600; }

/* ---------- Blog ---------- */
.blog-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
}
.blog-card {
  background: var(--white);
  border: 1px solid #eee;
  border-radius: 14px;
  padding: 26px;
  box-shadow: 0 2px 10px rgba(16, 24, 40, 0.04);
}
.blog-card .blog-date {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--roof-1);
  font-weight: 700;
}
.blog-card h3 { margin: 8px 0 10px; font-size: 1.15rem; }
.blog-card p { margin: 0 0 14px; color: var(--slate); font-size: 0.92rem; }
.blog-card a.read-more { color: var(--roof-1); font-weight: 600; }
.blog-meta { font-size: 0.85rem; color: var(--gray); margin: 0 0 24px; text-align: center; }

/* ---------- Cards as links (Home service/location cards) ---------- */
a.card { text-decoration: none; }
.card-link { color: var(--roof-1); font-weight: 600; font-size: 0.88rem; display: inline-block; margin-top: 10px; }

/* --- Contact form honeypot (contact.html) -------------------------------
   Hides the decoy field from people while leaving it in the DOM for bots.
   Positioned off-screen rather than display:none deliberately: some
   form-filling bots skip display:none inputs, and an unfilled honeypot
   catches nothing. Never put a real field in here. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* =========================================================================
   HOMEPAGE — "expensive minimalism" pass (2026-08-21)
   -------------------------------------------------------------------------
   Everything here is scoped behind `body.home` (or a `home-` prefixed class)
   ON PURPOSE. styles.css is shared by ~39 pages, and the brief was to
   restyle the homepage, so nothing below may reach the others.

   The look is drawn from franco-roofing.com, nextgenroofing.com,
   newmanroofing.com, toproofers.ca and dandlroofing.com. What they share,
   and what this implements: a SHORT protective headline instead of a
   paragraph; credentials placed after the hook rather than first; a numbered
   process section (all five have one, this site had none); large photography
   carrying the craftsmanship claim instead of adjectives; and a lot of air.

   The mechanics of "expensive": more whitespace, hairline rules instead of
   boxes and drop shadows, larger type with tighter letter-spacing, and
   colour used as an accent rather than a fill.
   ========================================================================= */

/* More air between sections than the shared 100px, homepage only. */
body.home .section { padding: clamp(84px, 11vw, 150px) 0; }

body.home .container { max-width: 1180px; }

/* Section headings: sentence case, tighter tracking, left-aligned. Centered
   all-caps headings read as a brochure; this reads as a practice. */
.home-section-title {
  font-size: clamp(1.9rem, 4vw, 3rem);
  line-height: 1.1;
  letter-spacing: -0.025em;
  margin: 10px 0 clamp(40px, 5vw, 68px);
  max-width: 20ch;
}

/* ---------- Hero ---------- */
.home-hero {
  padding: clamp(90px, 14vw, 190px) 0 clamp(70px, 9vw, 120px);
}

/* The logo above the eyebrow. Deliberately subordinate to the headline -- it
   identifies, the headline sells -- so it sits well under the h1's size.
   The resting mark is the white-type artwork, so it takes a DARK shadow, not
   a glow -- this video runs from near-black roof to bright sky, and white
   type disappears into the bright frames without one. drop-shadow rather than
   box-shadow because it follows the letterforms' alpha instead of drawing a
   rectangle round a transparent PNG, and offset down so the mark reads as
   sitting above the footage rather than glowing on it.

   js/motion.js observes this element to drive the header hand-off, and reads
   its data-intro-src for the colour artwork the load animation assembles. */
.home-hero-logo {
  display: block;
  width: clamp(172px, 18vw, 264px);
  height: auto;
  margin: 0 0 clamp(20px, 2.4vw, 30px);
  filter: drop-shadow(0 3px 16px rgba(30, 21, 23, 0.55));
}

.home-hero-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.76rem;
  font-weight: 600;
  color: var(--slate);
  margin: 0 0 clamp(24px, 3vw, 40px);
}

.home-hero-title {
  /* The single most important "expensive" lever on the page: large, tightly
     tracked, and short enough to break across two or three lines. */
  font-size: clamp(2.4rem, 6.4vw, 5rem);
  line-height: 1.03;
  letter-spacing: -0.035em;
  font-weight: 700;
  margin: 0;
  max-width: 16ch;
}

.home-hero-sub {
  font-size: clamp(1rem, 1.5vw, 1.2rem);
  line-height: 1.65;
  color: var(--slate);
  max-width: 54ch;
  margin: clamp(22px, 3vw, 34px) 0 0;
}

.home-hero-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 22px;
  margin-top: clamp(30px, 4vw, 46px);
}

.home-hero-phone {
  font-size: 0.98rem;
  color: var(--slate);
  border-bottom: 1px solid rgba(74, 60, 62, 0.28);
  padding-bottom: 2px;
}
.home-hero-phone:hover { color: var(--maroon); border-bottom-color: var(--maroon); }

/* ---------- Brand swoop ----------
   The curved bottom edge of the hero. See the markup comment in index.html
   for why it is two paths and why it is a sibling of both sections.

   The wrapper is a zero-height full-bleed strip whose top edge IS the seam
   between the hero and the credential band. translateY(-100%) then lifts the
   whole SVG to sit entirely ABOVE that seam, inside the hero -- which is the
   point. The band is not laid across the join, it replaces it: video above
   the curve, page white below it, and the credential strip picks that white
   up with nothing to give the join away.

   z-index 3 clears the hero's internal layers (video at -2, scrim at -1
   inside .home-hero--video's isolation context) while staying well under the
   sticky header at 100. */
.home-swoop {
  position: relative;
  width: 100%;
  height: 0;
  z-index: 3;
  /* Ornament only: never intercept a click meant for the CTA above it. */
  pointer-events: none;
}

.home-swoop svg {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: clamp(64px, 8vw, 116px);
  transform: translateY(-100%);
  display: block;
}

/* The page's own white, not a hard-coded one. If --white ever moves, the
   shape that fakes the hero's bottom edge has to move with it or a band of
   the wrong white appears under the curve. */
.home-swoop__ground { fill: var(--white); }

/* Nothing on the band itself: no stroke, no shadow. It reads as the edge of
   the photograph, and an edge does not cast a shadow onto the page it ends
   at. */
.home-swoop__band { stroke: none; }

/* Same reasoning as the band: the gold accent is part of the page's edge,
   not an object sitting on it. Its curve is the band's lower edge exactly,
   so the two share it and no hairline of video shows between them. */
.home-swoop__accent { stroke: none; }

/* The credential strip's own hairline top border would run dead straight
   underneath the curve, which is the one thing that gives the whole illusion
   away. The band is the divider now, so the rule comes off.

   The padding is what the border used to buy in breathing room, plus a
   little for the curve landing above it. */
.home-swoop + .home-creds {
  border-top: 0;
  padding-top: clamp(34px, 4vw, 52px);
}

/* Narrow screens. preserveAspectRatio:none squashes the 1440-wide box into a
   phone, so the sweep steepens as the screen narrows unless the height comes
   down with it. Holding the box near its desktop aspect keeps the same
   gentle curve rather than a diagonal wedge. */
@media (max-width: 760px) {
  .home-swoop svg { height: clamp(38px, 9vw, 62px); }
  .home-swoop + .home-creds { padding-top: clamp(26px, 6vw, 38px); }
}

/* ---------- Credential strip ---------- */
.home-creds {
  border-top: 1px solid rgba(30, 21, 23, 0.10);
  border-bottom: 1px solid rgba(30, 21, 23, 0.10);
  padding: clamp(26px, 3.5vw, 40px) 0;
}

.home-creds-label {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--gray);
  margin: 0 0 22px;
  text-align: center;
}

.home-creds-row {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: clamp(30px, 6vw, 76px);
}

.home-creds-row img {
  /* These logos have wildly different aspect ratios (a diamond, a wide
     wordmark, an oval, a shield), so a single `height` makes the square ones
     look tiny. Cap both axes and let each sit at its own optical size. */
  height: clamp(46px, 5.5vw, 66px);
  /* 230 not 180: the CertainTeed mark is a 3.5:1 tile, and a narrower cap
     letterboxed it well under the 66px the other marks get, leaving it the
     runt of the row rather than one of four equals. At 230 it runs full
     height like Brava and DaVinci. */
  max-width: 230px;
  width: auto;
  object-fit: contain;
  /* Full colour, deliberately. These were desaturated to keep the strip a
     quiet band, but a greyed-out manufacturer mark reads as a disclaimer
     rather than a credential -- and these four are the strongest thing the
     company has to say. Boast with them. */
  opacity: 1;
  transition: filter var(--transition-med), opacity var(--transition-med);
}
/* The GAF mark is a solid colour block, not dark type on white. At the same
   height as the wordmarks it carries roughly twice their optical weight and
   pulls the eye to the left end of the row, so it rides one notch smaller.
   Scoped to this strip only -- the same file is a full-size badge on the
   certification pages. */
.home-creds-row img.is-block-mark {
  height: clamp(38px, 4.4vw, 52px);
}

.home-creds-row a:hover img { transform: translateY(-2px); }

/* ---------- Full-bleed showpiece ---------- */
/* ---------- Proof (the rare credential) ---------- */
.home-proof-title {
  font-size: clamp(1.9rem, 4vw, 3rem);
  line-height: 1.1;
  letter-spacing: -0.025em;
  margin: 10px 0 clamp(48px, 6vw, 80px);
  max-width: 20ch;
}

.home-proof-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: clamp(34px, 5vw, 70px);
  padding-bottom: clamp(44px, 5vw, 70px);
  border-bottom: 1px solid rgba(30, 21, 23, 0.10);
}

.home-proof-num {
  font-size: clamp(3.2rem, 8vw, 5.6rem);
  line-height: 0.95;
  letter-spacing: -0.05em;
  font-weight: 700;
  color: var(--maroon);
  margin: 0 0 16px;
}
/* The connector word ("of") set small and quiet so the numerals carry it. */
.home-proof-num span {
  font-size: 0.26em;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--slate);
  vertical-align: middle;
  /* NOTE: this margin is in `em` of the SHRUNKEN span (0.26 of the parent),
     so 0.5em here is only ~0.35rem of real gap next to a 5.6rem numeral --
     it read as "1OF 12". Sized against the parent instead. */
  margin: 0 0.85em;
}
.home-proof-num span:first-child { margin-left: 0; }
/* A unit that belongs to the numeral it follows ("3%"), not a connector word
   between two numerals ("1 of 12"). The generous connector margin reads as a
   space in the number itself here, so it is pulled back in tight. */
.home-proof-num .unit { margin-left: 0.15em; margin-right: 0; }

.home-proof-label {
  font-size: 1rem;
  line-height: 1.6;
  color: var(--slate);
  margin: 0;
  max-width: 30ch;
}

.home-proof-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: clamp(30px, 4vw, 56px);
  padding-top: clamp(44px, 5vw, 70px);
}

/* No card chrome at all: a hairline top rule instead of a bordered, shadowed
   box. This is the single biggest difference between the old "premium" look
   and an actually expensive one. */
.home-proof-card {
  display: block;
  border-top: 2px solid var(--charcoal);
  padding-top: 22px;
  transition: border-color var(--transition-med);
}
.home-proof-card:hover { border-top-color: var(--maroon); }
.home-proof-card h3 {
  font-size: 1.35rem;
  letter-spacing: -0.015em;
  margin: 0 0 12px;
}
.home-proof-card p {
  color: var(--slate);
  line-height: 1.7;
  margin: 0 0 16px;
}
.home-proof-card-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--roof-1);
  margin: 0 0 10px;
}
.home-proof-card .card-link { color: var(--maroon); font-weight: 600; font-size: 0.92rem; }

/* Certifications index: the cards carry the real manufacturer mark instead
   of the identical trophy glyph every one of them used to share. Five cards
   with the same icon is five cards with no icon. */
.cred-tile .cred-card__mark { height: 46px; margin: 0 0 16px; }
.cred-tile .cred-card__mark img { max-width: 130px; }

/* A single explaining line under the section title. The credentials only
   land if the reader knows nobody handed them out to themselves. */
.home-proof-lede {
  font-size: 1.05rem;
  line-height: 1.7;
  color: var(--slate);
  max-width: 58ch;
  margin: -28px 0 clamp(40px, 5vw, 64px);
}

/* Second row: the three table-stakes certifications, three-up. Separate
   grid from .home-proof-cards rather than one five-card auto-fit, because
   auto-fit strands a lone fifth card on nearly every desktop width. */
.cred-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: clamp(30px, 4vw, 56px);
  padding-top: clamp(34px, 4vw, 52px);
}

/* The manufacturer mark inside a credential card.

   Fixed-height box with the logo contained inside it, because these four
   marks are a diamond, a wide wordmark, an oval and a pentagon -- setting
   `height` on the images directly makes the square ones tiny next to the
   wide one. The box gives them a shared optical baseline instead. */
.cred-card__mark {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  height: 60px;
  margin: 4px 0 20px;
}
.cred-card__mark img {
  max-height: 100%;
  max-width: 150px;
  width: auto;
  object-fit: contain;
  transition: transform var(--transition-med);
}
.cred-card:hover .cred-card__mark img { transform: translateY(-2px); }

/* cert-certainteed.png is the one mark supplied with an opaque white
   background rather than transparency, so on the off-white Elite Recognition
   band it draws a visible white rectangle the other three do not. multiply
   blends white into whatever is behind it and leaves the artwork alone, which
   fixes it in CSS without re-cutting the asset. Selected by filename so it
   applies wherever that logo is used and nowhere else -- if the file is ever
   replaced with a transparent version, delete this rule. */
.cred-card__mark img[src$="cert-certainteed.png"] {
  mix-blend-mode: multiply;
}

/* The one-line boast, set in the brand red so the eye lands on it before
   the paragraph underneath. This is the sentence that does the work. */
.cred-card__claim {
  color: var(--maroon) !important;
  font-weight: 650;
  font-size: 1rem;
  margin: 0 0 12px !important;
}

@media (max-width: 620px) {
  .cred-card__mark { height: 50px; }
}

.home-inline-note {
  margin: clamp(36px, 4vw, 56px) 0 0;
  font-size: 0.98rem;
  color: var(--slate);
}
.home-inline-note a {
  color: var(--maroon);
  font-weight: 600;
  border-bottom: 1px solid rgba(135, 4, 18, 0.25);
}
.home-inline-note a:hover { border-bottom-color: var(--maroon); }

/* ---------- Numbered service list ---------- */
/* Replaces a five-box card grid. Five equal boxes read as a menu; numbered
   rows separated by hairlines read as a body of work. */
.home-list { border-top: 1px solid rgba(30, 21, 23, 0.12); }

.home-list-row {
  display: grid;
  grid-template-columns: minmax(64px, 88px) 1fr auto;
  align-items: baseline;
  gap: 12px;
  padding: clamp(24px, 3vw, 38px) 0;
  border-bottom: 1px solid rgba(30, 21, 23, 0.12);
  transition: background var(--transition-med), padding-left var(--transition-med);
}
.home-list-row:hover {
  background: rgba(30, 21, 23, 0.02);
  padding-left: 14px;
}

.home-list-num {
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--gray);
  font-variant-numeric: tabular-nums;
}

.home-list-body { display: block; }

.home-list-name {
  display: block;
  font-size: clamp(1.2rem, 2.3vw, 1.65rem);
  font-weight: 600;
  letter-spacing: -0.018em;
  line-height: 1.25;
}

.home-list-desc {
  display: block;
  margin-top: 8px;
  color: var(--slate);
  line-height: 1.65;
  max-width: 62ch;
}

.home-list-arrow {
  font-size: 1.2rem;
  color: var(--gray);
  transition: transform var(--transition-med), color var(--transition-med);
}
.home-list-row:hover .home-list-arrow {
  color: var(--maroon);
  transform: translateX(5px);
}

ct rows ---------- */
.home-contact-rows { border-top: 1px solid rgba(30, 21, 23, 0.12); }

.home-contact-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  padding: clamp(20px, 2.6vw, 30px) 0;
  border-bottom: 1px solid rgba(30, 21, 23, 0.12);
  transition: padding-left var(--transition-med);
}
.home-contact-row:hover { padding-left: 12px; }

.home-contact-label {
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--gray);
}

.home-contact-value {
  font-size: clamp(1.05rem, 2vw, 1.4rem);
  font-weight: 600;
  letter-spacing: -0.015em;
}
.home-contact-row:hover .home-contact-value { color: var(--maroon); }

/* ---------- Homepage overrides of shared components ---------- */
/* The shared .eyebrow is centered and copper on most pages; on this page it
   sits left, above a left-aligned title. */
body.home .eyebrow {
  color: var(--slate);
  letter-spacing: 0.14em;
  font-size: 0.72rem;
  font-weight: 600;
  margin-bottom: 0;
}

/* Materials read as quiet type, not filled chips. */
body.home .materials-grid { justify-content: flex-start; }

@media (max-width: 620px) {
  .home-list-row {
    grid-template-columns: 1fr auto;
    row-gap: 6px;
  }
  /* The number moves above the name instead of squeezing a third column. */
  .home-list-num { grid-column: 1 / -1; }
  .home-list-row:hover { padding-left: 0; }
  .home-contact-row:hover { padding-left: 0; }
  .home-creds-row img { height: 26px; }
  /* Keep the block mark at the same ~0.8 ratio it holds on desktop. Without
     this it wins on specificity and GAF becomes the biggest logo in the row. */
  .home-creds-row img.is-block-mark { height: 21px; }
}


/* ---------- Materials page hero: looping roof montage ----------
   A modifier on .page-hero rather than a change to it. .page-hero is shared
   by 38 pages and only the eight materials pages carry a video, so every
   rule below is scoped behind .page-hero--video and the other pages keep
   the flat copper gradient they have now.

   The poster frame is set inline per page as the section's background-image.
   That covers three cases with one asset: the moment before the video has
   buffered, a browser that refuses to autoplay, and a visitor who has asked
   their OS to reduce motion (the video is hidden outright below). */
.page-hero--video {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: 96px 0 88px;
  background-color: var(--charcoal);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}
.page-hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Negative z-index inside the isolated stacking context paints the video
     over the section's own background but under the copy. */
  z-index: -2;
}
.page-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Heavier at the top and bottom than through the middle: the header and
     the CTA keep their contrast without flattening the roof in the centre,
     which is the part worth looking at. */
  background: linear-gradient(
    180deg,
    rgba(30, 21, 23, 0.74) 0%,
    rgba(30, 21, 23, 0.44) 45%,
    rgba(30, 21, 23, 0.76) 100%
  );
}
.page-hero--video h1 { text-shadow: 0 2px 18px rgba(30, 21, 23, 0.55); }
.page-hero--video p.page-hero-sub {
  color: rgba(255, 255, 255, 0.95);
  text-shadow: 0 1px 12px rgba(30, 21, 23, 0.6);
}

@media (max-width: 760px) {
  .page-hero--video { padding: 66px 0 60px; }
}
@media (max-width: 480px) {
  .page-hero--video { padding: 52px 0 46px; }
}

/* Reduced motion gets the poster frame and no movement at all. */
@media (prefers-reduced-motion: reduce) {
  .page-hero__video { display: none; }
}

/* ---------- Homepage hero: looping montage behind the type ----------
   Same modifier pattern as .page-hero--video above, and the same reason for
   it: everything is scoped to .home-hero--video so the plain type-on-white
   hero is one class away if Caleb wants it back.

   The composition from the 2026-08-21 "expensive minimalism" pass is
   deliberately untouched — still left-aligned, still one CTA, still the big
   tightly-tracked headline. Only the ground under it changes. The gold CTA
   already read against dark, so it needs no override. */
.home-hero--video {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background-color: var(--charcoal);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}
.home-hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
}
.home-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Two layers. The vertical one protects the sticky white header at the
     top and the credential strip seam at the bottom. The horizontal one is
     heavier on the left because the type is left-aligned there, which keeps
     the right side of the frame open for the roof itself. */
  background:
    linear-gradient(90deg, rgba(30, 21, 23, 0.78) 0%, rgba(30, 21, 23, 0.52) 52%, rgba(30, 21, 23, 0.34) 100%),
    linear-gradient(180deg, rgba(30, 21, 23, 0.42) 0%, rgba(30, 21, 23, 0.20) 40%, rgba(30, 21, 23, 0.55) 100%);
}
.home-hero--video .home-hero-eyebrow { color: rgba(255, 255, 255, 0.82); }
.home-hero--video .home-hero-title {
  color: var(--white);
  text-shadow: 0 2px 24px rgba(30, 21, 23, 0.5);
}
.home-hero--video .home-hero-sub {
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 1px 14px rgba(30, 21, 23, 0.55);
}
.home-hero--video .home-hero-phone {
  color: rgba(255, 255, 255, 0.9);
  border-bottom-color: rgba(255, 255, 255, 0.4);
}
.home-hero--video .home-hero-phone:hover {
  color: var(--white);
  border-bottom-color: var(--white);
}

/* Narrow screens have no room to keep the right side of the frame open, so
   the horizontal gradient flattens into an even wash. */
@media (max-width: 760px) {
  .home-hero__scrim {
    background: linear-gradient(
      180deg,
      rgba(30, 21, 23, 0.62) 0%,
      rgba(30, 21, 23, 0.42) 45%,
      rgba(30, 21, 23, 0.68) 100%
    );
  }
}

@media (prefers-reduced-motion: reduce) {
  .home-hero__video { display: none; }
}

/* ---------- Button shine on hover ----------
   Ported from the Tethered Crew site so the two properties feel related: a
   skewed highlight band parked off the left edge that sweeps across on hover.

   It hangs off .btn rather than off each variant, so all 107 buttons on the
   site pick it up, including the contact form's submit. Nothing on the site
   already used ::after on a button, so there is nothing to collide with.

   The band passes over the label as well as the fill — that is the effect,
   not a bug. pointer-events: none keeps it from eating the click. */
.btn {
  position: relative;
  /* Clips the band to the button. Does not clip the button's own box-shadow
     or the :focus-visible ring, which are painted outside its border box. */
  overflow: hidden;
}
.btn::after {
  content: "";
  position: absolute;
  top: 0;
  left: -60%;
  width: 35%;
  height: 100%;
  background: linear-gradient(
    115deg,
    transparent,
    rgba(255, 255, 255, 0.5),
    transparent
  );
  transform: skewX(-20deg);
  transition: left 0.65s var(--ease);
  pointer-events: none;
}
.btn:hover::after { left: 130%; }

/* Two variants fill WHITE on hover, so a white band would be invisible on
   exactly the frames it is meant to be seen. They get a dark band instead,
   at a lower alpha because dark-on-white reads much harder than the reverse. */
.btn-outline-light::after,
.page-hero .btn-outline::after {
  background: linear-gradient(
    115deg,
    transparent,
    rgba(30, 21, 23, 0.2),
    transparent
  );
}

@media (prefers-reduced-motion: reduce) {
  .btn::after { display: none; }
}

/* Build credit in the footer. .footer-bottom is a space-between flex row, so
   this becomes a third item and the phone/address line moves to the middle;
   at the mobile breakpoint the row already wraps and all three stack. Dimmer
   than the copyright it sits beside — it is a signature, not a CTA. */
.footer-credit a {
  color: rgba(255, 255, 255, 0.62);
  border-bottom: 1px solid rgba(255, 255, 255, 0.22);
  padding-bottom: 1px;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}
.footer-credit a:hover {
  color: var(--roof-4);
  border-bottom-color: var(--roof-4);
}

/* ==========================================================================
   MOTION — the load intro and the scroll reveals.

   Nothing below is hidden until the inline <head> script sets .js-reveal on
   the document. That ordering is the whole safety net: if the script never
   runs, no rule here matches and the page is an ordinary, fully visible page.
   Hiding by default in the stylesheet would mean a blank site whenever JS
   fails. It is in the head rather than with the other scripts so the hidden
   state is in place before first paint, instead of content appearing and then
   being snatched away.

   The selector list below is mirrored by GROUPS + SOLO in js/motion.js. Keep
   the two in step: CSS decides what starts hidden, motion.js decides what gets
   watched, and an element in one list but not the other either never appears
   or never moves.

   The service-area map is deliberately absent. It is inline SVG that must be
   visible whether or not any script runs, and a reveal starts at opacity:0
   and waits for JS to switch it on -- wired in here, a JS failure would leave
   the map invisible instead of merely un-clickable.
   ========================================================================== */

.js-reveal :is(
  .home-hero-eyebrow, .home-hero-title, .home-hero-sub, .home-hero-actions,
  .page-hero .breadcrumbs, .page-hero .eyebrow, .page-hero h1,
  .page-hero-sub, .page-hero .cta-buttons,
  .home-creds-label, .home-creds-row .cert-logo,
  .home-proof-title, .home-proof-figure, .home-proof-card,
  .areas-grid a, .areas-note, .home-list-row, .mat-card,
  .gallery-item, .home-contact-row,
  .section > .container > .eyebrow, .section-title, .home-section-title,
  .card, .cert-badge, .material-pill, .faq-item, .link-chip,
  .prose > *, .plate, .feature-photo, .map-embed,
  .cta-banner-inner > *,
  .site-footer .footer-brand, .site-footer .footer-col, .footer-bottom > *
):not(.is-in) {
  opacity: 0;
  transform: translateY(18px);
}

.js-reveal .is-in {
  opacity: 1;
  transform: none;
  /* the delay is the stagger, set per element by js/motion.js */
  transition: opacity .82s cubic-bezier(.16, 1, .3, 1) var(--reveal-delay, 0ms),
              transform .82s cubic-bezier(.16, 1, .3, 1) var(--reveal-delay, 0ms);
}

/* ---------- The load intro ----------
   A white sheet with the logo centred on it, which flies to the header. The
   sheet is built by motion.js, so a visitor without JS never has one to get
   stuck behind. */
.intro-veil {
  position: fixed;
  inset: 0;
  z-index: 9999;
  /* No background of its own: the white lives on .intro-veil__sheet below, so
     it can fade without taking the flying logo with it. */
  background: transparent;
  display: grid;
  place-items: center;
  pointer-events: none;      /* never swallow a click, even mid-flight */
}
/* The logo is assembled from four clipped copies of the same bitmap stacked in
   register -- see the long comment above runIntro() in js/motion.js for the
   measured cut lines and why the right-hand one is diagonal.

   The clip lives on the wrapper, and so does the A's transform. That is what
   lets the A scale by itself: the wrapper and its clip scale together as one
   object. Put the transform on the image inside instead and the window holds
   still while the artwork is magnified behind it, which shows a zoomed crop
   of the logo's middle rather than a large A. */
/* The white ground the logo assembles on. A separate layer, not the veil's
   own background, so the fade at the end of the flight clears the sheet while
   the mark stays solid -- see runIntro() in js/motion.js. Absolutely
   positioned so it fills the veil instead of being centred by its grid. */
.intro-veil__sheet {
  position: absolute;
  inset: 0;
  background: var(--white);
  /* Both this and .intro-lockup are positioned, so without explicit levels
     the painting order is decided by DOM order alone -- which works, but
     silently inverts if anyone reorders the two appends in motion.js and
     leaves the sheet covering the logo completely. */
  z-index: 0;
}

.intro-lockup {
  position: relative;
  z-index: 1;
  height: clamp(56px, 9vw, 96px);
  /* 720x193 is the natural size of logo-horizontal.png. The lockup has to
     declare the aspect itself: its children are absolutely positioned, so
     they cannot give it one. */
  aspect-ratio: 720 / 193;
  transform-origin: top left;   /* the FLIP maths in motion.js assumes this */
  will-change: transform;
}
/* The pieces' group. Exists so the lockup can hold a second layer beside them
   and give each its own filter -- see buildLockup() in js/motion.js. */
.il-group { position: absolute; inset: 0; }

/* THE GLOW GOES HERE, on the group, and never on .il-part. The group is four
   clipped pieces of one bitmap; a filter on each piece draws a shadow along
   that piece's own clip edge, which lights up both cut lines and turns an
   invisible seam into a visible one. On the group it applies to the
   composited result, so it follows the logo's outline.

   COLOUR LAYER ONLY. That artwork is maroon and has to cross onto dark video
   as the veil clears; the white copy stacked above it wants the opposite
   treatment and gets it below. Values are deliberately tiny -- a first pass at
   2px and 16px read as a fuzzy halo rather than as separation. */
.intro-lockup .il-group {
  filter: drop-shadow(0 0 1px rgba(255, 255, 255, 0.7))
          drop-shadow(0 0 4px rgba(255, 255, 255, 0.22));
}

/* The white copy of the finished mark, cross-fading in over the coloured
   pieces as the veil clears. Same dark shadow as .home-hero-logo, because by
   the time it is fully up it IS that mark and the hand-off must be seamless. */
.intro-swap {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  filter: drop-shadow(0 3px 16px rgba(30, 21, 23, 0.55));
}

.il-part {
  position: absolute;
  inset: 0;
  /* clip-path is written by motion.js from the measured pixel positions. */
}
.il-part img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  will-change: transform, opacity;
}
/* DOM order is left, right, roof, tagline, so the roof paints last and stays
   on top of the wordmark while it is still oversized. Reorder these and the
   shrinking A passes UNDER the McR and Y as it comes down. */

/* The A's scale lives on the WRAPPER, because the wrapper is what carries the
   clip-path -- transform it and the clipped A scales as one object, whereas
   transforming the img inside would magnify the artwork behind a window that
   stays put. The origin is the roof's own centre in the source bitmap, so the
   A shrinks onto its final position rather than sliding in from the corner of
   the lockup. */
.intro-lockup .il-part--a {
  transform-origin: 73% 42%;
  will-change: transform;
}
/* Holds the real logo back for the first frames, before motion.js has built
   the sheet. From then on motion.js holds it with an inline style, because
   this class comes off partway through the flight so the page can unfold. */
.intro-on .home-hero-logo { opacity: 0; }

/* A page where everything slides is exactly what a vestibular sensitivity
   reacts to. The head script never sets .js-reveal under reduce, so none of
   the above applies; this is the belt to that braces. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal :is(
    .home-hero-eyebrow, .home-hero-title, .home-hero-sub, .home-hero-actions,
    .page-hero .breadcrumbs, .page-hero .eyebrow, .page-hero h1,
    .page-hero-sub, .page-hero .cta-buttons,
    .home-creds-label, .home-creds-row .cert-logo,
    .home-proof-title, .home-proof-figure, .home-proof-card,
    .areas-grid a, .areas-note, .home-list-row, .mat-card,
    .gallery-item, .home-contact-row,
    .section > .container > .eyebrow, .section-title, .home-section-title,
    .card, .cert-badge, .material-pill, .faq-item, .link-chip,
    .prose > *, .plate, .feature-photo, .map-embed,
    .cta-banner-inner > *,
    .site-footer .footer-brand, .site-footer .footer-col, .footer-bottom > *
  ) {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .intro-veil { display: none; }
}


/* ==========================================================================
   ELITE RECOGNITION — the black shelf.

   Caleb asked for the section to go black so it reads as luxurious, and that
   also solves a real problem: the award photographs are lit objects on a
   near-black ground (0-16 at the top, lifting to about 40-64 where the plinth
   catches the light). On the old pale-grey section they would have been five
   dark rectangles with hard edges. On black the frame disappears and only the
   trophy is left, which is the whole effect.

   --footer-black is reused rather than a new value: it already exists for
   exactly this reason, a neutral R=G=B black, because --charcoal is warm and
   goes muddy over a large area.
   ========================================================================== */
.home-proof--dark {
  background: var(--footer-black);
  color: var(--white);
  /* The swoop is absolutely positioned against this. */
  position: relative;
}

/* ---------- Certifications swoop ----------
   The curved top edge of the black shelf. See the markup comment in
   index.html for why it is two paths and how the shared curve works.

   Mirror of .home-swoop rather than a reuse of it: that one is scoped to the
   hero and carries its own geometry, and one class trying to be both edges
   would need every number to become a variable for no gain. */
.cert-swoop {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: clamp(52px, 7vw, 100px);
  /* Ornament only: it sits over the section's top padding, so it must never
     eat a click meant for anything below. */
  pointer-events: none;
  z-index: 1;
}
.cert-swoop svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* MUST MATCH WHATEVER SECTION FOLLOWS THIS ONE. The swoop is the bottom edge
   of the black shelf, so the shape under the band is the next section's
   background curving up to meet it.

   It has already had to move once. When services sat next this was
   --gray-light; services moved below materials on 2026-09-08 and the next
   section became a plain .section on body white, so it is --white now. Get
   this wrong and a band of the wrong colour sits under the curve -- subtle
   enough to miss on a laptop and obvious on a good screen. Reorder the
   homepage and check this line. */
.cert-swoop__ground { fill: var(--white); }
.cert-swoop__band { stroke: none; }

/* The swoop is absolutely positioned and therefore paints above the
   section's static content. The trophy captions clear it at desktop widths
   but not by much, and not at all if the padding is ever reduced, so the
   content is lifted explicitly rather than left to chance. */
.home-proof--dark > .container {
  position: relative;
  z-index: 2;
}

/* Narrow screens: preserveAspectRatio:none steepens the sweep as the box
   narrows, so the height comes down with the width to hold the same gentle
   curve rather than a diagonal wedge. */
@media (max-width: 760px) {
  .cert-swoop { height: clamp(30px, 6vw, 48px); }
}
/* The section's only line of type. Same register as the "Certified & Awarded
   By" label at the top of the page -- 0.14em tracking, 600 weight, small --
   so the page's two credential moments rhyme instead of competing.

   Quiet on purpose. Five trophies with the company's name engraved on them
   are the argument; a headline set over them is the one thing that can make
   them look like they need help. Held at 0.5 alpha rather than full white for
   the same reason: it labels the shelf, it does not announce it. */
/* Tighter than the shared homepage section rhythm (84-150px), because this
   section is now one small label and one row of objects. At the shared
   padding it ran 693px tall for ~380px of content and read as a gap someone
   forgot to fill rather than as deliberate space. Still generous -- the
   trophies want air around them -- just not cavernous.

   The selector has to carry `body.home .section` or it loses: that rule is
   (0,2,1) and a bare .home-proof--dark is (0,1,0), so the shared padding
   silently wins and nothing appears to happen. */
body.home .section.home-proof--dark {
  padding: clamp(66px, 8vw, 104px) 0;
}

.home-proof-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.72rem;
  font-weight: 600;
  text-align: center;
  color: rgba(255, 255, 255, 0.5);
  margin: 0 0 clamp(38px, 5vw, 62px);
}

/* NOTE: .home-proof-title / -lede / -figures / -figure / -num and the
   --dark overrides just below them are no longer used by index.html -- the
   headline, lede and the three big figures came out of this section on
   2026-09-08. Kept rather than deleted because .home-proof-num's gold and the
   figure layout are the only drawing of that pattern on the site and are
   worth having if the numbers come back. .home-inline-note IS still live on
   another page. */
.home-proof--dark .eyebrow { color: var(--roof-3); }
.home-proof--dark .home-proof-title { color: var(--white); }
.home-proof--dark .home-proof-lede { color: rgba(255, 255, 255, 0.72); }
/* Gold, not maroon. The brand red is a dark hue and these numerals are the
   loudest thing in the section -- on black it would read as almost brown. */
.home-proof--dark .home-proof-num { color: var(--roof-3); }
.home-proof--dark .home-proof-label { color: rgba(255, 255, 255, 0.72); }
.home-proof--dark .home-proof-figures { border-bottom-color: rgba(255, 255, 255, 0.14); }
.home-proof--dark .home-inline-note { color: rgba(255, 255, 255, 0.62); }
.home-proof--dark .home-inline-note a {
  color: var(--roof-3);
  border-bottom-color: rgba(218, 152, 79, 0.35);
}

/* ---------- The shelf ---------- */
.award-shelf {
  list-style: none;
  margin: clamp(44px, 5vw, 72px) 0 0;
  padding: 0;
  display: grid;
  /* Five across on a wide screen, and it folds to 3/2/1 on its own. minmax
     with a floor rather than a fixed repeat(5) so a narrow desktop window
     wraps instead of crushing every trophy to a sliver. */
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  gap: clamp(14px, 2vw, 30px);
}

.award__link {
  display: block;
  text-decoration: none;
  color: inherit;
  text-align: center;
  border-radius: 10px;
  padding: 6px 6px 14px;
  transition: transform .35s cubic-bezier(.22,1,.36,1), background-color .35s ease;
}
.award__img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 8px;
}
/* The name is the one piece of type left on the shelf, so it stays quiet:
   small, letterspaced and dim until you go near it. Without it the shelf is
   five unlabelled photographs, which is minimal to the point of being a
   guessing game on a phone where there is no hover to help. */
.award__name {
  display: block;
  margin-top: 10px;
  font-size: 0.72rem;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.55);
  transition: color .25s ease;
}
.award__link:hover,
.award__link:focus-visible {
  transform: translateY(-6px);
  background: rgba(255, 255, 255, 0.04);
}
.award__link:hover .award__name,
.award__link:focus-visible .award__name { color: var(--roof-3); }
.award__link:focus-visible { outline: 2px solid var(--roof-3); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  .award__link { transition: none; }
  .award__link:hover, .award__link:focus-visible { transform: none; }
}


/* ==========================================================================
   CERTIFICATION SPLIT HERO — trophy on the dark half, the award's story on
   the white half.

   The brief was "you don't have to scroll to read it all" on a computer, and
   that it should match the black shelf on the homepage people click through
   from. So the section is sized to the viewport minus the 74px sticky header,
   and the copy column is what has to fit -- not the photograph.

   svh, not vh. On mobile browsers vh is the tallest the viewport ever gets
   (toolbars retracted), so a 100vh panel is taller than the screen actually
   showing and the button at the bottom sits under the URL bar. svh is the
   smallest state, which is the one that has to work.
   ========================================================================== */
.cert-split {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  min-height: calc(100svh - 74px);
  background: var(--white);
}

.cert-split__art {
  background: var(--footer-black);
  display: grid;
  place-items: center;
  padding: clamp(20px, 3vw, 48px);
}
.cert-split__art img {
  display: block;
  width: auto;
  /* Bounded by HEIGHT, not width: these are 1:1.26 portraits, so sizing them
     by column width makes them overflow a short laptop screen vertically. */
  max-height: min(72svh, 620px);
  max-width: 100%;
  height: auto;
}

.cert-split__copy {
  display: grid;
  place-items: center;
  padding: clamp(28px, 4vw, 64px);
}
.cert-split__inner { max-width: 62ch; width: 100%; }

/* .breadcrumbs is white by default -- it was written for the dark .page-hero
   this section replaced. On a white panel that is invisible, so it is
   re-coloured here rather than changed globally, since every other page still
   puts breadcrumbs on the dark hero. */
.cert-split__inner .breadcrumbs { margin-top: 0; color: var(--slate); }
.cert-split__inner .breadcrumbs a { color: var(--maroon); }
.cert-split__inner .breadcrumbs a:hover { color: var(--maroon-dark); }
.cert-split__inner .eyebrow { color: var(--roof-1); }
.cert-split__inner h1 {
  font-size: clamp(1.7rem, 2.9vw, 2.7rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin: 6px 0 12px;
  color: var(--charcoal);
}
/* The claim line, in the brand maroon -- the one piece of colour on the white
   half, so the eye lands on the boast before the explanation. */
.cert-split__sub {
  color: var(--maroon);
  font-weight: 650;
  font-size: clamp(1rem, 1.35vw, 1.12rem);
  line-height: 1.5;
  margin: 0 0 clamp(16px, 2vw, 26px);
}
.cert-split__inner h2 {
  font-size: clamp(1.05rem, 1.4vw, 1.25rem);
  margin: 0 0 8px;
  color: var(--charcoal);
}
.cert-split__inner h3 {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--slate);
  margin: clamp(16px, 2vw, 24px) 0 8px;
}
.cert-split__inner p { color: var(--slate); line-height: 1.65; margin: 0 0 10px; }
/* The global reset is `ul { list-style: none }`, and this list is not inside
   .prose, so without an explicit disc these read as four unmarked lines. */
.cert-split__inner ul { margin: 0; padding-left: 1.15em; list-style: disc; }
.cert-split__inner li { color: var(--slate); line-height: 1.55; margin-bottom: 7px; }
.cert-split__inner li::marker { color: var(--maroon); }
.cert-split__inner .cta-buttons { margin-top: clamp(18px, 2.5vw, 30px); }

/* SHORT SCREENS. A 1280x720 laptop is the case that breaks the promise: the
   copy ran 24px past the fold at the comfortable spacing above. Rather than
   shrink the type for everyone, the padding and the vertical rhythm tighten
   only when the viewport is actually short. Measured, not guessed -- at 720px
   this is what brings the CTA back above the fold. */
@media (min-width: 901px) and (max-height: 800px) {
  .cert-split__copy { padding: 22px clamp(24px, 3vw, 44px); }
  .cert-split__art { padding: 18px; }
  .cert-split__art img { max-height: 60svh; }
  .cert-split__inner h1 { font-size: clamp(1.5rem, 2.2vw, 2rem); margin-bottom: 8px; }
  .cert-split__sub { margin-bottom: 12px; }
  .cert-split__inner h3 { margin-top: 14px; }
  .cert-split__inner li { margin-bottom: 5px; line-height: 1.45; }
  .cert-split__inner .cta-buttons { margin-top: 16px; }
}

/* Under 900px the two halves stop being a split at all -- side by side they
   would each be too narrow to read. Stacked, the trophy leads and the copy
   follows, and the fixed height comes off so nothing is ever clipped. */
@media (max-width: 900px) {
  .cert-split { grid-template-columns: 1fr; min-height: 0; }
  .cert-split__art { padding: clamp(24px, 6vw, 40px); }
  .cert-split__art img { max-height: 46svh; }
}

/* Job photographs on a certification page. Brava's old showcase styled these
   through a .brava-poster-scoped rule that went away with the showcase, so
   they get a small rule of their own here rather than a page-local block. */
.cert-photos {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: clamp(16px, 2.4vw, 30px);
}
.cert-photos img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 10px;
}
.cert-photos span {
  display: block;
  margin-top: 10px;
  text-align: center;
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--slate);
}


/* ==========================================================================
   SERVICE-AREA COUNTY MAP — replaced the spinning 3D globe, 2026-09-08.

   72 towns was too many for a pin each, so the clickable unit is the county.
   The 31 McRay has worked in are filled and interactive; the other 46 are
   drawn flat, because a state map missing two thirds of its counties reads as
   a mistake rather than as a service area.

   The old version cost 670KB of three.js. This is a 16KB inline SVG.
   ========================================================================== */
.okc {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: clamp(20px, 3vw, 48px);
  align-items: center;
  margin-bottom: clamp(28px, 4vw, 48px);
}
.okc-map { min-width: 0; }
.okc-svg { display: block; width: 100%; height: auto; }

.okc-county {
  fill: #ece7e5;
  stroke: var(--white);
  /* non-scaling-stroke keeps every border one hairline no matter how the SVG
     is scaled -- without it the lines thicken on a wide screen and the map
     looks like a woodcut. */
  stroke-width: 1.1;
  vector-effect: non-scaling-stroke;
}
.okc-county.is-served {
  fill: var(--maroon);
  cursor: pointer;
  transition: fill .2s ease;
}
.okc-county.is-served:hover { fill: #A81321; }
.okc-county.is-served.is-active { fill: var(--roof-3); }
/* Chrome draws its default focus ring on an SVG <path> that carries tabindex
   even when the focus came from a mouse click, which put a blue rectangle
   around whichever county you had just clicked. Killed for plain :focus and
   kept for :focus-visible, so a keyboard user still gets a clear indicator
   and a mouse user does not. The selected county is already obvious -- it
   turns gold -- so nothing is lost by removing it for clicks.

   The ring is drawn as a stroke rather than an outline: outline follows the
   path's bounding BOX, so on a county that is not a neat rectangle the ring
   floats well outside the shape it is meant to indicate. */
.okc-county.is-served:focus { outline: none; }
.okc-county.is-served:focus-visible {
  outline: none;
  stroke: var(--charcoal);
  stroke-width: 2.5;
}

.okc-panel {
  border-left: 3px solid var(--maroon);
  padding-left: clamp(14px, 1.6vw, 22px);
  min-width: 0;
}
.okc-panel__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--roof-1);
  margin: 0 0 6px;
}
.okc-panel__count { margin: 0 0 14px; color: var(--slate); font-size: 0.95rem; }
.okc-panel__count strong { font-size: 1.6rem; color: var(--maroon); font-weight: 700; }

.okc-panel__towns {
  list-style: none;
  margin: 0;
  padding: 0;
  /* Oklahoma County alone has 16 towns, so the list scrolls rather than
     stretching the map row to fit its longest county. */
  max-height: 260px;
  overflow-y: auto;
}
/* space-between existed to push the per-town count to the right edge. That
   count is withheld now (see county-map.js), so a lone town name in a
   space-between row is just a left-aligned name -- but the rule is changed
   rather than left in place, because it would silently re-right-align
   anything added beside the name later. */
.okc-panel__towns li {
  display: flex;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid rgba(30, 21, 23, 0.08);
  font-size: 0.92rem;
}
.okc-panel__towns a { color: var(--maroon); font-weight: 600; text-decoration: none; }
.okc-panel__towns a:hover { text-decoration: underline; }
.okc-panel__towns span { color: var(--charcoal); }
/* Kept: nothing renders a <b> in this list any more, but the count is one
   line away in county-map.js if it is ever wanted for a county view rather
   than a town one. */
.okc-panel__towns b { color: var(--gray); font-weight: 600; font-variant-numeric: tabular-nums; }

@media (max-width: 820px) {
  .okc { grid-template-columns: 1fr; }
  .okc-panel { border-left: 0; border-top: 3px solid var(--maroon); padding: 16px 0 0; }
  .okc-panel__towns { max-height: none; }
}

@media (prefers-reduced-motion: reduce) {
  .okc-county.is-served { transition: none; }
}


/* ---------- Homepage Get In Touch: quick form left, details right ----------
   The form leads because it is the thing that actually captures a lead; the
   phone/email/contact-form rows sit beside it for anyone who would rather
   pick up the phone than type. Below 900px the form still comes first, since
   on a phone "tap to call" is already one tap away in the header. */
.home-contact-split {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 64px);
  align-items: start;
}

.home-contact-form {
  /* Slightly warmer than the flat --gray-light the contact page uses, so the
     card reads as a panel on the homepage's near-white background rather
     than as an unstyled block. */
  background: var(--off-white);
  border: 1px solid rgba(30, 21, 23, 0.10);
  box-shadow: 0 18px 40px rgba(30, 21, 23, 0.06);
}
.home-contact-form__title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--charcoal);
}
.home-contact-form button { justify-self: stretch; text-align: center; }

/* The rows were built to span the full container width. Beside the form they
   need to start at its top edge, not float in the middle of the column. */
.home-contact-split .home-contact-rows { margin-top: 0; }

@media (max-width: 900px) {
  .home-contact-split { grid-template-columns: 1fr; }
}


/* ---------- Our Work hero: the house builds itself ----------
   See the markup comment in our-work.html for why the copy sits above the
   artwork rather than over it, and js/work-hero.js for the scrub.

   EVERYTHING TALL IS BEHIND .is-scrub, which only js/work-hero.js adds. The
   resting state below is an ordinary hero -- normal height, nothing sticky,
   showing the finished house. That is what JS-off and reduced-motion get, and
   it is why a failure here can never strand someone in 260vh of blank page. */
.work-hero {
  position: relative;
  background: var(--white);
  padding-top: clamp(26px, 4vw, 52px);
}

.work-hero__stage {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.work-hero__copy {
  flex: none;
  text-align: center;
}
.work-hero__copy .breadcrumbs { justify-content: center; }
.work-hero__copy h1 {
  font-size: clamp(2.2rem, 5.2vw, 3.6rem);
  line-height: 1.05;
  letter-spacing: -0.03em;
  margin: 6px 0 0;
}
/* Sits above the gallery grid, not in the hero -- see the markup comment. */
.work-lede {
  font-size: clamp(0.98rem, 1.3vw, 1.1rem);
  line-height: 1.65;
  color: var(--slate);
  max-width: 62ch;
  margin: 0 0 clamp(30px, 4vw, 48px);
}

/* The artwork. contain, never cover: the frames are on white and the exploded
   roof layers run to the very top edge, so cover crops the very thing the
   animation exists to show. */
.work-hero__frame {
  display: block;
  width: 100%;
  height: auto;
  max-width: 1180px;
  object-fit: contain;
}

.work-hero__hint {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--gray);
  margin: 0;
  /* Only meaningful once the sequence is actually scrubbable. */
  display: none;
}

/* ---- scrubbing (JS present, motion allowed) ---- */

/* 260vh gives the 48 frames about 1.6 viewport-heights of travel after the
   sticky screen is subtracted -- enough that the build reads as a sequence
   rather than a flicker, short enough that nobody feels trapped in it. */
.work-hero.is-scrub {
  height: 260vh;
  padding-top: 0;
}
.work-hero.is-scrub .work-hero__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  /* The sticky header is translucent glass and overlaps this, so the copy
     needs its own clearance rather than relying on document flow. */
  padding: clamp(78px, 9vh, 100px) 0 clamp(16px, 2.4vh, 28px);
  overflow: hidden;
}
/* flex: 1 with min-height: 0 is what lets the artwork take the leftover
   height and shrink properly inside a flex column -- without min-height the
   image's intrinsic height wins and pushes the hint off the bottom. */
/* max-width comes OFF while scrubbing. With it on, 1180px was the binding
   constraint and the artwork rendered 542px tall inside a box that had room
   for more -- and the finished house only occupies 66% of the frame's height
   (the exploded frames need the rest), so every pixel lost here costs the
   house nearly one and a half. Height drives it instead. */
.work-hero.is-scrub .work-hero__frame {
  flex: 1 1 auto;
  min-height: 0;
  height: 100%;
  max-width: none;
}
.work-hero.is-scrub .work-hero__hint {
  display: block;
  margin-top: clamp(10px, 1.6vh, 20px);
  /* --scrub is written by work-hero.js each frame. x4 so the hint is gone
     within the first quarter of the travel, once its job is done. opacity
     clamps to 0 on its own, so no min() is needed. */
  opacity: calc(1 - var(--scrub, 0) * 4);
}

/* Narrow screens. The artwork is 1.78:1, so on a 390px-wide phone it can only
   ever be ~220px tall -- inside a 100vh sticky stage that left the house
   marooned in about 400px of empty white. So the stage sizes to its content
   here instead and simply sticks at the top while the page scrolls past it.
   A sticky box shorter than the viewport is fine when the section behind it
   is the same white as the page: there is nothing to see in the gap. */
@media (max-width: 760px) {
  .work-hero.is-scrub { height: 200vh; }
  .work-hero.is-scrub .work-hero__stage {
    height: auto;
    padding: clamp(62px, 8vh, 84px) 0 clamp(12px, 2vh, 20px);
  }
  .work-hero.is-scrub .work-hero__frame {
    flex: none;
    height: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .work-hero.is-scrub { height: auto; padding-top: clamp(26px, 4vw, 52px); }
  .work-hero.is-scrub .work-hero__stage { position: static; height: auto; }
  .work-hero.is-scrub .work-hero__hint { display: none; }
}


/* ---------- Homepage services: five photo cards ----------
   Five across on desktop, because there are exactly five services and an
   auto-fit grid would orphan one on most widths. It steps to 3 then 2 rather
   than shrinking five columns into slivers. */
.svc-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(10px, 1.4vw, 20px);
}

.svc-card {
  display: block;
  text-decoration: none;
  color: inherit;
  min-width: 0;
}
.svc-card__media {
  display: block;
  position: relative;
  overflow: hidden;
  border-radius: 12px;
  background: var(--gray-light);
  margin-bottom: 14px;
}
.svc-card__media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  transition: transform .5s cubic-bezier(.22,1,.36,1), filter .4s ease;
}

/* Grey until you point at it. Guarded by (hover: hover) because a touch
   device never fires hover -- ungated, every card on a phone would stay
   permanently grey with no way for the visitor to reveal the photograph. */
@media (hover: hover) {
  .svc-card__media img { filter: grayscale(1) contrast(1.02); }
  .svc-card:hover .svc-card__media img,
  .svc-card:focus-visible .svc-card__media img {
    filter: grayscale(0);
    transform: scale(1.04);
  }
}
/* Keyboard users get the same reveal on every device, hover or not. */
.svc-card:focus-visible .svc-card__media img { filter: grayscale(0); }
.svc-card:focus-visible { outline: 2px solid var(--maroon); outline-offset: 4px; border-radius: 12px; }

.svc-card__num {
  display: block;
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  color: var(--gray);
  font-weight: 700;
  margin-bottom: 4px;
}
.svc-card__name {
  display: block;
  font-weight: 700;
  font-size: clamp(0.95rem, 1.05vw, 1.06rem);
  line-height: 1.25;
  color: var(--charcoal);
  margin-bottom: 5px;
  transition: color .2s ease;
}
.svc-card:hover .svc-card__name { color: var(--maroon); }
.svc-card__desc {
  display: block;
  font-size: 0.86rem;
  line-height: 1.45;
  color: var(--slate);
}

@media (max-width: 1080px) {
  .svc-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 700px) {
  .svc-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (prefers-reduced-motion: reduce) {
  .svc-card__media img { transition: filter .4s ease; }
  .svc-card:hover .svc-card__media img { transform: none; }
}


/* ---------- Material type catalogue (homepage) ----------
   Seven types, each a square close-up of the material with its name under it.
   Square on purpose: the subject is a SURFACE, not a scene, so a 1:1 crop
   gives the texture the most pixels. The services row above uses 4:5 because
   those photographs are of people working.

   auto-fit rather than a fixed five or four across. Seven does not divide
   evenly into any sensible column count, so a fixed grid leaves an orphan on
   most widths; auto-fit lets the last row be short and tidy instead. */
/* THREE ACROSS, not auto-fit. Nine tiles divide evenly by three, so the grid
   is pinned rather than left to flow -- auto-fit at this min-width would give
   four across on a wide screen and leave an orphan on the last row, which is
   the exact thing the ninth tile was added to avoid.

   Three columns also lets the tiles run bigger than the old four-across did,
   which suits close-ups of a surface. */
.mat-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(14px, 1.8vw, 26px);
}
@media (max-width: 900px) { .mat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .mat-grid { grid-template-columns: 1fr; } }

.mat-card {
  display: block;
  text-decoration: none;
  color: inherit;
  min-width: 0;
}

.mat-card__media {
  display: block;
  overflow: hidden;
  border-radius: 12px;
  background: var(--gray-light);
  margin-bottom: 12px;
}
.mat-card__media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  transition: transform .5s cubic-bezier(.22,1,.36,1);
}

/* NO greyscale here, unlike the service cards. Those are photographs of work
   and the desaturation is a styling choice; these are the product, and a
   homeowner is looking at them to judge colour and texture. Hiding that until
   hover would defeat the catalogue. Hover just pushes in slightly. */
@media (hover: hover) {
  .mat-card:hover .mat-card__media img { transform: scale(1.05); }
}
.mat-card:focus-visible { outline: 2px solid var(--maroon); outline-offset: 4px; border-radius: 12px; }
.mat-card:focus-visible .mat-card__media img { transform: scale(1.05); }

.mat-card__name {
  display: block;
  font-weight: 700;
  font-size: clamp(0.95rem, 1.05vw, 1.06rem);
  letter-spacing: -0.012em;
  margin-bottom: 3px;
}
.mat-card__desc {
  display: block;
  color: var(--slate);
  font-size: 0.86rem;
  line-height: 1.5;
}


/* ---------- Social links under the contact details ---------- */
.social-row {
  display: flex;
  gap: 14px;
  margin-top: clamp(22px, 3vw, 34px);
}
.social-link {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px solid rgba(30, 21, 23, 0.14);
  color: var(--slate);
  transition: color .2s ease, border-color .2s ease, background-color .2s ease, transform .2s ease;
}
/* fill:currentColor is what lets one hover rule recolour the whole glyph. */
.social-link svg { fill: currentColor; display: block; }
.social-link:hover {
  color: var(--white);
  background: var(--maroon);
  border-color: var(--maroon);
  transform: translateY(-2px);
}
.social-link:focus-visible {
  outline: 2px solid var(--maroon);
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .social-link { transition: color .2s ease, background-color .2s ease; }
  .social-link:hover { transform: none; }
}


/* ---------- "Made by" strip on the material pages ----------
   Which manufacturers make this material, as their own logos, each one a link
   to the certification McRay holds from them. The logo is the claim; the link
   is the proof.

   DELIBERATELY QUIET. This was bordered cards with a heading, a lede and the
   certification title printed under each logo, and it read as a component
   rather than a note. What the certification IS lives in aria-label and title
   now: announced, and there on hover, without a second line of type under
   every mark.

   Only brands McRay is certified with ever appear -- a material with no
   certified maker gets no strip at all rather than logos we have no standing
   to show. See BRANDS in build-material-pages.py. */
.brand-band {
  padding: clamp(20px, 2.4vw, 30px) 0;
  border-bottom: 1px solid rgba(30, 21, 23, 0.08);
}
.brand-band__label {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.66rem;
  font-weight: 600;
  color: var(--gray);
  text-align: center;
  margin: 0 0 14px;
}

.brand-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(26px, 4vw, 52px);
}

.brand-mark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: var(--slate);
  font-weight: 600;
  font-size: 0.92rem;
  transition: color var(--transition-fast), opacity var(--transition-fast);
}
/* The logos are different shapes -- a square block, a wide wordmark, an oval
   -- so cap both axes and let each sit at its own optical size, the same way
   the homepage credential strip does. */
.brand-mark img {
  display: block;
  height: 30px;
  max-width: 110px;
  width: auto;
  object-fit: contain;
}
.brand-mark:hover { color: var(--maroon); }
.brand-mark:focus-visible { outline: 2px solid var(--maroon); outline-offset: 4px; border-radius: 4px; }


/* ---------- Drone band: footage under a hero that is not a video ----------
   The certification pages (trophy split hero) and Our Work (scroll-built
   house) already have a hero doing a job a video would fight, so their drone
   footage sits in a full-bleed band directly under it instead. The poster is
   the section's own background, which covers JS off, reduced motion and the
   moment before the video arrives. js/script.js only fetches the mp4 as the
   band nears the screen, so a visitor who never scrolls pays nothing. */
.drone-band {
  position: relative;
  overflow: hidden;
  height: clamp(240px, 42vw, 560px);
  background-color: var(--charcoal);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}
.drone-band__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.drone-band__label {
  position: absolute;
  left: 24px;
  bottom: 18px;
  margin: 0;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(30, 21, 23, 0.55);
  color: var(--white);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
@media (max-width: 480px) {
  .drone-band__label { left: 16px; bottom: 12px; font-size: 0.7rem; }
}
@media (prefers-reduced-motion: reduce) {
  .drone-band__video { display: none; }
}

/* ---------- Generated blog (tools/build-blog.py) ----------------------------
   Everything here is .bl-* so the hand-built pages are untouched. Cards are
   separated by a hairline and spacing, not shadows: nothing on a listing
   floats, so nothing should look like it does. */
.bl-topics {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 40px;
  padding-bottom: 20px;
  border-bottom: 1px solid #e6dfdd;
}
.bl-topic {
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--slate);
  padding: 7px 14px;
  border: 1px solid #ddd3d1;
  border-radius: 999px;
  text-decoration: none;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}
.bl-topic:hover { border-color: var(--maroon); color: var(--maroon); }
.bl-topic.is-current { background: var(--charcoal); border-color: var(--charcoal); color: var(--white); }

.bl-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 44px 32px;
}
.bl-card { display: flex; flex-direction: column; min-width: 0; }
.bl-card__media {
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 6px;
  background: #e9e3e1;
  margin-bottom: 16px;
}
.bl-card__media img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.5s ease; }
.bl-card:hover .bl-card__media img { transform: scale(1.03); }
.bl-card__meta { font-size: 0.78rem; color: var(--gray); margin: 0 0 8px; letter-spacing: 0.02em; }
.bl-card__meta a { color: var(--maroon); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; }
.bl-card h3 { font-size: 1.14rem; line-height: 1.35; margin: 0 0 8px; }
.bl-card h3 a { color: var(--charcoal); text-decoration: none; }
.bl-card h3 a:hover { color: var(--maroon); }
.bl-card p { margin: 0; color: var(--slate); font-size: 0.93rem; line-height: 1.55; }

.bl-pager {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 24px;
  margin-top: 56px;
  font-size: 0.92rem;
  color: var(--gray);
}
.bl-pager a { color: var(--maroon); font-weight: 700; }

.bl-article-wrap { padding-top: 56px; }
.bl-article { max-width: 760px; }
.bl-meta { font-size: 0.86rem; color: var(--gray); margin: 0 0 28px; }
.bl-meta a { color: var(--maroon); font-weight: 700; }
.bl-toc {
  border: 1px solid #e6dfdd;
  border-radius: 6px;
  padding: 16px 22px;
  margin: 0 0 36px;
  background: var(--off-white);
}
.bl-toc summary { font-weight: 700; cursor: pointer; color: var(--charcoal); }
.bl-toc ol { margin: 12px 0 0; padding-left: 20px; }
.bl-toc li { margin: 6px 0; font-size: 0.93rem; }
.bl-toc a { color: var(--slate); }
.bl-toc a:hover { color: var(--maroon); }
.bl-body { max-width: none; }
.bl-body h2 { margin: 40px 0 14px; scroll-margin-top: 110px; }
.bl-body ol { margin: 0 0 16px; padding-left: 22px; list-style: decimal; }
.bl-body a { color: var(--maroon); text-decoration: underline; text-underline-offset: 2px; }
.bl-body strong { color: var(--charcoal); }
.bl-body img { max-width: 100%; height: auto; border-radius: 6px; }
.bl-body table { width: 100%; border-collapse: collapse; margin: 0 0 20px; font-size: 0.93rem; }
.bl-body th, .bl-body td { border-bottom: 1px solid #e6dfdd; padding: 10px 8px; text-align: left; vertical-align: top; }
.bl-body th { color: var(--charcoal); }
.bl-faq details { border-bottom: 1px solid #e6dfdd; padding: 14px 0; }
.bl-faq summary { font-weight: 700; cursor: pointer; color: var(--charcoal); }
.bl-faq p { margin: 10px 0 0; }
.bl-byline {
  display: flex;
  gap: 16px;
  align-items: center;
  margin-top: 48px;
  padding-top: 24px;
  border-top: 1px solid #e6dfdd;
}
.bl-byline img { border-radius: 50%; flex: none; }
.bl-byline p { margin: 0; font-size: 0.9rem; color: var(--slate); }
.bl-byline a { color: var(--maroon); font-weight: 600; }
.bl-areas-label { margin-top: 64px; }
@media (max-width: 600px) {
  .bl-grid { gap: 36px; }
  .bl-topics { flex-wrap: nowrap; overflow-x: auto; margin-left: -16px; margin-right: -16px; padding: 0 16px 16px; }
  .bl-topic { flex: none; }
}

/* ---------- Job map (js/job-map.js, tools/build-job-map.py) ---------------
   The tab replaces the RealWork Labs plugin on the WordPress site. Brand
   maroon rather than their green. Map tiles are greyed so the only colour
   on the map is the work itself. */
.jm-tiles { filter: grayscale(1) contrast(0.92) brightness(1.04); }
.jm-count { max-width: 64ch; margin: -8px auto 28px; text-align: center; color: var(--slate); }
.jm-count strong { color: var(--charcoal); }
.jm-map {
  height: 460px;
  border: 1px solid #e0d7d5;
  border-radius: 6px;
  background: #ece8e7;
  z-index: 0;            /* keep Leaflet's panes under the sticky header */
}
.jm-map.is-failed::after {
  content: "The map could not load.";
  display: grid;
  place-items: center;
  height: 100%;
  color: var(--gray);
}
.leaflet-container { font-family: var(--font); }

.jm-tab {
  position: fixed;
  left: 0;
  top: 50%;
  z-index: 900;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px 10px;
  border: 0;
  border-radius: 0 6px 6px 0;
  background: var(--maroon);
  color: var(--white);
  font: 700 0.78rem/1 var(--font);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  writing-mode: vertical-rl;
  /* No rotate(180deg) here. It used to flip the label to read upward, but it
     flipped the whole button with it: the rounded corners ended up against
     the screen edge, the square side faced the page, and the hover grew the
     tab into the edge instead of out from it. Unrotated, the label reads
     top to bottom and the tab's shape faces the page. */
  transform: translateY(-50%);
  cursor: pointer;
  box-shadow: 4px 0 18px rgba(30, 21, 23, 0.22);
  transition: padding 0.18s ease, background 0.18s ease, transform 0.26s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.jm-tab:hover, .jm-tab:focus-visible { background: var(--maroon-dark); padding-right: 14px; }
/* The hover pull (js/job-map.js, peek): the drawer's edge comes out this far
   and the tab moves with it. While peeking the drawer takes no clicks and
   shows no backdrop, so the page underneath still works. */
.jm-tab.is-peek { transform: translateY(-50%) translateX(40px); z-index: 1001; }
/* The tab slides out from under the cursor that is hovering it. This invisible
   strip fills the gap it leaves, so the cursor is still "on the tab" and the
   pull does not snap back and start over. */
.jm-tab.is-peek::before { content: ""; position: absolute; top: 0; bottom: 0; right: 100%; width: 40px; }
.jm-drawer.is-armed { pointer-events: none; }
.jm-drawer.is-peek .jm-drawer__panel { transform: translateX(calc(-100% + 40px)); overflow: hidden; }
.jm-tab:focus-visible { outline: 3px solid var(--roof-3); outline-offset: 2px; }
.jm-tab__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--roof-4);
  box-shadow: 0 0 0 3px rgba(251, 168, 107, 0.3);
}
@media (prefers-reduced-motion: no-preference) {
  .jm-tab__dot { animation: jm-pulse 2.4s ease-in-out infinite; }
}
@keyframes jm-pulse { 50% { box-shadow: 0 0 0 6px rgba(251, 168, 107, 0); } }
@media (max-width: 760px) {
  /* On a phone a mid-screen edge tab sits on top of body text. A small pill
     in the bottom-left corner stays out of the reading line. */
  .jm-tab {
    top: auto;
    bottom: 16px;
    left: 16px;
    writing-mode: horizontal-tb;
    transform: none;
    border-radius: 999px;
    padding: 11px 16px;
  }
  .jm-tab:hover, .jm-tab:focus-visible { padding-right: 16px; }
}

.jm-drawer { position: fixed; inset: 0; z-index: 1000; }
.jm-drawer__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(18, 18, 18, 0.45);
  opacity: 0;
  transition: opacity 0.25s ease;
}
.jm-drawer__panel {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: 1;
  width: min(560px, 100vw);
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 24px;
  background: var(--off-white);
  box-shadow: 8px 0 40px rgba(18, 18, 18, 0.25);
  transform: translateX(-100%);
  transition: transform 0.26s cubic-bezier(0.2, 0.8, 0.2, 1);
  overflow-y: auto;
}
.jm-drawer.is-open .jm-drawer__panel { transform: none; }
.jm-drawer.is-open .jm-drawer__backdrop { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .jm-drawer__panel, .jm-drawer__backdrop { transition: none; }
}
.jm-open, .jm-open body { overflow: hidden; }
.jm-drawer__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
.jm-drawer__eyebrow {
  margin: 0 0 4px;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--maroon);
}
.jm-drawer__title { margin: 0; font-size: 1.5rem; }
.jm-drawer__close {
  flex: none;
  width: 40px;
  height: 40px;
  border: 1px solid #ddd3d1;
  border-radius: 50%;
  background: var(--white);
  font-size: 1.5rem;
  line-height: 1;
  color: var(--charcoal);
  cursor: pointer;
}
.jm-drawer__find { display: grid; gap: 6px; font-size: 0.85rem; font-weight: 600; color: var(--slate); }
.jm-drawer__select {
  font: inherit;
  font-size: 1rem;
  font-weight: 400;
  padding: 10px 12px;
  border: 1px solid #cfc4c2;
  border-radius: 6px;
  background: var(--white);
  color: var(--charcoal);
}
.jm-drawer__stat { margin: 0; color: var(--slate); }
.jm-drawer__stat strong { color: var(--maroon); font-size: 1.15rem; }
.jm-drawer__map {
  flex: 1 1 auto;
  min-height: 320px;
  border: 1px solid #e0d7d5;
  border-radius: 6px;
  background: #ece8e7;
}
.jm-drawer__note { margin: 0; font-size: 0.8rem; color: var(--gray); }
.jm-drawer__link { align-self: flex-start; }
@media (max-width: 480px) {
  .jm-drawer__panel { padding: 18px 16px; }
  .jm-map { height: 360px; }
}

/* ---------- Roof of the Week (tools/roof-of-the-week.py) ------------------ */
.rw-entry { max-width: 1040px; }
.rw-latest-title { font-size: clamp(1.6rem, 3vw, 2.2rem); margin: 0 0 22px; }
.rw-latest-title a { color: var(--charcoal); }
.rw-latest-title a:hover { color: var(--maroon); }
.rw-video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--charcoal);
  border-radius: 6px;
}
.rw-facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin: 24px 0 8px;
  border-top: 1px solid #e6dfdd;
  border-bottom: 1px solid #e6dfdd;
}
.rw-facts div { padding: 14px 16px 14px 0; }
.rw-facts div + div { padding-left: 16px; border-left: 1px solid #e6dfdd; }
.rw-facts dt { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--gray); }
.rw-facts dd { margin: 4px 0 0; font-weight: 600; color: var(--charcoal); }
.rw-facts a { color: var(--maroon); }
.rw-story { margin: 28px 0; max-width: 720px; }
.rw-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
}
.rw-gallery__item { display: block; aspect-ratio: 4 / 3; overflow: hidden; border-radius: 4px; background: #e9e3e1; }
.rw-gallery__item img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.4s ease; }
.rw-gallery__item:hover img { transform: scale(1.04); }
.rw-pager { display: flex; justify-content: space-between; margin-top: 40px; font-weight: 700; }
.rw-pager a { color: var(--maroon); }
.rw-map { height: 520px; }
.rw-map-note { text-align: center; color: var(--gray); font-size: 0.82rem; margin: 12px 0 0; }
.rw-tour { display: block; margin: 18px auto 0; }
.rw-pin span {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--maroon);
  border: 2px solid var(--white);
  color: var(--white);
  font: 700 0.8rem/1 var(--font);
  box-shadow: 0 2px 8px rgba(18, 18, 18, 0.35);
}
.rw-pin--drop span { animation: rw-drop 0.45s cubic-bezier(0.2, 1.4, 0.4, 1) both; }
@keyframes rw-drop { from { transform: translateY(-24px) scale(0.6); opacity: 0; } }
.rw-pop { display: grid; gap: 4px; color: var(--charcoal); text-decoration: none; }
.rw-pop img { width: 100%; height: auto; border-radius: 4px; margin-bottom: 4px; }
.rw-pop__n { font-size: 0.72rem; color: var(--gray); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; }
.rw-pop strong { font-size: 0.95rem; }
@media (max-width: 760px) {
  .rw-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .rw-facts div:nth-child(3) { padding-left: 0; border-left: 0; }
  .rw-facts div:nth-child(n+3) { border-top: 1px solid #e6dfdd; }
  .rw-map { height: 400px; }
}


/* ==========================================================================
   WHERE WE WORK — the live map explorer on the homepage (#mw), the project
   cards and the project viewer. js/job-map.js fills all of it from the CRM
   feed; the markup between the generated markers in index.html is what shows
   before that, and it already has the real totals and every town.

   One radius (6px) and hairline borders throughout. Nothing here casts a
   shadow except the project viewer, which is the only thing that floats.
   ========================================================================== */
.mw {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: clamp(20px, 3vw, 44px);
  align-items: stretch;
  margin-bottom: clamp(28px, 4vw, 48px);
}
.mw-map {
  min-width: 0;
  height: clamp(380px, 46vw, 540px);
  border: 1px solid #e0d7d5;
  border-radius: 6px;
  background: #ece8e7;      /* holds the space, and the tone, until the tiles arrive */
  z-index: 0;               /* keeps Leaflet's own stacking below the sticky header */
}
.mw-side { display: flex; flex-direction: column; min-width: 0; max-height: clamp(380px, 46vw, 540px); }
.mw-side__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--roof-1);
  margin: 0 0 6px;
}
.mw-side__count { margin: 0 0 12px; color: var(--slate); font-size: 0.95rem; }
.mw-side__count strong { font-size: 1.9rem; color: var(--maroon); font-weight: 700; font-variant-numeric: tabular-nums; }
.mw-side__reset {
  align-self: flex-start;
  margin: 0 0 10px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--roof-1);
  text-decoration: underline;
  cursor: pointer;
}
.mw-towns {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  border-top: 1px solid rgba(30, 21, 23, 0.1);
}
.mw-towns li { border-bottom: 1px solid rgba(30, 21, 23, 0.08); }
.mw-towns a,
.mw-towns button {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  width: 100%;
  padding: 9px 8px 9px 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.95rem;
  text-align: left;
  color: var(--charcoal);
  text-decoration: none;
  cursor: pointer;
}
.mw-towns span { font-weight: 600; }
.mw-towns b { color: var(--slate); font-weight: 500; font-variant-numeric: tabular-nums; }
.mw-towns a:hover span,
.mw-towns button:hover span { color: var(--maroon); }
.mw-towns button.is-current span,
.mw-towns button.is-current b { color: var(--maroon); }
.mw-towns a:focus-visible,
.mw-towns button:focus-visible,
.mw-side__reset:focus-visible { outline: 2px solid var(--maroon); outline-offset: 2px; }
.mw-side__link { align-self: flex-start; color: var(--maroon); font-weight: 700; text-decoration: none; border-bottom: 2px solid currentColor; padding-bottom: 2px; }
.mw-side__link:hover { color: var(--maroon-dark); }

/* Map markers. A bubble is a town's count; a pin is a published project. */
/* Leaflet's own stylesheet arrives later and sets display:block on every
   marker, so these two need the extra class to keep their layout. */
.leaflet-marker-icon.mw-bubble {
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--maroon);
  border: 2px solid var(--white);
  color: var(--white);
  font: 700 0.78rem/1 var(--font);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.mw-bubble:hover, .mw-bubble:focus-visible { background: var(--maroon-dark); outline: none; }
.mw-bubble:focus-visible { border-color: var(--roof-4); }
.leaflet-marker-icon.mw-pin {
  border-radius: 50%;
  background: var(--roof-3);
  border: 3px solid var(--white);
  outline: 2px solid var(--roof-1);
  cursor: pointer;
}
.mw-pin:hover, .mw-pin:focus-visible { background: var(--roof-4); }

.mw-projects { margin-bottom: clamp(28px, 4vw, 48px); }
.mw-projects__title { font-size: clamp(1.25rem, 2.2vw, 1.6rem); margin: 0 0 18px; }
.mw-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 20px; }
.mw-card {
  display: flex;
  flex-direction: column;
  padding: 0;
  border: 1px solid #e0d7d5;
  border-radius: 6px;
  background: var(--white);
  font: inherit;
  text-align: left;
  color: var(--charcoal);
  cursor: pointer;
  overflow: hidden;
}
.mw-card:hover { border-color: var(--maroon); }
.mw-card:focus-visible { outline: 2px solid var(--maroon); outline-offset: 2px; }
.mw-card__media { position: relative; display: block; aspect-ratio: 4 / 3; background: #ece8e7; }
.mw-card__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mw-card__blank { display: block; width: 100%; height: 100%; }
.mw-card__n {
  position: absolute;
  right: 8px;
  bottom: 8px;
  padding: 3px 8px;
  border-radius: 6px;
  background: rgba(18, 18, 18, 0.72);
  color: var(--white);
  font-size: 0.75rem;
  font-weight: 600;
}
.mw-card__body { display: flex; flex-direction: column; gap: 4px; padding: 14px 16px 16px; }
.mw-card__meta { font-size: 0.8rem; color: var(--slate); }
.mw-card__title { font-size: 1.05rem; font-weight: 700; line-height: 1.25; }
.mw-card__desc {
  font-size: 0.9rem;
  color: var(--slate);
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.mw-card--compact { flex-direction: row; align-items: stretch; }
.mw-card--compact .mw-card__media { flex: 0 0 96px; aspect-ratio: 1; }
.mw-card--compact .mw-card__body { padding: 10px 12px; justify-content: center; }
.mw-card--compact .mw-card__title { font-size: 0.95rem; }

.jm-drawer__projects h3 { margin: 4px 0 10px; font-size: 1rem; }
.jm-drawer__cards { display: grid; gap: 10px; }

/* The project viewer: photos on the left, the write-up on the right. */
.mw-viewing, .mw-viewing body { overflow: hidden; }
.mw-viewer {
  width: min(1080px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px);
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: var(--off-white);
  color: var(--charcoal);
  box-shadow: 0 24px 80px rgba(18, 18, 18, 0.45);
  overflow: hidden;
}
.mw-viewer[open] { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(280px, 1fr); }
.mw-viewer::backdrop { background: rgba(18, 18, 18, 0.7); }
.mw-viewer__stage { position: relative; background: #121212; display: grid; place-items: center; min-height: 320px; }
.mw-viewer:not(.has-photos) .mw-viewer__stage { display: none; }
.mw-viewer:not(.has-photos)[open] { grid-template-columns: 1fr; width: min(560px, calc(100vw - 32px)); }
.mw-viewer__img { display: block; max-width: 100%; max-height: calc(100dvh - 32px); width: auto; height: auto; object-fit: contain; }
.mw-viewer__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.9);
  color: var(--charcoal);
  font-size: 1.7rem;
  line-height: 1;
  cursor: pointer;
}
.mw-viewer__prev { left: 12px; }
.mw-viewer__next { right: 12px; }
.mw-viewer:not(.has-many) .mw-viewer__nav { display: none; }
.mw-viewer__pos { position: absolute; left: 12px; bottom: 10px; margin: 0; padding: 3px 8px; border-radius: 6px; background: rgba(18, 18, 18, 0.72); color: var(--white); font-size: 0.78rem; }
.mw-viewer__pos:empty { display: none; }
.mw-viewer__text { position: relative; display: flex; flex-direction: column; gap: 10px; padding: 28px 24px 24px; overflow-y: auto; max-height: calc(100dvh - 32px); }
.mw-viewer__close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 40px;
  height: 40px;
  border: 1px solid #ddd3d1;
  border-radius: 50%;
  background: var(--white);
  font-size: 1.5rem;
  line-height: 1;
  color: var(--charcoal);
  cursor: pointer;
}
.mw-viewer__meta { margin: 0; padding-right: 48px; font-size: 0.82rem; color: var(--slate); }
.mw-viewer__title { margin: 0; font-size: clamp(1.3rem, 2.4vw, 1.7rem); line-height: 1.2; }
.mw-viewer__desc { margin: 0; color: var(--slate); line-height: 1.6; white-space: pre-line; }
.mw-viewer__desc:empty { display: none; }
.mw-viewer__thumbs { display: grid; grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: 6px; }
.mw-viewer__thumbs button { padding: 0; border: 2px solid transparent; border-radius: 6px; background: none; cursor: pointer; overflow: hidden; aspect-ratio: 1; }
.mw-viewer__thumbs button.is-current { border-color: var(--maroon); }
.mw-viewer__thumbs img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mw-viewer__cta { align-self: flex-start; margin-top: 6px; }
.mw-viewer__town { color: var(--roof-1); font-weight: 600; font-size: 0.92rem; }
.mw-viewer button:focus-visible, .mw-viewer a:focus-visible { outline: 2px solid var(--maroon); outline-offset: 2px; }

/* Reviews: a row that scrolls sideways, so twelve of them cost one row of
   height. Laid out as Google lays a review out, with Google's own star
   yellow, since these are Google reviews and say so. */
.mw-stars { color: #FBBC04; letter-spacing: 0.04em; white-space: nowrap; font-size: 1.05rem; line-height: 1; }
.mw-stars i { font-style: normal; color: #DADCE0; }
.mw-g { width: 20px; height: 20px; flex: none; }
.mw-avatar {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: #fff;
  font-weight: 500;
  font-size: 1.1rem;
}
.mw-reviews { margin-bottom: clamp(28px, 4vw, 48px); }
.mw-reviews__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 20px; margin-bottom: 18px; }
.mw-reviews__head .mw-projects__title { margin: 0; }
.mw-reviews__sum { margin: 0; display: flex; align-items: center; gap: 8px; color: var(--slate); font-size: 0.95rem; }
.mw-reviews__sum strong { color: var(--charcoal); font-size: 1.25rem; font-weight: 600; }
.mw-reviews__sum a { color: #1A73E8; }
.mw-reviews__row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(270px, 340px);
  gap: 16px;
  overflow-x: auto;
  padding-bottom: 10px;
  scroll-snap-type: x proximity;
}
.mw-reviews__row:focus-visible { outline: 2px solid var(--maroon); outline-offset: 4px; }
.mw-review {
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
  border: 1px solid #DADCE0;
  border-radius: 8px;
  background: var(--white);
}
.mw-review__head { display: flex; align-items: center; gap: 12px; }
.mw-review__id { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; line-height: 1.3; }
.mw-review__id strong { color: #202124; font-weight: 500; }
.mw-review__id span { color: #70757A; font-size: 0.82rem; }
.mw-review__rating { display: flex; align-items: center; gap: 8px; color: #70757A; font-size: 0.85rem; }
.mw-review__text {
  margin: 0;
  color: #3C4043;
  font-size: 0.93rem;
  line-height: 1.55;
  display: -webkit-box;
  -webkit-line-clamp: 6;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.mw-review__reply {
  margin: 0;
  padding-left: 12px;
  border-left: 2px solid #DADCE0;
  color: #5F6368;
  font-size: 0.85rem;
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.mw-review__reply strong { display: block; color: #3C4043; font-weight: 500; }
.mw-review__project { align-self: flex-start; margin-top: auto; padding: 0; border: 0; background: none; font: inherit; font-size: 0.88rem; font-weight: 600; color: var(--maroon); text-decoration: underline; cursor: pointer; }
.mw-viewer__review { border-top: 1px solid #e0d7d5; padding-top: 12px; }
.mw-viewer__review-label { margin: 0 0 6px; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--roof-1); }
.mw-viewer__review .mw-review { padding: 0; border: 0; background: none; }
.mw-viewer__review .mw-review__text { -webkit-line-clamp: unset; display: block; }
.mw-viewer__review .mw-review__reply { -webkit-line-clamp: unset; display: block; }
.jm-drawer__stat .mw-stars { font-size: 0.9rem; }

@media (max-width: 820px) {
  .mw { grid-template-columns: 1fr; }
  .mw-map { height: 360px; }
  .mw-side { max-height: none; }
  .mw-towns { max-height: 264px; }
  .mw-viewer[open] { grid-template-columns: 1fr; overflow-y: auto; }
  .mw-viewer__img { max-height: 56dvh; }
  .mw-viewer__text { max-height: none; overflow: visible; }
}

/* ==========================================================================
   THE WORK FEED — our-work.html (#wf) and the block under each town's map
   (.wf-town). js/job-map.js fills both from the CRM feed. Same rules as the
   explorer above: one 6px radius, hairline borders, no shadows. Review cards
   keep Google's look (.mw-review); project cards are McRay's.
   ========================================================================== */
.wf-section .work-lede { margin-bottom: clamp(20px, 3vw, 32px); }
.wf-map {
  height: clamp(340px, 42vw, 520px);
  border: 1px solid #e0d7d5;
  border-radius: 6px;
  background: #ece8e7;
  z-index: 0;
}
.wf.is-failed .wf-map { display: none; }
.wf-bar { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 22px; margin: 22px 0 6px; }
.wf-count { margin: 0; color: var(--slate); font-size: 0.95rem; }
.wf-count strong { font-size: 1.9rem; color: var(--maroon); font-weight: 700; font-variant-numeric: tabular-nums; }
.wf-reset {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--roof-1);
  text-decoration: underline;
  cursor: pointer;
}
.wf-townlink { font-size: 0.88rem; font-weight: 700; color: var(--maroon); }
.wf-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; margin-left: auto; }
.wf-seg { display: flex; border: 1px solid #cfc4c2; border-radius: 6px; overflow: hidden; background: var(--white); }
.wf-seg button {
  padding: 8px 14px;
  border: 0;
  border-left: 1px solid #cfc4c2;
  background: none;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--slate);
  cursor: pointer;
}
.wf-seg button:first-child { border-left: 0; }
.wf-seg button[aria-pressed="true"] { background: var(--maroon); color: var(--white); }
.wf-service { display: flex; align-items: center; gap: 8px; font-size: 0.88rem; font-weight: 600; color: var(--slate); }
.wf-service select {
  font: inherit;
  font-weight: 400;
  padding: 7px 10px;
  border: 1px solid #cfc4c2;
  border-radius: 6px;
  background: var(--white);
  color: var(--charcoal);
}

.wf-areas { padding: 14px 0 22px; border-bottom: 1px solid rgba(30, 21, 23, 0.1); margin-bottom: 24px; }
.wf-areas__title {
  margin: 0 0 10px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--roof-1);
}
.wf-chips { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; }
.wf-chips a,
.wf-chips button {
  display: block;
  padding: 7px 12px;
  border: 1px solid #d9cfcd;
  border-radius: 6px;
  background: var(--white);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--charcoal);
  text-decoration: none;
  cursor: pointer;
}
.wf-chips b { margin-left: 4px; font-weight: 500; color: var(--slate); font-variant-numeric: tabular-nums; }
.wf-chips a:hover,
.wf-chips button:hover { border-color: var(--maroon); }
.wf-chips button[aria-pressed="true"] { background: var(--maroon); border-color: var(--maroon); color: var(--white); }
.wf-chips button[aria-pressed="true"] b { color: rgba(255, 255, 255, 0.8); }
/* Folded to the busiest towns once the script is running. Without it every
   town stays listed, each one a plain link to its own page. */
.wf.is-live .wf-chips:not(.is-all) li:nth-child(n+15) { display: none; }
.wf-areas__more {
  margin-top: 12px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--roof-1);
  text-decoration: underline;
  cursor: pointer;
}

.wf-feed { display: flex; align-items: flex-start; gap: 20px; }
.wf-col { flex: 1 1 0; min-width: 0; display: grid; gap: 20px; }
.wf-feed .mw-review__text,
.wf-feed .mw-review__reply { -webkit-line-clamp: unset; display: block; }
.wf-job { border: 1px solid #e0d7d5; border-radius: 6px; background: var(--white); overflow: hidden; }
.wf-job__media { position: relative; aspect-ratio: 4 / 3; background: #ece8e7; }
.wf-job__open { display: block; width: 100%; height: 100%; padding: 0; border: 0; background: none; cursor: zoom-in; }
.wf-job__open img { display: block; width: 100%; height: 100%; object-fit: cover; }
.wf-job__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.9);
  color: var(--charcoal);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
}
.wf-job__prev { left: 10px; }
.wf-job__next { right: 10px; }
.wf-job__dots { position: absolute; left: 0; right: 0; bottom: 10px; display: flex; justify-content: center; gap: 6px; pointer-events: none; }
.wf-job__dots i { width: 7px; height: 7px; border-radius: 50%; background: rgba(255, 255, 255, 0.55); box-shadow: 0 0 0 1px rgba(18, 18, 18, 0.25); }
.wf-job__dots i.is-current { background: var(--white); }
.wf-job__body { display: grid; gap: 8px; padding: 16px 18px 18px; }
.wf-job__meta { display: flex; justify-content: space-between; gap: 12px; margin: 0; font-size: 0.82rem; color: var(--slate); }
.wf-job__title { margin: 0; font-size: 1.12rem; line-height: 1.25; }
.wf-job__title button { padding: 0; border: 0; background: none; font: inherit; color: var(--charcoal); text-align: left; cursor: pointer; }
.wf-job__title button:hover { color: var(--maroon); }
.wf-job__service { margin: 0; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--roof-1); }
.wf-job__desc { margin: 0; color: var(--slate); font-size: 0.93rem; line-height: 1.6; white-space: pre-line; }
.wf-job__review { margin-top: 4px; padding-top: 14px; border-top: 1px solid #e0d7d5; }
.wf-job__review .mw-review { padding: 0; border: 0; background: none; }
.wf button:focus-visible, .wf a:focus-visible { outline: 2px solid var(--maroon); outline-offset: 2px; }
.wf-empty { margin: 0; padding: 28px 0; color: var(--slate); max-width: 62ch; }
.wf-empty a { display: inline-block; margin-left: 4px; color: var(--maroon); font-weight: 700; }
.wf-more { display: block; margin: 28px auto 0; }
.wf-more[hidden], .wf-feed[hidden], .wf-filters[hidden], .wf-seg[hidden], .wf-service[hidden] { display: none; }

.wf-town { margin-top: clamp(28px, 4vw, 44px); }
.wf-town .mw-grid { margin-bottom: clamp(24px, 3vw, 36px); }
.wf-town__reviews { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 16px; align-items: start; margin-bottom: 20px; }
.wf-town__all { margin: 0; }

@media (max-width: 700px) {
  .wf-map { height: 340px; }
  .wf-filters { margin-left: 0; width: 100%; }
  .wf-chips { flex-wrap: nowrap; overflow-x: auto; margin: 0 -16px; padding: 0 16px 6px; }
  .wf-chips li { flex: none; }
  .wf.is-live .wf-chips:not(.is-all) li:nth-child(n+15) { display: block; }
  .wf-areas__more { display: none; }
}


/* --------------------------------------------------------------------------
   Rotating word in the homepage headline (2026-10-19, same approach as the
   RetailMark site). Pure CSS: the phrases are stacked in one grid cell and
   take turns, so the cell is as wide as the longest phrase from first paint
   and nothing below it ever jumps. It sits on its own line under "A roof
   that's" so a short word never leaves a gap mid sentence. Screen readers get
   the one real sentence in .cycle-sr, not four.
   -------------------------------------------------------------------------- */
.home-hero-title .cycle { display: grid; }
.home-hero-title .cycle-sr {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.home-hero-title .cycle-track { display: grid; }
.home-hero-title .cycle-track > span {
  grid-area: 1 / 1;
  justify-self: start;
  white-space: nowrap;
  /* McRay red. The logo's own --maroon is too dark to read on the video, so
     this is the brighter accent red from the same palette. */
  color: var(--accent-red);
  opacity: 0;
  /* 4 phrases x 3.5s: each holds about three seconds, half a second to swap. */
  animation: hero-cycle 14s infinite;
}
.home-hero-title .cycle-track > span:nth-child(2) { animation-delay: 3.5s; }
.home-hero-title .cycle-track > span:nth-child(3) { animation-delay: 7s; }
.home-hero-title .cycle-track > span:nth-child(4) { animation-delay: 10.5s; }

@keyframes hero-cycle {
  0%   { opacity: 0; transform: translateY(0.3em); }
  3%   { opacity: 1; transform: none; }
  22%  { opacity: 1; transform: none; }
  25%  { opacity: 0; transform: translateY(-0.3em); }
  100% { opacity: 0; transform: translateY(0.3em); }
}

/* A phrase that changes every few seconds is exactly what reduced motion is
   for. Hold the first one. */
@media (prefers-reduced-motion: reduce) {
  .home-hero-title .cycle-track > span { animation: none; opacity: 0; transform: none; }
  .home-hero-title .cycle-track > span:first-child { opacity: 1; }
}
