/* ==========================================================================
   Dynamic Fence LLC - sitewide stylesheet
   One file, no build step, no frameworks.
   Sections: fonts / tokens / reset / base / layout / components / motion / print
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts (self-hosted)

   Three families, each with one job:
   - CA Cape Rock (Envato licence): the fat Clarendon slab. h1 and h2 only.
   - Lora: the softer serif. Italic accent words inside h1/h2, the testimonial,
     h3 and every display usage below it (stat numbers, FAQ questions, cities).
   - Einer Grotesk (Envato licence): everything set as text, nav, buttons, forms.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Cape Rock';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/cape-rock-regular.woff2') format('woff2');
}
@font-face {
  font-family: 'Cape Rock';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/cape-rock-italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Lora';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/lora-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Lora';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/lora-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Lora';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/lora-500italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Lora';
  font-style: italic;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/lora-600italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Einer Grotesk';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/einer-grotesk-regular.woff2') format('woff2');
}
@font-face {
  font-family: 'Einer Grotesk';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/einer-grotesk-book.woff2') format('woff2');
}
@font-face {
  font-family: 'Einer Grotesk';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/einer-grotesk-bold.woff2') format('woff2');
}
@font-face {
  font-family: 'Einer Grotesk';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('../fonts/einer-grotesk-heavy.woff2') format('woff2');
}

/* --------------------------------------------------------------------------
   2. Design tokens
   -------------------------------------------------------------------------- */
:root {
  /* palette */
  --paper: #FAF8F4;
  --ink: #211E19;
  --ink-soft: #141210;
  --roast: #2B211A;
  --cream: #F2EEE5;
  /* Nudged from the design's #C65A2E so white button and band text clears
     WCAG AA (4.70:1); visually indistinguishable from the approved hex. */
  --terracotta: #BE5429;
  --terracotta-dark: #A6441F;
  --terracotta-light: #E8A87C;
  --gold: #E2A63D;
  --muted: #5C564C;
  --muted-dark-bg: #A39C8D;
  --nav-idle: #6B655C;
  --label: #8A8375;
  --hairline: #E7E1D6;
  --hairline-strong: #D8D1C4;
  --hairline-dark: rgba(242, 238, 229, 0.15);
  --hairline-dark-strong: rgba(242, 238, 229, 0.2);

  /* type */
  /* Cape Rock is the slab used for h1 and h2. It ships one weight, 400. */
  --font-display: 'Cape Rock', Georgia, 'Times New Roman', serif;
  /* Lora is the soft serif: italic accents, the testimonial, h3 and below. */
  --font-display-soft: 'Lora', Georgia, 'Times New Roman', serif;
  --font-body: 'Einer Grotesk', system-ui, -apple-system, 'Segoe UI', Arial, sans-serif;
  /* Cape Rock sets much wider and heavier than Lora 600, so the display sizes
     came down roughly 14% when the slab replaced the serif. */
  --fs-h1: clamp(34px, 3.9vw, 52px);
  /* 34, not 35: at 35 the longest style heading on /fence-styles, "Driveway
     Gates & Automation", misses its 636px column by six pixels and strands the
     ampersand at the end of the first line. */
  --fs-h2: clamp(25px, 2.8vw, 34px);
  --fs-h2-cta: clamp(27px, 3.1vw, 38px);
  --fs-h3: clamp(20px, 2vw, 26px);
  --fs-quote: clamp(21px, 2.6vw, 30px);
  --fs-lead: clamp(16.5px, 1.4vw, 18px);
  --fs-body: 16.5px;
  --fs-small: 15px;
  --fs-label: 13px;

  /* layout */
  --maxw: 1280px;
  --gutter: 56px;
  --section-y: 104px;
  --band-y: 104px;
  --header-h: 80px;
  /* How far the overlay logo grows past its compact size on the Home hero. */
  --brand-scale: 1.875;

  /* motion */
  --ease: cubic-bezier(.2, .7, .3, 1);
  --radius: 0;
}

@media (min-width: 900px) and (max-width: 1119px) {
  :root { --brand-scale: 1.5; }
}
@media (max-width: 899px) {
  :root {
    --gutter: 24px;
    --section-y: 72px;
    --band-y: 72px;
    --header-h: 72px;
    /* compact logo is 52px here, so this lands at 92px */
    --brand-scale: 1.77;
  }
}
@media (max-width: 559px) {
  :root { --brand-scale: 1.38; }
}
@media (max-width: 420px) {
  :root { --gutter: 20px; }
}

/* --------------------------------------------------------------------------
   3. Reset + base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: auto; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.7;
  min-width: 320px;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

img, video { display: block; max-width: 100%; }

h1, h2, h3, h4 {
  margin: 0;
}
/* Cape Rock: the big statements. One weight, and its caps carry enough colour
   that the negative tracking Lora wanted would close the counters up. */
h1, h2 {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: 0.002em;
  /* The slab runs about a fifth wider than Lora at the same size, so headings
     that used to sit on two comfortable lines now rag badly if they wrap
     greedily. Balanced wrapping evens them out; the Home hero opts back out. */
  text-wrap: balance;
}
h3, h4 {
  font-family: var(--font-display-soft);
  font-weight: 600;
  letter-spacing: -0.01em;
}
h1 { font-size: var(--fs-h1); line-height: 1.1; }
h2 { font-size: var(--fs-h2); line-height: 1.18; }
h3 { font-size: var(--fs-h3); line-height: 1.25; }
em { font-style: italic; color: var(--terracotta); }
/* The intended contrast: slab roman against serif italic. Lora carries a taller
   x-height than Cape Rock (51 against 45 per 100px), so the accent words are
   dialled back to 0.96em. That lands the two cap heights within a pixel of each
   other and leaves the italic just enough extra x-height to hold its own
   against the slab's weight. */
h1 em, h2 em {
  font-family: var(--font-display-soft);
  font-weight: 600;
  font-size: 0.96em;
  letter-spacing: -0.005em;
}

p { margin: 0 0 16px; }
p:last-child { margin-bottom: 0; }

a { color: var(--terracotta); }
a:hover { color: var(--terracotta-dark); }

ul { margin: 0; padding: 0; list-style: none; }

button { font: inherit; color: inherit; }

hr { border: 0; border-top: 1px solid var(--hairline); margin: 0; }

/* Anchor targets clear the pinned header */
[id] { scroll-margin-top: calc(var(--header-h) + 16px); }

::selection { background: var(--terracotta); color: #fff; }

/* Focus states: 2px terracotta outline, offset 2px */
:focus-visible {
  outline: 2px solid var(--terracotta);
  outline-offset: 2px;
}
.band-dark :focus-visible,
.site-footer :focus-visible { outline-color: var(--terracotta-light); }
.band-cta :focus-visible { outline-color: #fff; }

/* Skip link */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--ink);
  color: var(--cream);
  padding: 12px 20px;
  font-weight: 800;
  font-size: 14px;
  letter-spacing: 0.06em;
  text-decoration: none;
}
.skip-link:focus {
  left: 8px;
  top: 8px;
  color: var(--cream);
}

/* --------------------------------------------------------------------------
   4. Layout helpers
   -------------------------------------------------------------------------- */
.container {
  max-width: var(--maxw);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

.section { padding: var(--section-y) 0; }
.section--tight { padding: calc(var(--section-y) * 0.7) 0; }

.band-dark {
  background: var(--ink);
  color: var(--cream);
  padding: var(--band-y) 0;
}
.band-roast {
  background: var(--roast);
  color: var(--cream);
  padding: calc(var(--band-y) + 8px) 0;
}
.band-cta {
  background: var(--terracotta);
  color: #fff;
  padding: 96px 0;
  text-align: center;
}
@media (max-width: 900px) {
  .band-cta { padding: 72px 0; }
}

/* --------------------------------------------------------------------------
   5. Small shared utilities

   The header is the topmost element on every page; the old call bar strip is
   gone and the phone number now lives in the header Call control.
   -------------------------------------------------------------------------- */

/* Middle dot separator: at most one per line, always given room to breathe */
.dot { padding: 0 0.7em; }

/* Present for assistive tech, out of the layout. Used by the call back form's
   honeypot, which must stay in the DOM so automated fillers still see it. */
.vis-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   6. Header + navigation

   SOLID is the default state, so inner pages need no extra work.
   Home adds .site-header--overlay: fixed and transparent over the video hero,
   returning to the solid look once <body> gains .header-solid (set by the
   IntersectionObserver sentinel inside the hero).
   -------------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 40px;
  padding: 8px var(--gutter);
  background: rgba(250, 248, 244, 0.92);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--hairline);
  transition: background .25s var(--ease), border-color .25s var(--ease);
}

/* Home: pinned over the hero video. Without JS this modifier never engages,
   so the header simply renders in its solid default state. */
.js .site-header--overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
}
.js body:not(.header-solid) .site-header--overlay {
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border-bottom-color: transparent;
}
.js body:not(.header-solid) .site-header--overlay .nav a { color: rgba(242, 238, 229, 0.82); }
.js body:not(.header-solid) .site-header--overlay .nav a:hover,
.js body:not(.header-solid) .site-header--overlay .nav a:focus-visible,
.js body:not(.header-solid) .site-header--overlay .nav a[aria-current="page"] {
  color: #fff;
  border-bottom-color: var(--terracotta-light);
}
.js body:not(.header-solid) .site-header--overlay .nav-toggle {
  color: var(--cream);
  border-color: rgba(242, 238, 229, 0.45);
}
.js body:not(.header-solid) .site-header--overlay .nav-toggle .bars,
.js body:not(.header-solid) .site-header--overlay .nav-toggle .bars::before,
.js body:not(.header-solid) .site-header--overlay .nav-toggle .bars::after {
  background: var(--cream);
}
.js body:not(.header-solid) .site-header--overlay .nav-toggle[aria-expanded="true"] .bars {
  background: transparent;
}
/* Focus stays visible against the video */
.js body:not(.header-solid) .site-header--overlay :focus-visible {
  outline-color: var(--cream);
}

.brand {
  position: relative;
  display: block;
  height: 64px;
  aspect-ratio: 330 / 283;
  flex: 0 0 auto;
  text-decoration: none;
  color: var(--ink-soft);
}
.brand-logo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* Growth anchors at the top left corner, so the big Home state spills down
     over the hero and never upward out of the viewport. */
  transform-origin: top left;
  transition: opacity .25s var(--ease), transform .3s var(--ease);
}
/* Dark mark is the default; the cream mark fades in only over the video. */
.brand-logo--light { opacity: 0; }
.js body:not(.header-solid) .site-header--overlay .brand-logo--dark { opacity: 0; }
.js body:not(.header-solid) .site-header--overlay .brand-logo--light { opacity: 1; }
/* Home, before the sentinel is passed: the cream mark plays large over the
   hero. .brand keeps its 64px box, so the header height never moves and there
   is no layout shift; only the painted mark scales. */
.js body:not(.header-solid) .site-header--overlay .brand-logo {
  transform: scale(var(--brand-scale));
}
/* The open nav panel covers the room the big mark needs, so it steps back to
   the compact size for as long as the panel is down. */
.js body.nav-open .site-header--overlay .brand-logo {
  transform: none;
}
/* Reduced motion swaps the two sizes instantly. */
@media (prefers-reduced-motion: reduce) {
  .brand-logo { transition: opacity .25s var(--ease); }
}

.nav {
  display: flex;
  gap: 30px;
  margin-left: auto;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.nav a {
  color: var(--nav-idle);
  text-decoration: none;
  padding-bottom: 4px;
  border-bottom: 2px solid transparent;
  transition: color .25s var(--ease), border-color .25s var(--ease);
  white-space: nowrap;
}
.nav a:hover,
.nav a:focus-visible {
  color: var(--ink);
  border-bottom-color: var(--terracotta);
}
.nav a[aria-current="page"] {
  color: var(--ink);
  border-bottom-color: var(--terracotta);
}

.header-cta {
  flex: 0 0 auto;
  background: var(--terracotta);
  color: #fff;
  text-decoration: none;
  font-weight: 800;
  font-size: 14px;
  letter-spacing: 0.04em;
  padding: 12px 22px;
  transition: background .25s var(--ease), transform .25s var(--ease), box-shadow .25s var(--ease);
}
.header-cta:hover {
  background: var(--roast);
  color: #fff;
}

/* Ghost sibling of .header-cta: same pill, outline instead of fill.
   Ink on paper in the solid state, cream over the Home hero. */
/* The two CTAs read as one pair, so they sit closer together than the
   header's own 40px gap. */
.call-wrap {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  margin-right: -24px;
}
.header-call {
  display: inline-block;
  background: transparent;
  border: 1px solid var(--ink);
  color: var(--ink);
  text-decoration: none;
  font-family: var(--font-body);
  font-weight: 800;
  font-size: 14px;
  letter-spacing: 0.04em;
  padding: 11px 22px;
  cursor: pointer;
  white-space: nowrap;
  transition: background .25s var(--ease), color .25s var(--ease),
              border-color .25s var(--ease), transform .25s var(--ease),
              box-shadow .25s var(--ease);
}
.header-call:hover {
  background: var(--terracotta);
  border-color: var(--terracotta);
  color: #fff;
}
/* Holding the dialog open is a pressed state, not a second estimate CTA:
   ink, so the pair never reads as two identical terracotta pills. */
.header-call[aria-expanded="true"],
.header-call[aria-expanded="true"]:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--cream);
}
.js body:not(.header-solid) .site-header--overlay .header-call {
  border-color: var(--cream);
  color: var(--cream);
}
.js body:not(.header-solid) .site-header--overlay .header-call:hover {
  background: var(--terracotta);
  border-color: var(--terracotta);
  color: #fff;
}
.js body:not(.header-solid) .site-header--overlay .header-call[aria-expanded="true"],
.js body:not(.header-solid) .site-header--overlay .header-call[aria-expanded="true"]:hover {
  background: var(--ink-soft);
  border-color: var(--cream);
  color: var(--cream);
}

.nav-toggle {
  display: none;
  margin-left: auto;
  background: transparent;
  border: 1px solid var(--hairline-strong);
  padding: 10px 12px;
  cursor: pointer;
  align-items: center;
  gap: 9px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.13em;
  color: var(--ink);
}
.nav-toggle .bars {
  position: relative;
  display: block;
  width: 20px;
  height: 2px;
  background: var(--ink);
}
.nav-toggle .bars::before,
.nav-toggle .bars::after {
  content: "";
  position: absolute;
  left: 0;
  width: 20px;
  height: 2px;
  background: var(--ink);
}
.nav-toggle .bars::before { top: -6px; }
.nav-toggle .bars::after { top: 6px; }
.nav-toggle[aria-expanded="true"] .bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .bars::before { top: 0; transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] .bars::after { top: 0; transform: rotate(-45deg); }

.nav-panel {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  background: var(--paper);
  border-bottom: 1px solid var(--hairline);
  box-shadow: 0 18px 30px -22px rgba(33, 30, 25, 0.5);
  padding: 8px var(--gutter) 24px;
}
.nav-panel a {
  display: block;
  padding: 14px 0;
  border-bottom: 1px solid var(--hairline);
  color: var(--ink);
  text-decoration: none;
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 0.02em;
}
.nav-panel a[aria-current="page"] { color: var(--terracotta); }
.nav-panel .header-cta {
  display: block;
  text-align: center;
  margin-top: 20px;
  border-bottom: 0;
  color: #fff;
  font-size: 15px;
  padding: 15px 22px;
}
.nav-panel .panel-call {
  display: block;
  margin-top: 12px;
  text-align: center;
  border-bottom: 0;
  color: var(--muted);
  font-weight: 800;
  font-size: 15px;
}
/* There is no anchor to hang a popover from inside the panel, so this one
   opens the same dialog as a centred modal. */
.nav-panel .panel-callback {
  display: block;
  width: 100%;
  margin-top: 10px;
  padding: 10px 0 0;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--hairline);
  text-align: center;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 15px;
  color: var(--muted);
  cursor: pointer;
  transition: color .25s var(--ease);
}
.nav-panel .panel-callback:hover { color: var(--terracotta); }

@media (max-width: 899px) {
  .site-header { gap: 16px; }
  .nav, .site-header > .header-cta { display: none; }
  .call-wrap { margin-left: auto; margin-right: 0; }
  .nav-toggle { display: inline-flex; margin-left: 0; }
  .nav-panel.is-open { display: block; }
  .brand { height: 52px; }
}
@media (max-width: 419px) {
  .site-header { gap: 12px; }
  .header-call { padding: 11px 16px; }
}
@media (min-width: 900px) and (max-width: 1119px) {
  .site-header { gap: 20px; padding-left: 28px; padding-right: 28px; }
  .nav { gap: 18px; font-size: 13.5px; }
  .header-call, .site-header > .header-cta { padding-left: 16px; padding-right: 16px; }
  .call-wrap { margin-right: -8px; }
}

/* --------------------------------------------------------------------------
   6b. Call popover

   Built by main.js on fine-pointer devices only. Touch devices never see it:
   there the header Call control stays a plain tel: link, so a tap dials.
   Two placements, one element and one form:
   - anchored under the header button, right aligned to it
   - centred modal over a scrim, opened from the mobile nav panel where there
     is no anchor to hang it from
   -------------------------------------------------------------------------- */
.call-pop {
  position: absolute;
  /* clears the header's bottom hairline, whichever pill height is in play */
  top: calc(100% + 20px);
  right: 0;
  z-index: 70;
  width: 320px;
  max-width: calc(100vw - 32px);
  padding: 22px 24px 24px;
  background: var(--paper);
  border: 1px solid var(--hairline-strong);
  box-shadow: 0 24px 44px -22px rgba(33, 30, 25, 0.55);
  color: var(--ink);
  text-align: left;
}
.call-pop[hidden] { display: none; }

.call-pop--modal {
  position: fixed;
  top: max(96px, 10vh);
  left: var(--gutter);
  right: var(--gutter);
  width: auto;
  max-width: 400px;
  max-height: 78vh;
  overflow-y: auto;
  margin: 0 auto;
  z-index: 90;
}
.call-scrim {
  position: fixed;
  inset: 0;
  z-index: 88;
  background: rgba(20, 18, 16, 0.6);
}
.call-scrim[hidden] { display: none; }

.call-pop-number {
  display: inline-block;
  font-family: var(--font-display-soft);
  font-weight: 600;
  font-size: 26px;
  line-height: 1.2;
  color: var(--ink);
  text-decoration: none;
  transition: color .25s var(--ease);
}
.call-pop-number:hover { color: var(--terracotta); }
.call-pop-note {
  margin: 6px 0 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--muted);
}
.call-pop-rule {
  height: 1px;
  margin: 18px 0;
  background: var(--hairline);
}
.call-pop-head {
  margin: 0 0 14px;
  font-family: var(--font-display-soft);
  font-weight: 600;
  font-size: 18px;
  line-height: 1.3;
}
.call-pop-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 16px;
}
.call-pop-label {
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.15em;
  color: var(--label);
}
.call-pop-input,
.call-pop-select {
  width: 100%;
  border: none;
  border-bottom: 1px solid var(--hairline-strong);
  background: transparent;
  font-family: var(--font-body);
  font-size: 16px;
  padding: 7px 0;
  color: var(--ink);
  border-radius: 0;
  transition: border-color .25s var(--ease);
}
.call-pop-input:focus,
.call-pop-select:focus { border-bottom-color: var(--terracotta); }
.call-pop-submit {
  width: 100%;
  margin-top: 6px;
  padding: 13px 18px;
  font-size: 14.5px;
}
.call-pop-submit[disabled] { opacity: 0.7; cursor: default; }
.call-pop-error {
  margin: 0 0 14px;
  border: 1px solid var(--terracotta);
  padding: 10px 12px;
  font-size: 14px;
  font-weight: 700;
  line-height: 1.5;
}
.call-pop-error[hidden] { display: none; }
.call-pop-done[hidden] { display: none; }
.call-pop-done p {
  margin: 0 0 10px;
  font-family: var(--font-display-soft);
  font-weight: 600;
  font-size: 18px;
  line-height: 1.35;
}

@media (prefers-reduced-motion: no-preference) {
  .call-pop {
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity .18s var(--ease), transform .18s var(--ease);
  }
  .call-pop--modal { transform: translateY(-10px); }
  .call-pop.is-open { opacity: 1; transform: none; }
  .call-scrim { opacity: 0; transition: opacity .18s var(--ease); }
  .call-scrim.is-open { opacity: 1; }
}

/* --------------------------------------------------------------------------
   7. Buttons + text links
   -------------------------------------------------------------------------- */
.btn {
  display: inline-block;
  text-decoration: none;
  font-weight: 800;
  font-size: 16px;
  letter-spacing: 0.02em;
  padding: 16px 30px;
  border: 0;
  cursor: pointer;
  transition: background .25s var(--ease), color .25s var(--ease),
              transform .25s var(--ease), box-shadow .25s var(--ease);
}
.btn-solid {
  background: var(--terracotta);
  color: #fff;
}
.btn-solid:hover {
  background: var(--roast);
  color: #fff;
}
.btn-cream {
  background: var(--cream);
  color: var(--roast);
}
.btn-cream:hover {
  background: #fff;
  color: var(--roast);
}

.link-rule {
  display: inline-block;
  color: var(--ink);
  text-decoration: none;
  font-weight: 800;
  font-size: 16px;
  border-bottom: 2px solid var(--terracotta);
  padding-bottom: 3px;
  transition: color .25s var(--ease);
}
.link-rule:hover { color: var(--terracotta); }
.link-rule--light {
  color: #fff;
  border-bottom-color: rgba(255, 255, 255, 0.5);
}
.link-rule--light:hover { color: #fff; border-bottom-color: #fff; }
.band-dark .link-rule {
  color: var(--cream);
  border-bottom-color: var(--terracotta-light);
}
.band-dark .link-rule:hover { color: var(--terracotta-light); }

/* --------------------------------------------------------------------------
   8. Section header device (hairline, optional small-caps label)
   -------------------------------------------------------------------------- */
.section-head {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-bottom: 32px;
}
.section-head .rule {
  flex: 1;
  height: 1px;
  background: var(--hairline);
}
.band-dark .section-head .rule { background: var(--hairline-dark); }

/* Einer Grotesk Heavy sets wider than Nunito Sans 800 did, so the caps rows
   all came in a few hundredths: 0.22em opened into gaps the grotesk does not
   need. */
.eyebrow {
  font-size: var(--fs-label);
  font-weight: 800;
  letter-spacing: 0.18em;
  color: var(--terracotta);
  line-height: 1.4;
}
.band-dark .eyebrow { color: var(--terracotta-light); }

.section-title { margin: 0 0 24px; }
.section-lede {
  font-size: var(--fs-lead);
  line-height: 1.7;
  color: var(--muted);
  max-width: 58ch;
}
.band-dark .section-lede { color: var(--muted-dark-bg); }

/* --------------------------------------------------------------------------
   9. Photo frames
   -------------------------------------------------------------------------- */
.frame {
  position: relative;
  min-width: 0;
}
.frame::before {
  content: "";
  position: absolute;
  inset: 24px -24px -24px 24px;
  border: 1px solid var(--hairline-strong);
  pointer-events: none;
}
.frame > .frame-media {
  position: relative;
  height: 100%;
  overflow: hidden;
  transition: transform .4s var(--ease);
}
.frame:hover > .frame-media { transform: translate(-4px, -4px); }
.frame-media img,
.frame-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.media-fill {
  min-width: 0;
  overflow: hidden;
  transition: transform .35s var(--ease);
}
.media-fill img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Tall portrait source in a landscape slot: bias the crop upward so the
   subject and its surroundings survive instead of the middle band. */
.media-fill--upper img { object-position: center 32%; }
.media-fill:hover { transform: translateY(-6px); }

@media (max-width: 767px) {
  .frame::before { inset: 16px -16px -16px 16px; }
  .frame:hover > .frame-media,
  .media-fill:hover { transform: none; }
}

/* --------------------------------------------------------------------------
   10. Hero: one full-bleed cinematic layer, running under the fixed header.

   Desktop: four landscape photos stacked and slowly crossfaded, each drifting
   while it holds the frame. The portrait footage goes soft once it is cropped
   and upscaled to desktop width, so photography carries the wide view.
   Under 900px: the three portrait pans, played through one after another,
   which is the footage's home turf.
   Only the layer for the current form factor is displayed, and only its
   opening frame is in the markup, so neither form factor pays for the other.
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: stretch;
  overflow: hidden;
  isolation: isolate;
  background: var(--ink-soft);
}
.hero-media {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
}
/* The <picture> is only there to gate the opening photo by breakpoint; it must
   not become a box between .hero-stills and its layer. */
.hero-media picture { display: contents; }
.hero-stills,
.hero-clips {
  position: absolute;
  inset: 0;
}
.hero-clips { display: none; }
.hero-still,
.hero-clip {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  z-index: 1;
}
/* The incoming layer sits on top so it can fade up over a frame that is still
   fully opaque. Two layers cross-dissolving at 50% each would only cover 75%
   of the frame, and the backdrop would flash through the middle of every
   handover. */
.hero-still.is-current,
.hero-clip.is-current {
  opacity: 1;
  z-index: 2;
}
/* Scrim: the copy sits just above the middle of the frame now, so the weight
   is carried by a broad veil through the centre and a left column, rather
   than the bottom-heavy ramp the old bottom-anchored copy needed. The top
   band stays: it is what keeps the overlaid nav legible over bright sky.
   Worst case (a blown-out white photo) the copy column still measures about
   7:1 against cream, so every frame in the rotation clears AA. */
.hero-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    /* header band, keeps the overlaid nav and the big logo legible */
    linear-gradient(to bottom,
      rgba(20, 18, 16, 0.62) 0%,
      rgba(20, 18, 16, 0.40) 10%,
      rgba(20, 18, 16, 0.14) 22%,
      rgba(20, 18, 16, 0.04) 30%),
    /* body veil, strongest across the middle where the copy now sits */
    linear-gradient(to bottom,
      rgba(20, 18, 16, 0) 0%,
      rgba(20, 18, 16, 0.36) 22%,
      rgba(20, 18, 16, 0.58) 44%,
      rgba(20, 18, 16, 0.70) 68%,
      rgba(20, 18, 16, 0.84) 100%),
    /* left column, the one the copy actually sits in */
    linear-gradient(to right,
      rgba(20, 18, 16, 0.74) 0%,
      rgba(20, 18, 16, 0.40) 46%,
      rgba(20, 18, 16, 0) 82%);
}
/* Watched by main.js to flip the header between transparent and solid. */
.hero-sentinel {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: calc(100% - 140px);
  pointer-events: none;
}

/* A column, not a bottom-aligned block: the copy takes the free space with
   auto margins so it settles just above the middle of the frame, while the
   stat row stays anchored along the bottom edge as its own band. */
.hero-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: calc(var(--header-h) + 48px) var(--gutter) 72px;
  color: var(--cream);
}
.hero-copy {
  max-width: 46rem;
  margin: auto 0;
}
.hero .stats { flex: 0 0 auto; margin-top: 48px; }

.hero-kicker {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 26px;
}
.hero-kicker .tick {
  display: block;
  width: 48px;
  height: 1px;
  background: var(--terracotta-light);
  transform-origin: left;
  flex: 0 0 auto;
}
/* Cream, not terracotta-light: over a photograph the light accent only reaches
   ~2.7:1 at this size, well under AA. Cream clears 4.5:1 on every hero frame. */
.hero .eyebrow { color: var(--cream); }
/* Cape Rock is narrow enough at 52px that the whole line would fit the 46rem
   copy column, and the slab would then run to 60% of a 1440 frame, out of the
   scrim's left column and across the bright part of every hero photo. The em
   cap holds the two-line break at every step of the h1 clamp and keeps the
   headline inside the measured 31% the contrast pass was built on. */
.hero h1 {
  margin: 0 0 24px;
  max-width: 10em;
  color: var(--cream);
  /* Greedy, not balanced: balance splits this headline as "Enhance" over
     "your curb appeal." and strands the italic mid-line. The cap above already
     guarantees two lines, so plain wrapping gives the intended break, the slab
     phrase over the italic one. */
  text-wrap: wrap;
}
.hero h1 em { color: var(--terracotta-light); }
.hero-lede {
  font-size: var(--fs-lead);
  line-height: 1.7;
  color: rgba(242, 238, 229, 0.88);
  margin: 0 0 36px;
  max-width: 52ch;
}
/* The gap down to the stat row belongs to the stat row now: the copy block
   ends here, and what follows is a separate band along the bottom. */
.hero-actions {
  display: flex;
  align-items: center;
  gap: 28px;
  flex-wrap: wrap;
}
/* Stat row sits along the bottom of the frame, light on the scrim */
.hero .stat { border-left-color: rgba(242, 238, 229, 0.3); }
.hero .stat-value { color: var(--cream); }
.hero .stat-value--gold { color: var(--gold); }
.hero .stat-label { color: rgba(242, 238, 229, 0.74); }

/* The trust strip is the first paper section after the full-bleed hero, so it
   loses the top hairline it would otherwise sit behind. */
.hero + .trustline { border-top: 0; }
.hero + .trustline .container { padding-top: 26px; padding-bottom: 26px; }

@media (max-width: 899px) {
  .hero-stills { display: none; }
  .hero-clips { display: block; }
  /* Tightened so the whole hero, stat row included, lands inside 100dvh.
     The top padding also clears the enlarged overlay logo, which spills
     roughly 40px below the header bar at this width. */
  .hero-inner {
    padding-top: calc(var(--header-h) + 52px);
    padding-bottom: 44px;
  }
  .hero .stats { margin-top: 32px; }
  .hero-kicker { gap: 12px; margin-bottom: 20px; }
  .hero-kicker .tick { width: 24px; }
  .hero .eyebrow { font-size: 11px; letter-spacing: 0.12em; }
  .hero h1 { margin-bottom: 18px; }
  .hero-lede { font-size: 16px; margin-bottom: 28px; }
  .hero-actions { gap: 18px; }
  /* Portrait frame, no left column to lean on: the veil runs the full width
     and carries the copy from just under the logo to the stat row. */
  .hero-scrim {
    background:
      linear-gradient(to bottom,
        rgba(20, 18, 16, 0.68) 0%,
        rgba(20, 18, 16, 0.40) 12%,
        rgba(20, 18, 16, 0.14) 22%),
      linear-gradient(to bottom,
        rgba(20, 18, 16, 0) 0%,
        rgba(20, 18, 16, 0.40) 18%,
        rgba(20, 18, 16, 0.66) 40%,
        rgba(20, 18, 16, 0.74) 64%,
        rgba(20, 18, 16, 0.88) 100%);
  }
}

/* A 667px viewport, an iPhone SE or 8 in portrait, is the one frame the hero
   does not fit: kicker, headline, lede, buttons and stat row come to roughly
   728px at the normal spacing. Nothing is dropped here, the air between the
   blocks is simply taken in, which brings the column to about 646px and leaves
   the stat row inside the frame. Keyed to viewport height, so every taller
   phone keeps the generous version. */
@media (max-height: 700px) {
  .hero-inner {
    padding-top: calc(var(--header-h) + 20px);
    padding-bottom: 28px;
  }
  .hero-kicker { margin-bottom: 12px; }
  .hero h1 { margin-bottom: 12px; }
  .hero-lede { margin-bottom: 20px; }
  .hero .stats { margin-top: 20px; }
}

/* --------------------------------------------------------------------------
   11. Stat blocks
   -------------------------------------------------------------------------- */
.stats {
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
}
.stat {
  border-left: 1px solid var(--hairline-strong);
  padding-left: 14px;
}
.stat-value {
  font-family: var(--font-display-soft);
  font-weight: 600;
  font-size: 26px;
  line-height: 1.2;
}
.stat-value .star { color: var(--gold); }
.stat-value--gold { color: var(--gold); }
/* Sized so all three sit on one line inside the hero copy column */
.stat-label {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--label);
  line-height: 1.4;
  white-space: nowrap;
}
@media (max-width: 480px) {
  .stats { gap: 16px 18px; }
  .stat-value { font-size: 22px; }
  .stat-label { font-size: 11px; letter-spacing: 0.04em; }
}

/* --------------------------------------------------------------------------
   12. Trust line strip
   -------------------------------------------------------------------------- */
.trustline {
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.trustline .container {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px 24px;
  padding-top: 22px;
  padding-bottom: 22px;
}
.trustline span {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.12em;
  color: var(--muted);
  text-align: center;
}
/* Dividers only when the row certainly fits on one line */
@media (min-width: 1120px) {
  .trustline .container { gap: 0; flex-wrap: nowrap; }
  .trustline span {
    padding: 0 24px;
    border-right: 1px solid var(--hairline);
    white-space: nowrap;
  }
  .trustline span:last-child { border-right: 0; }
}

/* --------------------------------------------------------------------------
   13. Split rows (about teaser, service area)
   -------------------------------------------------------------------------- */
.split {
  display: grid;
  grid-template-columns: 1fr 1.15fr;
  gap: 80px;
  align-items: center;
}
.split--even { grid-template-columns: 1fr 1fr; }
.split-media { height: 440px; }
.split-media--short { height: 400px; }

@media (max-width: 1023px) {
  .split { gap: 48px; }
  .split-media { height: 380px; }
  .split-media--short { height: 340px; }
}
@media (max-width: 767px) {
  .split, .split--even { grid-template-columns: 1fr; gap: 36px; }
  .split-media, .split-media--short { height: 300px; }
}

/* --------------------------------------------------------------------------
   14. Services band + tile grid
   -------------------------------------------------------------------------- */
.services-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 40px;
  margin-bottom: 56px;
  flex-wrap: wrap;
}
.services-head h2 { margin: 0; }
.services-head p {
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--muted-dark-bg);
  margin: 0;
  max-width: 42ch;
}
.section-head--services { margin-bottom: 16px; }

.svc-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 40px 32px;
}
.svc-tile {
  text-decoration: none;
  color: var(--cream);
  display: block;
  min-width: 0;
  transition: transform .35s var(--ease);
}
.svc-tile:hover { color: var(--cream); transform: translateY(-8px); }
.svc-tile-media {
  height: 200px;
  min-width: 0;
  overflow: hidden;
  margin-bottom: 16px;
  background: var(--roast);
}
.svc-tile-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .5s var(--ease);
}
.svc-tile:hover .svc-tile-media img { transform: scale(1.04); }
.svc-tile-name {
  display: block;
  border-top: 1px solid var(--hairline-dark-strong);
  padding-top: 14px;
  font-family: var(--font-display-soft);
  font-weight: 600;
  font-size: 19px;
  line-height: 1.3;
  transition: color .25s var(--ease);
}
.svc-tile:hover .svc-tile-name { color: var(--terracotta-light); }

@media (max-width: 1023px) {
  .svc-grid { grid-template-columns: repeat(2, 1fr); gap: 32px 24px; }
}
@media (max-width: 767px) {
  .svc-grid { grid-template-columns: 1fr; gap: 28px; }
  .svc-tile:hover { transform: none; }
  .svc-tile-media { height: 220px; }
  .services-head { margin-bottom: 40px; }
}

/* --------------------------------------------------------------------------
   15. Before / after pairs
   -------------------------------------------------------------------------- */
.ba-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 48px;
}
.ba-pair {
  min-width: 0;
  transition: transform .35s var(--ease);
}
.ba-pair:hover { transform: translateY(-6px); }
.ba-images {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
  height: 280px;
}
.ba-images > div {
  min-width: 0;
  overflow: hidden;
  background: var(--hairline);
}
.ba-images img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ba-caption {
  display: flex;
  justify-content: space-between;
  padding-top: 12px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.17em;
  color: var(--label);
}
.ba-caption .after { color: var(--terracotta); }

@media (max-width: 900px) {
  .ba-grid { grid-template-columns: 1fr; gap: 36px; }
  .ba-pair:hover { transform: none; }
}
@media (max-width: 480px) {
  .ba-images { height: 220px; }
  .ba-caption { font-size: 11px; letter-spacing: 0.14em; }
}

/* --------------------------------------------------------------------------
   16. Testimonial band
   -------------------------------------------------------------------------- */
.quote-band .container { max-width: 900px; text-align: center; }
.quote-stars {
  color: var(--gold);
  font-size: 22px;
  letter-spacing: 8px;
  margin-bottom: 28px;
  line-height: 1;
}
.quote-text {
  font-family: var(--font-display-soft);
  font-style: italic;
  font-weight: 500;
  font-size: var(--fs-quote);
  line-height: 1.55;
  margin: 0 0 28px;
  color: var(--cream);
}
.quote-attrib {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
}
.quote-attrib .tick {
  width: 32px;
  height: 1px;
  background: var(--terracotta-light);
  flex: 0 0 auto;
}
.quote-source {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--terracotta-light);
}

/* --------------------------------------------------------------------------
   17. Service area city list
   -------------------------------------------------------------------------- */
.city-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 40px;
}
.city-grid li {
  font-size: 16px;
  font-weight: 700;
  padding: 11px 0;
  border-bottom: 1px solid var(--hairline);
}
@media (max-width: 380px) {
  .city-grid { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------------------
   18. CTA band
   -------------------------------------------------------------------------- */
.band-cta .container { max-width: 800px; }
.band-cta h2 {
  font-size: var(--fs-h2-cta);
  line-height: 1.2;
  margin: 0 0 14px;
  color: #fff;
  /* Cape Rock fills this 800px band, and About's longer line would otherwise
     break as "Ready to enhance your curb" over a lone "appeal?". */
  text-wrap: balance;
}
.band-cta h2 em { color: #fff; }
.band-cta p {
  font-size: 17px;
  line-height: 1.6;
  /* Full white: terracotta leaves only 4.28:1 even at full strength, so the
     0.88 alpha this used to carry gave away contrast the band cannot spare. */
  color: #fff;
  margin: 0 0 36px;
}
.cta-actions {
  display: flex;
  gap: 28px;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}

/* --------------------------------------------------------------------------
   19. Footer
   -------------------------------------------------------------------------- */
.site-footer {
  background: var(--ink);
  color: var(--muted-dark-bg);
  padding: 72px 0 36px;
  font-size: 14px;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr 1fr;
  gap: 48px;
}
.footer-brand img {
  height: 72px;
  width: auto;
  margin-bottom: 16px;
}
.footer-brand p {
  margin: 0;
  line-height: 1.7;
  max-width: 36ch;
}
.footer-col {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.footer-head {
  color: var(--cream);
  font-weight: 800;
  font-size: 12px;
  letter-spacing: 0.15em;
  margin-bottom: 6px;
}
.footer-col a {
  color: var(--muted-dark-bg);
  text-decoration: none;
  transition: color .2s var(--ease);
  width: fit-content;
}
.footer-col a:hover { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

.footer-bottom {
  max-width: var(--maxw);
  margin: 40px auto 0;
  padding: 22px var(--gutter) 0;
  border-top: 1px solid rgba(242, 238, 229, 0.12);
  font-size: 13px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px 28px;
  flex-wrap: wrap;
}
.footer-legal {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 24px;
}

@media (max-width: 900px) {
  .footer-grid { grid-template-columns: 1fr 1fr; gap: 36px; }
}
@media (max-width: 560px) {
  .footer-grid { grid-template-columns: 1fr; gap: 32px; }
  .footer-bottom { flex-direction: column; }
}

/* --------------------------------------------------------------------------
   20. Simple centred page (404)
   -------------------------------------------------------------------------- */
.plain-page {
  max-width: 720px;
  margin: 0 auto;
  padding: 120px var(--gutter) 140px;
  text-align: center;
}
.plain-page h1 { margin-bottom: 20px; }
.plain-page p {
  font-size: var(--fs-lead);
  color: var(--muted);
  margin: 0 0 36px;
}
.plain-links {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 20px 28px;
}
@media (max-width: 767px) {
  .plain-page { padding-top: 72px; padding-bottom: 88px; }
}

/* --------------------------------------------------------------------------
   21. Motion (all gated behind prefers-reduced-motion: no-preference)
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  @keyframes rise {
    from { opacity: 0; transform: translateY(28px); }
    to { opacity: 1; transform: none; }
  }
  @keyframes drawline {
    from { transform: scaleX(0); }
    to { transform: scaleX(1); }
  }

  .rise { animation: rise .8s both var(--ease); }
  .rise-1 { animation: rise .7s .05s both var(--ease); }
  .rise-2 { animation: rise .8s .15s both var(--ease); }
  .rise-3 { animation: rise .8s .28s both var(--ease); }
  .rise-4 { animation: rise .8s .40s both var(--ease); }
  .rise-5 { animation: rise .8s .52s both var(--ease); }
  .rise-media { animation: rise .9s .30s both var(--ease); }
  .drawline { animation: drawline .8s .40s both var(--ease); }

  /* Hero: layers hand the frame over on a slow dissolve. Only the incoming
     layer animates; the outgoing one holds at full opacity underneath and
     drops out once the new frame has fully arrived. Photos also drift, on a
     transition long enough that the outgoing one barely creeps back while it
     waits. Footage is already moving, so it only fades. */
  .hero-still { transition: opacity 0s var(--ease) 1.6s, transform 15.5s linear; }
  .hero-still.is-current { transition: opacity 1.5s var(--ease) 0s, transform 15.5s linear; }
  .hero-still--drift-a.is-drifting { transform: scale(1.06) translate3d(-1.4%, -0.7%, 0); }
  .hero-still--drift-b.is-drifting { transform: scale(1.06) translate3d(1.4%, 0.7%, 0); }
  .hero-clip { transition: opacity 0s var(--ease) 1.3s; }
  .hero-clip.is-current { transition: opacity 1.2s var(--ease) 0s; }

  .btn-solid:hover,
  .btn-cream:hover,
  .header-cta:hover {
    transform: translateY(-3px);
    box-shadow: 0 16px 32px -14px rgba(87, 42, 17, 0.55);
  }
  .header-cta:hover { transform: translateY(-2px); }
  /* The ghost pill lifts with its sibling, but never while it is holding the
     dialog open underneath it. */
  .header-call:hover:not([aria-expanded="true"]) {
    transform: translateY(-2px);
    box-shadow: 0 16px 32px -14px rgba(87, 42, 17, 0.55);
  }
  .btn-cream:hover { box-shadow: 0 16px 32px -14px rgba(0, 0, 0, 0.4); }

  /* Scroll reveal, only when JS is running so no-JS keeps content visible */
  .js .reveal {
    opacity: 0;
    transform: translateY(20px);
    transition: opacity .7s var(--ease), transform .7s var(--ease);
  }
  .js .reveal.is-visible {
    opacity: 1;
    transform: none;
  }
  .js .reveal-delay-1 { transition-delay: .08s; }
  .js .reveal-delay-2 { transition-delay: .16s; }
  .js .reveal-delay-3 { transition-delay: .24s; }

  /* Revealed elements own their transform, so hover lifts must out-rank
     .reveal.is-visible { transform: none }. */
  .js .svc-tile.reveal.is-visible,
  .js .ba-pair.reveal.is-visible,
  .js .media-fill.reveal.is-visible {
    transition: opacity .7s var(--ease), transform .35s var(--ease);
  }
  @media (hover: hover) {
    .js .svc-tile.reveal.is-visible:hover { transform: translateY(-8px); }
    .js .ba-pair.reveal.is-visible:hover,
    .js .media-fill.reveal.is-visible:hover { transform: translateY(-6px); }
  }
}

/* --------------------------------------------------------------------------
   23. Spacing utilities (keeps production markup free of inline styles)
   -------------------------------------------------------------------------- */
.mt-28 { margin-top: 28px; }
.mb-32 { margin-bottom: 32px; }
.mb-48 { margin-bottom: 48px; }
@media (max-width: 767px) {
  .mb-48 { margin-bottom: 32px; }
}

/* --------------------------------------------------------------------------
   24. Custom builds montage (Home)

   An editorial run, not a uniform grid. A twelve column track carries five
   deliberately unequal cells: the landscape lattice-top shot opens wide, the
   build-action loop stands tall beside it through two rows, two portrait
   cells fill in under the wide one, and a closing cell shares its row with
   the copy. The offset hairline from section 9 marks the two wide cells; the
   two captioned cells carry the words instead, so no cell does both.

   Every box is sized by the grid track and the media fills it with
   object-fit: cover, so the section reserves its full height before a single
   image arrives and nothing shifts while they stream in.
   -------------------------------------------------------------------------- */
.cb-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: 300px 440px 300px;
  /* Row gap is the wider one on purpose: captions hang in it, so every photo
     can end on the grid line whether its cell carries words or not. */
  gap: 44px 32px;
  margin-top: 24px;
}

.cb-cell {
  position: relative;
  min-width: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
}

/* Drawn on the cell rather than the media, so the photo can slide off its
   rule on hover the way .frame does. */
.cb-cell--framed::before {
  content: "";
  position: absolute;
  inset: 20px -20px -20px 20px;
  border: 1px solid var(--hairline-strong);
  pointer-events: none;
}

.cb-media {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: hidden;
  background: var(--hairline);
}
.cb-media > img,
.cb-media > video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* A portrait frame in a landscape box: the patio and chairs sit below the
   middle of the frame, so the crop is biased down instead of centred. */
.cb-media--lower > img { object-position: center 62%; }

/* The loop and its still share one box, stacked. The still is the whole
   story for reduced motion, Save-Data, 2G, and no-JS visitors: main.js is
   the only thing that ever gives the <video> a src, so those visitors
   request no mp4 at all. The video fades up once it is genuinely playing. */
.cb-media--loop > img,
.cb-media--loop > video {
  position: absolute;
  inset: 0;
}
.cb-media--loop > video { opacity: 0; }
.cb-media--loop > video.is-current { opacity: 1; }

/* Einer, small, muted. Deliberately not small caps: the eyebrow ration on
   Home is already spent, and these describe rather than label.

   Lifted out of the flow and into the row gap below its cell. In flow it
   would shorten only the cells that carry it, which left the captioned
   photos ending 32px above their uncaptioned neighbours in the same row. */
.cb-caption {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin: 10px 0 0;
  font-family: var(--font-body);
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
}

.cb-a { grid-column: 1 / 8;  grid-row: 1; }
.cb-b { grid-column: 8 / 13; grid-row: 1 / 3; }
.cb-c { grid-column: 1 / 4;  grid-row: 2; }
.cb-d { grid-column: 4 / 8;  grid-row: 2; }
.cb-e { grid-column: 1 / 7;  grid-row: 3; }

.cb-copy {
  grid-column: 8 / 13;
  grid-row: 3;
  align-self: end;
  min-width: 0;
}
.cb-copy p { margin: 0 0 20px; max-width: 46ch; }
.cb-copy p:last-child { margin-bottom: 0; }

@media (max-width: 1119px) {
  /* The columns narrow faster than the rows do, so the rows come down with
     them: at 380px the middle row turned the portrait cells into slivers. */
  .cb-grid {
    grid-template-rows: 240px 320px 240px;
    gap: 40px 26px;
  }
  .cb-cell--framed::before { inset: 16px -16px -16px 16px; }
}

@media (max-width: 899px) {
  .cb-grid {
    grid-template-columns: 1fr;
    grid-template-rows: auto;
    gap: 30px;
    margin-top: 20px;
  }
  .cb-a, .cb-b, .cb-c, .cb-d, .cb-e, .cb-copy {
    grid-column: 1;
    grid-row: auto;
  }
  .cb-copy { align-self: auto; }
  /* Stacked, there is no neighbouring cell to line up with, so the caption
     goes back in the flow directly under its own photo. */
  .cb-caption {
    position: static;
    flex: none;
    margin-top: 12px;
  }
  /* Stacked cells run to a fixed band so the section does not become a
     scroll wall. The loop is the exception: it keeps a portrait shape,
     contained rather than filling the viewport. */
  .cb-cell { height: 260px; }
  .cb-b { height: auto; }
  .cb-b .cb-media {
    flex: none;
    aspect-ratio: 3 / 4;
    max-height: 60vh;
  }
  /* The stacked band is narrow enough that a centred crop of the tropical
     frame keeps only boards. Bias it up so the palms the caption names are
     still in the picture. */
  .cb-d .cb-media > img { object-position: center 34%; }
  .cb-cell--framed::before { inset: 14px -14px -14px 14px; }
}

@media (max-width: 480px) {
  .cb-grid { gap: 26px; }
  .cb-cell { height: 220px; }
  /* Restated: the shorter band above would otherwise win over the auto
     height the loop cell needs to keep its portrait shape. */
  .cb-b { height: auto; }
  .cb-caption { font-size: 13px; }
}

@media (prefers-reduced-motion: no-preference) {
  .cb-media { transition: transform .35s var(--ease); }
  .cb-media--loop > video { transition: opacity .6s var(--ease); }

  /* The lift lives on the media, not on the cell, so it never argues with
     the transform .reveal.is-visible owns. Section 21 has to special-case
     .svc-tile and .ba-pair for exactly that reason. */
  @media (hover: hover) and (min-width: 900px) {
    .cb-cell:hover .cb-media { transform: translate(-5px, -5px); }
  }
}

/* --------------------------------------------------------------------------
   22. Print
   -------------------------------------------------------------------------- */
@media print {
  .site-header,
  .call-pop,
  .call-scrim,
  .nav-panel,
  .skip-link,
  .band-cta,
  video { display: none !important; }

  body {
    background: #fff;
    color: #000;
    font-size: 11.5pt;
    line-height: 1.5;
  }
  .band-dark, .band-roast, .site-footer {
    background: #fff !important;
    color: #000 !important;
    padding: 18pt 0;
  }
  .band-dark .eyebrow,
  .quote-source,
  .svc-tile,
  .svc-tile-name,
  .footer-head,
  .footer-col a,
  .quote-text { color: #000 !important; }
  .section, .band-dark, .band-roast { padding: 18pt 0; break-inside: avoid; }
  .frame::before { display: none; }
  .hero { padding: 12pt 0; gap: 18pt; grid-template-columns: 1fr; }
  .hero-media, .split-media, .split-media--short, .svc-tile-media, .ba-images { height: 150pt; }
  .cb-grid { grid-template-columns: 1fr; grid-template-rows: auto; gap: 12pt; }
  .cb-a, .cb-b, .cb-c, .cb-d, .cb-e, .cb-copy { grid-column: 1; grid-row: auto; }
  .cb-cell { height: 150pt; break-inside: avoid; }
  .cb-cell--framed::before { display: none; }
  a[href^="http"]::after,
  a[href^="tel"]::after { content: " (" attr(href) ")"; font-size: 8.5pt; word-break: break-all; }
  .nav a::after { content: none; }
  img { break-inside: avoid; }
}
