/* ============================================================
   Apex Build Pro — "The Site Board" (direction A, approved 2026-08-29)
   Charcoal ground like a painted site hoarding, bone type, one signal red
   for every action. The client's logo is navy + gold; neither is used as a
   UI colour anywhere, so the logo is the only navy/gold thing on the page.
   Signature carried over from "The Job File": ridge dividers, mono record
   labels (SVC 01, Fig. 02), hatched placeholders, photo-first tiles.
   ============================================================ */

/* Self-hosted type (Fontshare: Clash Display + General Sans, ITF Free Font License;
   IBM Plex Mono, OFL). No Google Fonts request; the logo wordmark is outlined SVG. */
@font-face {
  font-family: "Clash Display";
  src: url("/assets/fonts/ClashDisplay-Variable.woff2") format("woff2");
  font-weight: 200 700;
  font-display: swap;
}
@font-face {
  font-family: "General Sans";
  src: url("/assets/fonts/GeneralSans-Variable.woff2") format("woff2");
  font-weight: 200 700;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("/assets/fonts/ibm-plex-mono-v20-latin-regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("/assets/fonts/ibm-plex-mono-v20-latin-500.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
}

:root {
  /* Colour */
  --ground: #1f2326;       /* body ground */
  --ground-2: #2a2f33;     /* raised dark panel */
  --deep: #15181a;         /* utility strip, footer */
  --line: #3a4046;         /* rules on dark */
  --bone: #f1ede4;         /* text on dark, light panels */
  --bone-2: #d6cfbd;       /* recessed tone on --panel (placeholders); must stay DARKER than --panel */
  --bone-3: #cfc9bc;       /* muted text on dark (AA on --ground) */
  --ink: #1c1f22;          /* text on light */
  --ink-soft: #454b51;     /* muted text on light (AA on --bone) */
  --line-light: #c9c2b2;   /* rules on light */
  /* The ground of a light SECTION, distinct from --bone, which is a text colour.
     A full-screen #f1ede4 field measured 0.85 relative luminance and the owner
     said it hurt to look at (2026-09-03). This is 15% darker and still 12.1:1
     with --ink. Cards like .quote-card stay --bone: small bright areas read as
     paper, whole screens read as glare. */
  --panel: #e2dccd;
  --signal: #d63d22;       /* the one action colour (4.6:1 with white) */
  --signal-ink: #a32d13;   /* red as TEXT on --panel (5.2:1); --signal-deep is only 4.3:1 there */
  --signal-text: #ff7a5c;  /* small red text on dark grounds (6.2:1) */
  --signal-deep: #b93518;  /* hover, and text-on-bone (AA) */
  --ok: #3f8f4a;

  /* Type */
  --display: "Clash Display", "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --body: "General Sans", "Segoe UI", system-ui, sans-serif;
  --mono: "IBM Plex Mono", "Courier New", monospace;

  /* Rhythm */
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.5rem;
  --space-4: 2.5rem;
  --space-5: 4rem;
  --space-6: 6.5rem;
  --measure: 62ch;
  --wrap: 94rem;          /* fills a big screen; text blocks still cap at --measure */
  --wrap-narrow: 76rem;
  --chrome-h: calc(2.4rem + 4.5rem + 2px);  /* util strip + header, for the home overlay */

  /* Motion. Open/close are asymmetric: things arrive slower than they leave. */
  --t-fast: 150ms;
  --t-med: 260ms;
  --t-open: 220ms;
  --t-close: 150ms;
  --ease: cubic-bezier(0.3, 0.7, 0.3, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  /* Text load-in. Ported from 21st.dev Text Reveal (Mask) / Words Stagger.
     The whole split-reveal system is tunable from these five values: drop
     --reveal-blur to 0 and --stagger-word to 0 to reduce it to a plain fade. */
  --t-reveal: 620ms;       /* one word's or line's arrival */
  --stagger-word: 55ms;    /* between words in a heading */
  --stagger-line: 90ms;    /* between lines in prose */
  --reveal-rise: 130%;     /* mask travel, upstream yPercent:110, clears the descender pad */
  --reveal-blur: 6px;      /* upstream blur(10px), toned for this type */
}

/* ---------- Reset & base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@supports (interpolate-size: allow-keywords) { html { interpolate-size: allow-keywords; } }
body {
  margin: 0;
  background: var(--ground);
  color: var(--bone);
  font-family: var(--body);
  font-size: 1.125rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
img, svg, video { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: #fff; }
:focus-visible { outline: 3px solid var(--signal); outline-offset: 2px; border-radius: 2px; }

h1, h2, h3 {
  font-family: var(--display);
  line-height: 1.02;
  letter-spacing: 0.005em;
  margin: 0 0 var(--space-2);
  text-wrap: balance;
}
h1 { font-size: clamp(3rem, 7vw, 6rem); font-weight: 640; text-transform: uppercase; }
h2 { font-size: clamp(2.2rem, 4vw, 4rem); font-weight: 620; text-transform: uppercase; }
h3 { font-size: 1.45rem; font-weight: 600; }
p { margin: 0 0 var(--space-2); max-width: var(--measure); }
ul { padding-left: 1.2rem; }
li { margin-bottom: 0.4rem; max-width: var(--measure); }

.wrap { max-width: var(--wrap); margin-inline: auto; padding-inline: clamp(1.1rem, 4vw, 2.5rem); }

/* Light panels invert the text colours. Any section can be light. */
.on-light { background: var(--panel); color: var(--ink); }
.on-light a:hover { color: var(--signal-ink); }

/* Eyebrow: the record label */
.eyebrow {
  font-family: var(--mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bone-3);
  display: block;
  margin-bottom: var(--space-2);
}
.eyebrow b { color: var(--signal-text); font-weight: 500; }
.on-light .eyebrow { color: var(--ink-soft); }
.on-light .eyebrow b { color: var(--signal-ink); }

.lede { font-size: 1.2rem; color: var(--bone-3); max-width: 56ch; }
.on-light .lede { color: var(--ink-soft); }

/* ---------- The ridge line (signature) ---------- */
/* A pitched roofline as a divider. Colour it the same as the section it leads into. */
.ridge {
  height: 44px;
  background: var(--panel);
  clip-path: polygon(0 100%, 0 60%, 12% 60%, 22% 0, 32% 60%, 100% 60%, 100% 100%);
  margin-bottom: -1px;
}
/* Flipped ridge: sits after a hero and points down into the next section. The
   element is the colour of the section it leads INTO; the tooth (::before) is
   the colour of the hero above it, so nothing of the page ground shows through. */
.ridge--flip {
  clip-path: none;
  margin-top: -1px;
  margin-bottom: 0;
  --ridge-from: var(--ground-2);
}
.ridge--flip::before {
  content: "";
  display: block;
  height: 100%;
  background: var(--ridge-from);
  clip-path: polygon(0 0, 100% 0, 100% 40%, 88% 40%, 78% 100%, 68% 40%, 0 40%);
}
.ridge--right { clip-path: polygon(0 100%, 0 60%, 68% 60%, 78% 0, 88% 60%, 100% 60%, 100% 100%); }
.ridge--ground { background: var(--ground); }
.ridge--deep { background: var(--deep); }
/* The red survives as the roofline itself. Against the old red section this
   was invisible, one mass of colour; against the dark panel it is a pitched
   red line leading into the band, which is the signature doing its job. */
.ridge--accent { background: var(--signal-deep); }
.ridge--bone { background: var(--panel); }   /* named for the old token; it is the light panel ground */
/* A flipped ridge coming down out of a bone panel (the default --ridge-from is
   the hero tone, which is invisible against a bone section above). */
.ridge--from-bone { --ridge-from: var(--panel); }

/* ---------- Utility strip: the phone number is always at the top ---------- */
.util {
  background: var(--deep);
  border-bottom: 1px solid var(--line);
  font-size: 0.9rem;
  color: var(--bone-3);
}
.util .wrap { display: flex; justify-content: space-between; align-items: center; min-height: 2.4rem; gap: var(--space-2); }
.util .tel {
  font-family: var(--mono);
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--bone);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  white-space: nowrap;
}
.util .tel:hover { color: #fff; text-decoration: underline; }
.util .hours { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.util .hours + .hours { border-left: 1px solid var(--line); padding-left: var(--space-2); }
.util .hours-group { display: flex; gap: var(--space-2); align-items: center; min-width: 0; }

/* ---------- Header ---------- */
.site-header {
  background-color: rgba(31, 35, 38, 0.9);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  backdrop-filter: blur(12px) saturate(140%);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 50;
  transition: box-shadow var(--t-med) var(--ease),
              background-color var(--t-med) var(--ease),
              border-color var(--t-med) var(--ease);
}
/* Once you are off the hero the header settles into a translucent dark red.
   #5c1a0d is a darkened signal, not a new hue: bone holds 11:1 on it solid and
   8.7:1 at this alpha over the bone sections, so the nav stays AA either way. */
.site-header.is-scrolled {
  box-shadow: 0 10px 28px -20px rgba(0, 0, 0, 0.7);
  background-color: rgba(92, 26, 13, 0.82);
  border-bottom-color: rgba(214, 61, 34, 0.45);
}
@media (prefers-reduced-transparency: reduce) {
  .site-header { background-color: var(--ground); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .site-header.is-scrolled { background-color: #5c1a0d; }
}
.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: 4.5rem;
  /* Brand sits further left than the page content, as the client asked. */
  padding-left: clamp(0.5rem, 1.5vw, 1rem);
}
/* flex: none, or the header's flex row shrinks the brand while the fixed height
   holds, and the global img max-width:100% squashes the lockup out of shape.
   A logo may get smaller; it may never get narrower. */
.brand { display: block; flex: none; text-decoration: none; line-height: 0; }
.brand img { height: 3.4rem; width: auto; }
.site-nav { display: flex; align-items: center; gap: clamp(0.8rem, 2vw, 1.6rem); }
.site-nav a {
  font-family: var(--body);
  font-weight: 500;
  font-size: 0.98rem;
  color: var(--bone);
  text-decoration: none;
  padding: 0.35rem 0;
  border-bottom: 2px solid transparent;
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.site-nav a:hover { color: #fff; border-bottom-color: var(--bone-3); }
.site-nav a[aria-current="page"] { color: #fff; border-bottom-color: var(--signal); }
/* The nav CTA is a .btn; restore button styling that .site-nav a would override */
.site-nav a.btn-primary,
.site-nav a.btn-primary[aria-current="page"] {
  color: #fff;
  font-family: var(--display);
  font-size: 1.05rem;
  padding: 0.6rem 1.1rem;
  border: 0;
  border-radius: 0;
}
.site-nav a.btn-primary:hover { background: var(--signal-deep); }
.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--line);
  padding: 0.45rem 0.6rem;
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--bone);
  cursor: pointer;
  transition: transform var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.nav-toggle:active { transform: scale(0.97); border-color: var(--bone); }

/* Home: the chrome floats transparent over the full-screen video and turns solid
   the moment the page scrolls (or the mobile menu opens). */
.is-home .util { background: transparent; border-bottom-color: rgba(241, 237, 228, 0.16); position: relative; z-index: 51; }
.is-home .util .hours + .hours { border-left-color: rgba(241, 237, 228, 0.25); }
.is-home .site-header {
  background: transparent;
  border-bottom-color: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  transition: background var(--t-med) var(--ease), border-color var(--t-med) var(--ease), box-shadow var(--t-med) var(--ease);
}
.is-home .site-header.is-scrolled,
.is-home .site-header:has(.site-nav.open) {
  /* Same darkened signal as the base scrolled header. The home header starts
     transparent over the video, so this is where the red actually shows. */
  background: rgba(92, 26, 13, 0.82);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  backdrop-filter: blur(12px) saturate(140%);
  border-bottom-color: rgba(214, 61, 34, 0.45);
}
/* The open mobile menu hangs off the header and must stay legible against it,
   so the red is only for the scrolled state, never the open one. */
.is-home .site-header:has(.site-nav.open):not(.is-scrolled) {
  background: rgba(31, 35, 38, 0.92);
  border-bottom-color: var(--line);
}
@media (prefers-reduced-transparency: reduce) {
  .is-home .site-header:has(.site-nav.open):not(.is-scrolled) { background: var(--ground); }
  .is-home .site-header.is-scrolled { background: #5c1a0d; -webkit-backdrop-filter: none; backdrop-filter: none; }
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.1rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 0.8rem 1.4rem;
  border: 2px solid transparent;
  cursor: pointer;
  line-height: 1;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.btn-primary { background: var(--signal); border-color: var(--signal); color: #fff; }
.btn-primary:hover { background: var(--signal-deep); border-color: var(--signal-deep); color: #fff; }
.btn-ghost { background: transparent; color: var(--bone); border-color: var(--bone-3); }
.btn-ghost:hover { background: var(--bone); color: var(--ink); border-color: var(--bone); }
.on-light .btn-ghost { color: var(--ink); border-color: var(--ink); }
.on-light .btn-ghost:hover { background: var(--ink); color: var(--bone); }
.btn:active, .site-nav a.btn-primary:active { transform: translateY(1px) scale(0.985); }
.btn[disabled] { opacity: 0.6; cursor: default; }

/* ---------- Hero ---------- */
.hero {
  position: relative;
  overflow: hidden;
  padding: var(--space-6) 0 var(--space-5);
  isolation: isolate;
}
.hero .wrap {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: var(--space-5);
  align-items: center;
  position: relative;
  z-index: 1;
}
.hero h1 { color: var(--bone); }
/* The display word takes the lighter red: brighter on video, and 5.3:1 AA on the
   darkest ground it ever sits on. */
.hero h1 em { font-style: normal; color: var(--signal-text); }
.hero .lede { font-size: 1.25rem; }
.hero-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }
/* Photo hero (service pages): photo on the right, faint roofline behind. */
.hero--photo { background: var(--ground-2); }
.hero--photo::before {
  content: "";
  position: absolute;
  right: -6vw;
  bottom: -2rem;
  width: 62vw;
  max-width: 900px;
  aspect-ratio: 3 / 1;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 100' preserveAspectRatio='none'%3E%3Cpath d='M0 100 L110 12 L118 12 L300 100' fill='none' stroke='%23f1ede4' stroke-width='1.2' opacity='.14'/%3E%3C/svg%3E") no-repeat right bottom / 100% 100%;
  pointer-events: none;
}
/* Video hero (home): the loop fills the whole first screen, edge to edge, with the
   chrome floating over it. The poster shows until the video can play; with reduced
   motion or on small screens only the poster shows. */
.hero--video {
  min-height: 100svh;
  margin-top: calc(-1 * var(--chrome-h));
  display: grid;
  align-items: center;
  padding: calc(var(--chrome-h) + var(--space-4)) 0 var(--space-6);
  text-align: center;
}
.hero--video .hero-media { position: absolute; inset: 0; z-index: 0; background: var(--deep); }
.hero--video .hero-media video,
.hero--video .hero-media img {
  width: 100%; height: 100%; object-fit: cover;
  filter: saturate(0.7) contrast(1.05);
}
.hero--video .hero-media video { opacity: 0; transition: opacity 900ms var(--ease); }
.hero--video .hero-media video.is-playing { opacity: 1; }
.hero--video .hero-media img { position: absolute; inset: 0; }
.hero--video .hero-media::after {
  content: "";
  position: absolute; inset: 0;
  background:
    linear-gradient(0deg, var(--ground) 0%, rgba(31, 35, 38, 0.2) 30%, rgba(21, 24, 26, 0.55) 100%),
    rgba(21, 24, 26, 0.42);
}
.hero--video .wrap {
  display: block;
  max-width: 66rem;
  justify-self: center;
}
.hero--video h1 { font-size: clamp(3.2rem, 7.6vw, 7rem); }
.hero--video .lede { margin-inline: auto; }
.hero--video .hero-actions { justify-content: center; align-items: center; gap: var(--space-3); }
.hero-phone {
  font-family: var(--mono);
  font-size: 1.05rem;
  color: var(--bone);
  text-decoration: none;
  border-bottom: 1px solid var(--bone-3);
  padding-bottom: 2px;
}
.hero-phone:hover { color: #fff; border-bottom-color: #fff; }
/* Scroll cue pinned to the hero's bottom edge */
.hero-scroll {
  position: absolute;
  left: 50%;
  bottom: 1.4rem;
  transform: translateX(-50%);
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: 0.45rem;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  color: var(--bone-3);
  text-decoration: none;
}
.hero-scroll::after {
  content: "";
  width: 0.55rem;
  height: 0.55rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  animation: scroll-nudge 2.2s var(--ease) infinite;
}
.hero-scroll:hover { color: var(--bone); }
@keyframes scroll-nudge {
  0%, 100% { transform: rotate(45deg) translate(0, 0); opacity: 1; }
  50% { transform: rotate(45deg) translate(4px, 4px); opacity: 0.5; }
}

/* ---------- Quote card (home) and the wizard (quote page) ---------- */
.quote-card {
  background: var(--bone);
  color: var(--ink);
  padding: var(--space-3);
  box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.8);
}
.quote-card h2 { font-size: 1.6rem; margin-bottom: 0.3rem; }
.quote-card > p { color: var(--ink-soft); font-size: 0.95rem; margin-bottom: var(--space-2); }
.steps { display: flex; gap: 0.4rem; font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.04em; margin-bottom: var(--space-2); padding: 0; list-style: none; }
.steps li { flex: 1; padding: 0.35rem 0; border-top: 3px solid var(--line-light); color: var(--ink-soft); margin: 0; max-width: none; }
.steps li.is-on { border-color: var(--signal); color: var(--ink); }
.steps li.is-done { border-color: var(--ink); }
.opts { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: var(--space-2); }
.opts--3 { grid-template-columns: repeat(3, 1fr); }
.opts label {
  border: 1.5px solid var(--line-light);
  padding: 0.7rem 0.8rem;
  font-weight: 500;
  font-size: 0.98rem;
  cursor: pointer;
  display: flex; gap: 0.5rem; align-items: center;
  margin: 0;
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.opts label:hover { border-color: var(--ink); }
.opts label:has(input:checked) { border-color: var(--signal); background: #fff; }
.opts input { accent-color: var(--signal); margin: 0; width: auto; }
.quote-card .tel { display: block; margin-top: 0.9rem; font-size: 0.92rem; color: var(--ink-soft); }
.quote-card .tel b { font-family: var(--mono); font-weight: 500; color: var(--ink); }

.wizard { max-width: 48rem; }
.wizard fieldset { border: 0; padding: 0; margin: 0 0 var(--space-3); }
.wizard legend { font-family: var(--display); font-weight: 700; font-size: 1.5rem; text-transform: uppercase; margin-bottom: 0.8rem; }
.wizard [data-step] { display: none; }
.wizard [data-step].is-on { display: block; }
.wizard .actions { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.wizard .summary { background: #fff; border: 1px solid var(--line-light); padding: var(--space-2) var(--space-3); margin-bottom: var(--space-3); }
.wizard .summary dl { display: grid; grid-template-columns: max-content 1fr; gap: 0.3rem 1rem; margin: 0; font-size: 0.98rem; }
.wizard .summary dt { font-family: var(--mono); font-size: 0.78rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-soft); }
.wizard .summary dd { margin: 0; }
.wizard .hint { font-size: 0.92rem; color: var(--ink-soft); }

/* ---------- Figures: the record ---------- */
figure.record { margin: 0; }
figure.record img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
figure.record img.crop-b { object-position: center 78%; }
figure.record .portrait { aspect-ratio: 3 / 4; }
figure.record figcaption {
  font-family: var(--mono);
  font-size: 0.74rem;
  line-height: 1.5;
  color: var(--bone-3);
  padding-top: 0.55rem;
  border-top: 1px solid var(--line);
  margin-top: 0.55rem;
}
.on-light figure.record figcaption { color: var(--ink-soft); border-top-color: var(--line-light); }
figure.record figcaption strong { color: var(--signal-text); font-weight: 500; }
.on-light figure.record figcaption strong { color: var(--signal-ink); }

/* Video figure: a short muted loop of the crew at work, beside the text. */
figure.record video { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; background: var(--deep); }
figure.record .portrait-video { aspect-ratio: 9 / 16; max-height: 32rem; }

/* Placeholder: architectural hatching = "material to be confirmed".
   Swapped for real photography without any layout shift. */
.ph {
  width: 100%;
  border: 1px solid var(--line);
  background:
    repeating-linear-gradient(45deg, transparent 0 9px, rgba(241, 237, 228, 0.10) 9px 10px),
    var(--ground-2);
  display: grid;
  place-items: center;
  position: relative;
}
.on-light .ph {
  border-color: var(--line-light);
  background:
    repeating-linear-gradient(45deg, transparent 0 9px, rgba(28, 31, 34, 0.12) 9px 10px),
    var(--bone-2);
}
.ph::after {
  content: "PHOTOGRAPH TO FOLLOW";
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.18em;
  color: var(--bone-3);
  background: var(--ground);
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--line);
}
.on-light .ph::after { color: var(--ink-soft); background: var(--panel); border-color: var(--line-light); }
.ph-4x3 { aspect-ratio: 4 / 3; }
.ph-3x4 { aspect-ratio: 3 / 4; }
.ph-16x9 { aspect-ratio: 16 / 9; }
.ph-1x1 { aspect-ratio: 1 / 1; }

/* ---------- Service tiles (the twelve-service index) ---------- */
/* Photo tiles with the label over the bottom edge, hairline gaps like a board of prints. */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 2px;
  background: var(--line);
  border: 2px solid var(--line);
}
.tile {
  display: block;
  position: relative;
  text-decoration: none;
  color: var(--bone);
  background: var(--ground);
  aspect-ratio: 1 / 1.15;
  overflow: hidden;
}
.tile-media { position: absolute; inset: 0; }
.tile-media .ph { border: 0; height: 100%; }
.tile-media .ph::after { content: "PHOTO TO FOLLOW"; font-size: 0.66rem; letter-spacing: 0.16em; }
.tile-media img {
  width: 100%; height: 100%; object-fit: cover;
  filter: saturate(0.85);
  transition: transform 600ms var(--ease-out), filter var(--t-med) var(--ease);
}
.tile:hover .tile-media img { transform: scale(1.04); filter: saturate(1); }
.tile:active .tile-media img { transform: scale(1.01); }
.tile-cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 2.4rem 1rem 1rem;
  background: linear-gradient(0deg, rgba(21, 24, 26, 0.96) 0%, rgba(21, 24, 26, 0.7) 55%, transparent 100%);
  /* The description row opens with grid-template-rows (0fr -> 1fr), which the
     compositor handles without a layout-thrashing height animation. */
  display: grid;
  grid-template-rows: auto auto 0fr;
  transition: grid-template-rows var(--t-open) var(--ease-out);
}
.tile:hover .tile-cap, .tile:focus-visible .tile-cap { grid-template-rows: auto auto 1fr; }
.tile-ref { font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.14em; color: var(--bone-3); display: block; }
.tile h3 { margin: 0.1rem 0 0; font-size: 1.45rem; text-transform: uppercase; transition: color var(--t-fast) var(--ease); }
.tile:hover h3 { color: #fff; }
.tile p { margin: 0; font-size: 0.9rem; color: var(--bone-3); min-height: 0; overflow: hidden; opacity: 0; transition: opacity var(--t-open) var(--ease-out); }
.tile:hover p, .tile:focus-visible p { opacity: 1; margin-top: 0.2rem; }

/* ---------- Before / after records: every pair on the page, no picker ----------
   Client feedback 2026-08-30: show all the pairs stacked as you scroll, one big
   slider each, instead of a record list you have to click through. */
.ba-stack { display: grid; gap: var(--space-6); }
.ba-item {
  display: grid;
  grid-template-columns: minmax(0, 7.5fr) minmax(0, 4.5fr);
  gap: var(--space-5);
  align-items: center;
}
.ba-item:nth-child(even) > .ba { order: 1; }
/* Reordering alone drops the image into the narrow 4.5fr track and shrinks it
   by 40%. Swap the tracks too, so the sides alternate but the image stays wide. */
.ba-item:nth-child(even) { grid-template-columns: minmax(0, 4.5fr) minmax(0, 7.5fr); }
.ba-meta .ref {
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--signal-text);
  display: block;
  margin-bottom: 0.6rem;
}
.ba-meta h3 { font-size: clamp(1.6rem, 2.4vw, 2.3rem); text-transform: uppercase; margin-bottom: 0.5rem; }
.ba-meta p { color: var(--bone-3); margin-bottom: 0.4rem; }
.ba-meta .ba-hint { margin-top: var(--space-2); }
.ba {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--ground-2);
  user-select: none;
  -webkit-user-select: none;
  touch-action: pan-y;
}
.ba img, .ba .ph { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; pointer-events: none; border: 0; }
.ba .ph::after { display: none; }
.ba .after { clip-path: inset(0 0 0 var(--x, 50%)); }
.ba .handle {
  position: absolute; top: 0; bottom: 0; left: var(--x, 50%);
  width: 2px; background: var(--bone); transform: translateX(-1px);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);
}
.ba .knob {
  position: absolute; top: 50%; left: var(--x, 50%);
  transform: translate(-50%, -50%);
  width: 2.8rem; height: 2.8rem; border-radius: 50%;
  background: var(--bone); border: 2px solid var(--ink); color: var(--ink);
  display: grid; place-items: center;
  font-family: var(--display); font-weight: 700; font-size: 0.8rem; letter-spacing: -0.05em;
  cursor: ew-resize;
  box-shadow: 0 6px 18px -8px rgba(0, 0, 0, 0.8);
  transition: transform 120ms var(--ease);
}
.ba.is-dragging .knob { transform: translate(-50%, -50%) scale(0.94); }
.ba .tag {
  position: absolute; top: 0.75rem;
  font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--bone); background: rgba(21, 24, 26, 0.82);
  padding: 0.3rem 0.55rem;
}
.ba .tag.before { left: 0.75rem; }
.ba .tag.after-tag { right: 0.75rem; }
.ba-hint { font-family: var(--mono); font-size: 0.78rem; color: var(--bone-3); margin: 0; }

/* ---------- Sections ---------- */
.section { padding: var(--space-6) 0; }
.section--tight { padding: var(--space-5) 0; }
.section--panel { background: var(--panel); color: var(--ink); }         /* light */
.section--deep { background: var(--deep); }                               /* darker */
/* Was a full-bleed signal-red field. The owner asked for it to go twice
   (2026-09-02, 2026-09-03): at full width the red is glare, not emphasis.
   Now a raised dark panel, marked by a red rule along the top so the band is
   still clearly its own thing and the colour still means "act on this". */
/* No border here: the .ridge--accent above it carries the red, and a straight
   rule under a pitched roofline reads as a mistake. */
.section--accent {
  background: var(--ground-2);
  color: var(--bone);
}
.section--accent h2, .section--accent h3 { color: #fff; }
.section--accent .lede { color: var(--bone-3); }
.section-head { max-width: 56ch; margin-bottom: var(--space-4); }
.section-head--row { max-width: none; display: flex; justify-content: space-between; align-items: end; gap: var(--space-4); }
.section-head--row p { margin: 0; max-width: 30rem; }
.section-head--center { margin-inline: auto; text-align: center; }
.section-head--center .lede { margin-inline: auto; }

/* ---------- Quote band: the three-step card in its own row under the hero ---------- */
.quote-band .wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: var(--space-5);
  align-items: center;
}
.quote-band .cta-phone { font-size: 1.2rem; }

/* ---------- Full-bleed photo band: one job, the whole width of the screen ---------- */
.band-photo { position: relative; overflow: hidden; }
.band-photo video,
.band-photo img {
  width: 100%;
  height: clamp(20rem, 58vh, 40rem);
  object-fit: cover;
  filter: saturate(0.85);
}
.band-photo .band-cap {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 2.6rem clamp(1.1rem, 4vw, 2.5rem) 1rem;
  background: linear-gradient(0deg, rgba(21, 24, 26, 0.85) 0%, transparent 100%);
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bone);
}

/* ---------- Roof pair ----------
   Two views of one roof, held inside the wrap rather than run edge to edge:
   full bleed made a single photo the loudest thing on the page and it sat
   oddly against the restraint everywhere else. Asymmetric and staggered so it
   reads as a composed plate, not a gallery row, and distinct from the evenly
   sized stage frames in the record band above it. */
.roof-pair {
  display: grid;
  /* Equal columns and one aspect ratio: the two plates line up top and bottom.
     They used to be 6.4fr/4.6fr with the second dropped a --space-5 below the
     first, which the owner read as one being higher than the other rather than
     as a composition. Matched plates say "two jobs, same standard". */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
  align-items: start;
}
.roof-plate { margin: 0; position: relative; }
.plate-media {
  overflow: hidden;
  aspect-ratio: 4 / 3;
  border: 1px solid var(--line);
  transition: border-color 260ms var(--ease);
}
.roof-plate img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.85);
  /* scale and translate as INDIVIDUAL transform properties, not one transform
     string. The scroll drift writes --drift-y every frame and must land
     instantly; the hover scale has to ease over 460ms. One transform property
     cannot do both -- a transition on it would rubber-band the drift -- but
     `translate` and `scale` are separate properties with separate transitions.
     Base scale is oversized so the drift never exposes an edge in the frame. */
  scale: var(--plate-scale, 1.06);
  translate: 0 var(--drift-y, 0px);
  will-change: translate, scale;
  transition: scale 460ms var(--ease-out), filter 460ms var(--ease-out);
}
.roof-plate figcaption {
  position: relative;
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.1em;
  color: var(--bone-3);
  margin-top: 0.9rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--line);
}
/* The rule above the caption fills red from the left on hover. Same vocabulary
   as the lead rules on the jobsheet steps, so the gesture is already familiar. */
.roof-plate figcaption::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 100%;
  height: 1px;
  background: var(--signal);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 320ms var(--ease-out);
}
/* Gated: a touch tap fires a false hover and would leave a plate stuck zoomed.
   460ms is over the 300ms UI budget on purpose -- this is a photograph on a
   marketing page, not a control, and a fast zoom reads as a twitch. Nothing
   here implies the plate is clickable: no lift, no shadow, no pointer cursor. */
@media (hover: hover) and (pointer: fine) {
  .roof-plate:hover img { --plate-scale: 1.13; filter: saturate(1); }
  .roof-plate:hover .plate-media { border-color: var(--bone-3); }
  .roof-plate:hover figcaption::before { transform: scaleX(1); }
}
/* 70ms apart: enough that the pair arrives as two things, not a slab. */
.roof-pair > .reveal:nth-child(2) { transition-delay: 70ms; }
.roof-plate .plate-ref {
  display: block;
  color: var(--signal-text);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-bottom: 0.3rem;
}

/* ---------- Trust band: three promises ---------- */
.promises { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4); padding: 0; margin: 0; list-style: none; }
.promises li { margin: 0; max-width: none; }
.promises h3 { font-size: 1.7rem; text-transform: uppercase; margin-bottom: 0.3rem; }
.promises p { margin: 0; }
.section--accent .promises p { color: var(--bone-3); }
.section--accent .promises a { color: #fff; }

/* ---------- Materials section: head + example figures, cards below ---------- */
.matgrid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: var(--space-3) var(--space-5);
  align-items: start;
}
.matgrid .svc-grid { grid-column: 1 / -1; }
.materials-ex { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.materials-ex figure:first-child { grid-column: 1 / -1; }
@media (max-width: 56rem) {
  .matgrid { grid-template-columns: 1fr; }
}

/* ---------- Service cards (material choices, promises) ---------- */
.svc-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: var(--space-3); }
.svc {
  background: var(--ground-2);
  border: 1px solid var(--line);
  padding: var(--space-3);
  display: flex; flex-direction: column; gap: 0.6rem;
  transition: border-color var(--t-fast) var(--ease), transform var(--t-med) var(--ease);
}
.on-light .svc { background: #fff; border-color: var(--line-light); }
.svc:hover { border-color: var(--bone-3); transform: translateY(-3px); }
.svc .svc-ref { font-family: var(--mono); font-size: 0.74rem; letter-spacing: 0.14em; color: var(--bone-3); }
.on-light .svc .svc-ref { color: var(--ink-soft); }
.svc h3 { margin: 0; text-transform: uppercase; }
.svc p { color: var(--bone-3); font-size: 1rem; margin: 0; }
.on-light .svc p { color: var(--ink-soft); }

/* ---------- The job sheet (the process component, every page) ----------
   Replaces the four-equal-column process grid, which read as a template: equal
   columns, a centred header over left-aligned content, big 1-2-3-4 numerals and
   a rule above every column. This is an asymmetric split instead. The heading
   holds a left rail, the steps run down the right as ruled rows with a single
   hairline BETWEEN them, and the numerals are gone: the step title is the label.

   The quote and the photos lead because they are what the customer actually
   receives; the visit and the work are what happens in between. That is the
   hierarchy the old block flattened.

   Rolled out to all six pages 2026-09-01, so the old `.process` component is
   gone. Grounds differ: epc and quote sit on charcoal, the rest on bone. */
.jobsheet { display: grid; gap: var(--space-4); }
.jobsheet-head h2 { margin-bottom: var(--space-2); }
.jobsheet-head .lede { margin: 0; }

/* Grounds follow the .process convention: base is the dark ground, .on-light
   overrides for bone panels. Used on both here (epc and quote are charcoal). */
.jobsheet-steps { list-style: none; margin: 0; padding: 0; }
.jobsheet-steps li { padding: var(--space-3) 0; max-width: var(--measure); }
.jobsheet-steps li + li { border-top: 1px solid var(--line); }
.jobsheet-steps h3 { font-size: 1.4rem; font-weight: 600; margin: 0 0 0.35rem; }
.jobsheet-steps p { margin: 0; font-size: 1.0625rem; color: var(--bone-3); }
.on-light .jobsheet-steps li + li { border-top-color: var(--line-light); }
.on-light .jobsheet-steps p { color: var(--ink-soft); }

/* The first and last step lead: what starts the job, and what you are left
   holding. The red rule marks them and is the only signal colour on the panel. */
.jobsheet-steps li[data-lead] { padding-block: var(--space-4); }
.jobsheet-steps li[data-lead] h3 {
  font-size: 1.75rem;
  font-weight: 620;
  line-height: 1.08;
  letter-spacing: 0.005em;
  margin-bottom: 0.5rem;
}
.jobsheet-steps li[data-lead] h3::before {
  content: "";
  display: block;
  width: 2.5rem;
  height: 3px;
  background: var(--signal-text);   /* the documented red for dark grounds */
  margin-bottom: 0.9rem;
}
.jobsheet-steps li[data-lead] p { color: var(--bone); font-size: 1.125rem; }
.on-light .jobsheet-steps li[data-lead] h3::before { background: var(--signal); }
.on-light .jobsheet-steps li[data-lead] p { color: var(--ink); }

/* Reveal cascade down the rows */
.jobsheet-steps > .reveal:nth-child(2) { transition-delay: 60ms; }
.jobsheet-steps > .reveal:nth-child(3) { transition-delay: 120ms; }
.jobsheet-steps > .reveal:nth-child(4) { transition-delay: 180ms; }

@media (min-width: 64rem) {
  .jobsheet {
    /* The right column is sized to the text measure rather than 1fr: at a 94rem
       wrap a 1fr column leaves the row rules stopping in open space, and a
       column wider than the measure makes the rules overshoot the text. Sized
       to the measure, rule and text end together; the whitespace that remains
       to the right is deliberate, not leftover. */
    grid-template-columns: minmax(0, 21rem) minmax(0, 34rem);
    gap: var(--space-5);
    align-items: start;
  }
  /* Harmless when the steps are shorter than the viewport; earns its keep on
     short/landscape viewports where the rail would otherwise scroll away. */
  .jobsheet-head { position: sticky; top: 7.5rem; }
  /* Stepped rather than clamp()ed on purpose: a static type-scale audit cannot
     resolve clamp(), and these lead headings are what carry the page's scale
     now that the old 2.6rem step numerals are gone. */
  .jobsheet-steps li[data-lead] h3 { font-size: 2.3rem; }
}


/* ---------- Two-column feature ---------- */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}
.split--media-left > figure { order: -1; }
.pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }

/* ---------- Checklist ---------- */
.checklist { list-style: none; padding: 0; }
.checklist li { padding-left: 1.7rem; position: relative; margin-bottom: 0.7rem; }
.checklist li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.42em;
  width: 0.85rem;
  height: 0.5rem;
  border-left: 2.5px solid var(--signal);
  border-bottom: 2.5px solid var(--signal);
  transform: rotate(-45deg);
}
.on-light .checklist li::before, .section--panel .checklist li::before { border-color: var(--signal-deep); }

/* ---------- FAQ (native details/summary) ---------- */
.faq { border-top: 1px solid var(--line); max-width: 46rem; }
.faq details { border-bottom: 1px solid var(--line); }
.on-light .faq, .on-light .faq details { border-color: var(--line-light); }
.faq summary {
  cursor: pointer;
  list-style: none;
  font-family: var(--body);
  font-weight: 600;
  font-size: 1.0625rem;
  padding: 1rem 2.2rem 1rem 0;
  position: relative;
  transition: color var(--t-fast) var(--ease);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--signal-text); }
.on-light .faq summary:hover { color: var(--signal-ink); }
.faq summary::after {
  content: "";
  position: absolute;
  right: 0.4rem;
  top: 50%;
  width: 0.6rem;
  height: 0.6rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-70%) rotate(45deg);
  transition: transform var(--t-med) var(--ease);
}
.faq details[open] summary::after { transform: translateY(-30%) rotate(225deg); }
.faq .faq-body { padding: 0 0 1.1rem; color: var(--bone-3); }
.on-light .faq .faq-body { color: var(--ink-soft); }
.faq .faq-body p { margin-bottom: 0.6rem; }
.faq details::details-content {
  block-size: 0;
  overflow: hidden;
  transition: block-size var(--t-open) var(--ease-out), content-visibility var(--t-open) allow-discrete;
}
.faq details[open]::details-content { block-size: auto; }

/* ---------- CTA band (red) ---------- */
.cta-band .wrap {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--space-4);
  align-items: center;
}
.cta-band .cta-actions { display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }
.cta-band .btn-primary { background: var(--bone); border-color: var(--bone); color: var(--ink); }
.cta-band .btn-primary:hover { background: #fff; border-color: #fff; color: var(--ink); }
.cta-phone {
  font-family: var(--mono);
  font-size: 1.0625rem;
  color: #fff;
  text-decoration: none;
}
.cta-phone:hover { text-decoration: underline; color: #fff; }

/* ---------- Forms (always on a light panel) ---------- */
.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2) var(--space-3);
  max-width: 44rem;
}
.form-grid .full { grid-column: 1 / -1; }
label {
  display: block;
  font-family: var(--body);
  font-weight: 600;
  font-size: 0.95rem;
  margin-bottom: 0.35rem;
}
input, select, textarea {
  width: 100%;
  font-family: var(--body);
  font-size: 1rem;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line-light);
  padding: 0.7rem 0.75rem;
  transition: border-color var(--t-fast) var(--ease);
}
input:focus, select:focus, textarea:focus { border-color: var(--signal); outline: none; box-shadow: 0 0 0 3px rgba(214, 61, 34, 0.2); }
textarea { min-height: 8rem; resize: vertical; }
.form-note { font-size: 0.95rem; color: var(--ink-soft); }
.form-status { font-family: var(--mono); font-size: 0.95rem; margin-top: var(--space-2); }
.form-status[data-state="ok"] { color: #2c6e31; }
.form-status[data-state="error"] { color: var(--signal-deep); }
.hp { position: absolute; left: -9999px; opacity: 0; pointer-events: none; }

/* ---------- Certificates and EPC ---------- */
.cert-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); gap: var(--space-3); }
.cert { background: var(--ground-2); border: 1px solid var(--line); padding: var(--space-2); }
.cert .ph { aspect-ratio: 1 / 1.35; }
.cert .ph::after { content: "CERTIFICATE TO FOLLOW"; }
.cert img { width: 100%; aspect-ratio: 1 / 1.35; object-fit: contain; background: #fff; }
.cert h3 { font-size: 1.25rem; text-transform: uppercase; margin: 0.8rem 0 0.2rem; }
.cert p { font-size: 0.92rem; color: var(--bone-3); margin: 0; }
.epc { list-style: none; padding: 0; margin: 0; display: grid; gap: 4px; max-width: 34rem; }
.epc li { display: grid; grid-template-columns: var(--w) auto; align-items: center; gap: 0.8rem; margin: 0; max-width: none; }
.epc .band { font-family: var(--display); font-weight: 700; font-size: 1.4rem; color: #fff; padding: 0.35rem 0.8rem; clip-path: polygon(0 0, calc(100% - 0.9rem) 0, 100% 50%, calc(100% - 0.9rem) 100%, 0 100%); text-align: left; }
.epc .range { font-family: var(--mono); font-size: 0.78rem; color: var(--bone-3); letter-spacing: 0.04em; }
.epc .note { font-size: 0.9rem; color: var(--bone-3); margin-top: var(--space-2); }

/* ---------- Footer ---------- */
.site-footer {
  background: var(--deep);
  color: var(--bone-3);
  padding: var(--space-5) 0 var(--space-4);
  font-size: 0.95rem;
  border-top: 4px solid var(--ground-2);
}
.site-footer .wrap { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: var(--space-4); }
.site-footer .brand img { height: 3rem; margin-bottom: var(--space-2); }
.site-footer h3 { color: var(--bone); font-size: 1.2rem; letter-spacing: 0.04em; text-transform: uppercase; }
.site-footer a { color: var(--bone-3); text-decoration: none; }
.site-footer a:hover { color: #fff; text-decoration: underline; }
.site-footer ul { list-style: none; padding: 0; margin: 0; }
.site-footer li { margin-bottom: 0.45rem; }
.footer-meta {
  border-top: 1px solid var(--line);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.06em;
  color: var(--bone-3);
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* ---------- Scroll reveal (JS adds .in) ---------- */
.reveal { opacity: 0; transform: translateY(14px); transition: opacity 480ms var(--ease-out), transform 480ms var(--ease-out); }
.reveal.in { opacity: 1; transform: none; }
.tile-grid > .reveal:nth-child(2), .cert-grid > .reveal:nth-child(2) { transition-delay: 50ms; }
.tile-grid > .reveal:nth-child(3), .cert-grid > .reveal:nth-child(3) { transition-delay: 100ms; }
.tile-grid > .reveal:nth-child(4), .cert-grid > .reveal:nth-child(4) { transition-delay: 150ms; }
.tile-grid > .reveal:nth-child(n + 5) { transition-delay: 0ms; }
.tile-grid > .reveal:nth-child(4n + 2) { transition-delay: 50ms; }
.tile-grid > .reveal:nth-child(4n + 3) { transition-delay: 100ms; }
.tile-grid > .reveal:nth-child(4n + 4) { transition-delay: 150ms; }

/* ---------- Split text reveal ----------
   Ported from 21st.dev "Text Reveal (Mask)" (19257) and "Words Stagger" (18555).
   app.js wraps text nodes only, so inline <a>/<em>/<strong> survive; the page
   ships as plain text and is never split until the script runs. Both variants
   ride the existing .reveal observer, so there is one .in trigger for the site.

   Headings get the hard mask (no blur): the word slides up from behind an edge,
   which suits the ridge-and-rule language. Prose gets rise plus un-blur and no
   mask, so descenders and multi-line wraps are never clipped. */

/* Words, masked slide */
.split-words .w {
  display: inline-block;
  overflow: hidden;
  /* room for descenders, taken back out of the layout */
  padding-bottom: 0.15em;
  margin-bottom: -0.15em;
}
.split-words .w > i {
  display: inline-block;
  font-style: inherit;          /* <i> is a transform wrapper here, not emphasis */
  transform: translateY(var(--reveal-rise));
  transition: transform var(--t-reveal) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--stagger-word));
}
.split-words.in .w > i { transform: none; }

/* Lines, rise and un-blur. The DOM is not restructured into line boxes: words
   are wrapped as usual and simply share a --i per visual line, so a resize only
   has to recount indices rather than rebuild (and re-wrap) the paragraph. */
.split-lines .w { display: inline-block; }
.split-lines .w > i {
  display: inline-block;
  font-style: inherit;
  opacity: 0;
  transform: translateY(0.4em);
  filter: blur(var(--reveal-blur));
  transition: opacity var(--t-reveal) var(--ease-out),
              transform var(--t-reveal) var(--ease-out),
              filter var(--t-reveal) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--stagger-line));
}
.split-lines.in .w > i { opacity: 1; transform: none; filter: none; }

/* ---------- Scroll-scrubbed statement ----------
   Ported from 21st.dev id 24525, Motion's official "Scroll word reveal".
   Words sit at --o (rest) and are lit to 1 by app.js as the band is scrolled
   through. Rest is 0.3 rather than upstream's 0.15 so the line is never
   unreadable mid-scroll on charcoal. No transition: it is frame-driven. */
.statement { padding: 0; }
.statement-track { position: relative; height: 200vh; }
.statement-stage {
  position: sticky;
  top: 0;
  min-height: 100svh;
  display: grid;
  align-content: center;
}
.statement-layout {
  display: grid;
  grid-template-columns: 2px minmax(0, 1fr);
  gap: var(--space-3);
  align-items: stretch;
}
.statement-rule { position: relative; background: var(--line); }
.statement-rule > span {
  position: absolute;
  inset: 0;
  background: var(--signal);
  transform: scaleY(var(--p, 0));
  transform-origin: top;
}
.statement-line {
  /* An <h2>: it gives the band a real heading for the document outline, and it
     is display type, not the body copy a 1.2 leading would be tight for. */
  font-family: var(--display);
  text-transform: none;
  font-size: clamp(1.75rem, 3.6vw, 3.4rem);
  font-weight: 560;
  line-height: 1.2;
  max-width: 30ch;
  margin: 0;
  color: var(--bone);
}
.split-scrub .w { display: inline-block; }
.split-scrub .w > i {
  display: inline-block;
  font-style: inherit;
  opacity: var(--o, 0.3);
}

/* The three stage frames light in the same sweep as the words above: one
   metaphor, one ramp, no second scroll system. Defaults are full, so with no JS
   (or reduced motion, where paint() never runs) the record reads as a plain
   contact sheet rather than staying dimmed. */
.statement-figs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-5);
  /* Was 52rem, which left three photos at about 260px in a 94rem wrap: they
     read as thumbnails beside a 3.4rem headline. They now run the full width
     of the band. Running wider than the 30ch headline is the point; the
     photographs are the evidence the sentence is talking about. */
}
.statement-fig { margin: 0; opacity: var(--o, 1); }
.statement-fig img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid var(--line);
  filter: saturate(var(--s, 1));
}
.statement-fig figcaption {
  font-family: var(--mono);
  font-size: 0.86rem;
  line-height: 1.5;
  letter-spacing: 0.03em;
  color: var(--bone-3);
  margin-top: 0.85rem;
}
.statement-fig .stage-no {
  display: block;
  color: var(--signal-text);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-bottom: 0.25rem;
}

/* ---------- Header scroll progress ----------
   Ported from 21st.dev "Scroll Progress" (1033 / 18716). app.js injects the
   element at runtime, mirroring the header sentinel, because anything written
   into <header> in the HTML is wiped by scripts/apply-chrome.py. */
.scroll-progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  /* Bone, not signal red. At full scroll this runs the whole width of the
     header, and in the signal colour that reads as a stray bar rather than an
     orientation cue. Red is reserved for things you can act on. */
  background: var(--bone);
  opacity: 0.35;
  transform: scaleX(var(--p, 0));
  transform-origin: 0 50%;
  pointer-events: none;
  z-index: 2;
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  /* The plates keep their hover feedback, but as colour rather than movement:
     no zoom, and the caption rule appears rather than wiping across. Only the
     end states are set here; the blanket rule below already makes every
     transition instant, and the scroll drift is skipped in app.js under the
     same query. */
  .roof-plate:hover img { --plate-scale: 1.06; }
  .roof-plate figcaption::before { transform: scaleX(1); opacity: 0; }
  .roof-plate:hover figcaption::before { opacity: 1; }

  html { scroll-behavior: auto; }
  *, *::before, *::after { transition-duration: 0.01ms !important; transition-delay: 0ms !important; animation-duration: 0.01ms !important; }
  .reveal { opacity: 1; transform: none; }
  .tile:hover .tile-media img { transform: none; }
  .hero--video .hero-media video { display: none; }
  /* Split reveals and the scrubbed statement rest hidden, so they need
     explicit resets: the blanket rule above only zeroes durations. */
  .split-words .w > i,
  .split-lines .w > i { opacity: 1; transform: none; filter: none; }
  .split-scrub .w > i { opacity: 1; }
  .statement-track { height: auto; }
  .statement-stage { position: static; min-height: 0; padding: var(--space-6) 0; }
  .statement-rule > span { transform: scaleY(1); }
  .scroll-progress { display: none; }
}

/* ---------- Responsive ---------- */
@media (max-width: 64rem) {
  .tile-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .site-footer .wrap { grid-template-columns: 1fr 1fr; }
  /* The horizontal lockup is 4.56:1, so 3.4rem tall is 248px wide and the full
     nav has only ~220px to spare just above the 52rem mobile-nav breakpoint.
     Step the logo down rather than let the two collide. */
  .brand img { height: 2.8rem; }
}
@media (max-width: 56rem) {
  .hero .wrap, .split, .cta-band .wrap, .quote-band .wrap, .ba-item { grid-template-columns: 1fr; }
  .ba-item { gap: var(--space-3); }
  .ba-item:nth-child(even) > .ba { order: 0; }
  .ba-item:nth-child(even) { grid-template-columns: 1fr; }
  /* Three across a phone is 94px a photo, which shows nothing. Stage 01 leads
     at full width and the two that follow sit under it: the sequence still
     reads left to right, and the lead is big enough to see the sheeting. */
  .statement-figs { grid-template-columns: 1fr 1fr; gap: var(--space-2); margin-top: var(--space-4); }
  .statement-fig:first-child { grid-column: 1 / -1; }
  .statement-fig figcaption { font-size: 0; margin-top: 0.45rem; }
  .statement-fig .stage-no { font-size: 0.7rem; margin-bottom: 0; }  /* 11.2px: the detector's floor is 11 */
  .roof-pair { grid-template-columns: 1fr; gap: var(--space-4); }
  .ba-stack { gap: var(--space-5); }
  .hero--video { min-height: 92svh; }
  .split--media-left > figure { order: 0; }
  .tile-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .promises { grid-template-columns: 1fr; }
  .section-head--row { flex-direction: column; align-items: start; }
  .hero--video .hero-media video { display: none; } /* poster only: saves data on phones */
  .util .hours { display: none; }
}
@media (max-width: 52rem) {
  .nav-toggle { display: block; }
  /* The menu drops from the header it belongs to. Opaque on purpose: a
     backdrop-filter here nests inside the header's and Chromium paints the
     page text through it. Verified 2026-08-29. */
  .site-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--ground);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 18px 30px -24px rgba(0, 0, 0, 0.8);
    padding: var(--space-2) clamp(1.1rem, 4vw, 2.5rem) var(--space-3);
    transform-origin: top center;
    opacity: 0;
    transform: translateY(-6px) scaleY(0.98);
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--t-close) var(--ease), transform var(--t-close) var(--ease), visibility 0s linear var(--t-close);
  }
  .site-nav.open {
    opacity: 1;
    transform: none;
    visibility: visible;
    pointer-events: auto;
    transition: opacity var(--t-open) var(--ease-out), transform var(--t-open) var(--ease-out), visibility 0s;
  }
  .site-nav a { padding: 0.7rem 0; border-bottom: 1px solid var(--line); }
  .site-nav a[aria-current="page"] { border-bottom-color: var(--signal); }
  .site-nav .btn { margin-top: var(--space-2); justify-content: center; }
  .pair, .form-grid, .site-footer .wrap { grid-template-columns: 1fr; }
  .opts--3 { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 30rem) {
  .tile-grid { grid-template-columns: 1fr 1fr; }
  .tile h3 { font-size: 1.1rem; }
  .opts, .opts--3 { grid-template-columns: 1fr; }
  .brand img { height: 2.6rem; }
}
