/* ==========================================================================
   Breakara Demolition Queens

   FAMILY SITE. The visual language is inherited from the cluster's approved
   design reference, point-breakara-bronx, per design-start.mjs on 2026-08-16.

   Inheritance is VISUAL ONLY. These tokens and component rules were COPIED
   from that repository, never imported: this file is self-contained, there is
   no shared stylesheet, no component package and no build dependency between
   the two repositories. A later redesign of the Bronx site does not and must
   not automatically change this one.

   Shared with the family: palette and the structural role of each colour,
   typography relationships, button/form/card language, header/nav/footer
   language, spacing scale, container, breakpoints, motion character, and the
   accessibility decisions carried with them (the surface-aware focus token,
   breakpoint-scoped controls having an explicit base state, touch targets).

   Point-specific to Queens, and NOT inherited: section composition, hero
   execution, the three-up service presentation, and the local-conditions module.
   The location map is NOT Point-specific — it ships in the footer of every page
   as the family and the acceptance standard both require. See DESIGN-DNA.md §3.
   ========================================================================== */

/* -------- 1. Tokens ---------------------------------------------------- */
/* THE BRAND LAYER IS THE LOGO'S, AND UNTIL 2026-08-25 IT WAS NOT.
   This stylesheet declared `--hazard: #f2c300` and `--ink: #2b2e31`. Neither was
   a stylistic choice: both were wrong against this repository's own brand asset,
   and nothing had ever checked. Sampling `assets-src/brand/logo.png` with sharp
   at alpha >= 200 gives, as its dominant saturated pixel and dominant neutral:

       #f6bd19 (3038 px)      the hazard square and the rule under the lettering
       #343333 (25613 px)     the wordmark itself

   So every page served a #f6bd19 wordmark on a #f2c300 interface, and set the
   business's name in an ink that appears nowhere in its own logo. The tokens are
   now the mark's own values. The derived pair moves with the base it is derived
   from: #dfa806 is this hue pressed and #7a5f00 is this hue made legible as text.
   Alignment with the siblings is a CONSEQUENCE of the correction, not the reason
   for it — the reason is that the logo is the brand and this file disagreed. */
:root {
  --hazard: #f6bd19;
  --hazard-dark: #dfa806;
  --hazard-ink: #7a5f00;
  --graphite: #23262a;
  --graphite-soft: #2e3237;
  --steel: #565a5c;
  --ink: #343333;
  --concrete: #f4f3f1;
  --rule: #dddcd8;
  --white: #fff;

  /* Focus ring colour, kept as its own token because it is the one brand colour
     that has to change with the surface underneath it. The hazard hue measures
     1.72:1 on white and 1.55:1 on the concrete ground — nowhere near the 3:1 an
     indicator needs — so the light-ground ring uses the hue's accessible ink
     (6.06:1 on white, 5.47:1 on concrete, 3.52:1 on the hazard band). Dark
     grounds re-point this token at --hazard; see below.

     THESE FIGURES WERE RE-MEASURED FOR THE CORRECTED HUE, NOT CARRIED OVER.
     A token change is not neutral to a comment that records what was measured.
     Every pair the correction touches was recomputed against this file's own
     grounds and none regressed: ink on white 13.66 -> 12.59, ink on the hazard
     band 8.18 -> 7.32, graphite on the hazard band 9.10 -> 8.84, hazard on
     graphite 9.10 -> 8.84, and the two ring figures above both improved. */
  --focus: var(--hazard-ink);

  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  --container: 1140px;
  --gutter: 30px;
  --section: 60px;

  --shadow-sticky: 0 2px 10px -2px rgba(0, 0, 0, .41);
  /* explicit properties, never `all`: a blanket transition also animates
     inherited visibility, which can leave menu links invisible after opening */
  --ease: color .2s ease, background-color .2s ease, border-color .2s ease, box-shadow .2s ease, opacity .2s ease;
}

/* -------- 2. Base ------------------------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--white);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.6;
  color: var(--steel);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  margin: 0 0 15px;
  line-height: 1.25;
}
h1 { font-size: 30px; font-weight: 700; }
h2 { font-size: 30px; font-weight: 700; }
h3 { font-size: 22px; font-weight: 700; }
h4 { font-size: 17px; font-weight: 700; }

p { margin: 0 0 25px; }
p:last-child { margin-bottom: 0; }

a { color: var(--hazard-ink); text-decoration: none; transition: var(--ease); outline: none; }
a:hover, a:focus { color: var(--ink); }

img { max-width: 100%; height: auto; display: block; }

hr {
  margin: 45px 0;
  border: 0;
  border-top: 1px dotted var(--rule);
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}
/* The hazard ink that works on white drops to 2.61:1 on graphite, so every dark
   surface re-points the token at the hazard hue (9.10:1 there). The property
   inherits, so this reaches focusable descendants without repeating the rule. */
.hero, #sub_header, .bg_content, .box_feat, footer { --focus: var(--hazard); }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100000;
  background: var(--graphite); color: var(--white);
  padding: 12px 20px; font-weight: 700;
}
.skip-link:focus { left: 0; color: var(--white); }

.container {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 20px;
}

.section { padding: var(--section) 0; }
.section--tight { padding: 40px 0; }
.section--concrete { background: var(--concrete); }

.text-center { text-align: center; }
.lead { font-size: 17px; }
.measure { max-width: 66ch; }
/* A measure that is a container's ONLY child has nothing to sit beside it, so at
   desktop it left ~45% of the container empty on its right and read as a layout
   that had broken rather than as whitespace. Centring makes the single column
   deliberate. The :only-child scope matters: a measure inside a .row or a .col-*
   is aligned to its column and must keep its left edge. */
.container > .measure:only-child { margin-left: auto; margin-right: auto; }
/* The reference site scoped that fix to :only-child. This Point's sections pair a
   centred .section-title with a .measure beneath it, so the measure is never the
   only child and the rule above never fired: at desktop the copy sat hard left
   under a centred heading with ~45% of the container empty beside it, which reads
   as a layout that has broken rather than as whitespace.
   Scoped to a DIRECT child of a section container, so a .measure inside a .row or
   a .col-* — the contact page's two-column block — keeps its left edge. */
.section > .container > .measure { margin-left: auto; margin-right: auto; }

/* Template motif: section headings centred with generous space beneath */
.section-title { text-align: center; margin-bottom: 40px; }
.section-title h2 { margin-bottom: 12px; }
.section-title p { color: var(--steel); margin: 0 auto; max-width: 62ch; }

/* -------- 3. Grid ------------------------------------------------------ */
.row {
  display: grid;
  gap: var(--gutter);
  grid-template-columns: repeat(12, 1fr);
}
.col-4 { grid-column: span 4; }
.col-5 { grid-column: span 5; }
.col-6 { grid-column: span 6; }
.col-7 { grid-column: span 7; }
.col-8 { grid-column: span 8; }
.col-12 { grid-column: span 12; }

/* -------- 4. Buttons (template btn_1 / btn_quote) ---------------------- */
.btn {
  display: inline-block;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .02em;
  padding: 13px 30px;
  border-radius: 2px;
  transition: var(--ease);
  text-align: center;
}
.btn--primary { background: var(--hazard); color: var(--ink); }
.btn--primary:hover, .btn--primary:focus { background: var(--ink); color: var(--white); }
.btn--onband { background: var(--ink); color: var(--white); }
.btn--onband:hover, .btn--onband:focus { background: var(--white); color: var(--ink); }
.btn--ghost {
  background: transparent; color: var(--white);
  --btn-ring: inset 0 0 0 2px rgba(255, 255, 255, .8);
  box-shadow: var(--btn-ring);
}
.btn--ghost:hover, .btn--ghost:focus { background: var(--white); color: var(--ink); }

/* Template motif: "read more" link with a trailing arrow glyph */
.link_normal { font-weight: 500; }
.link_normal::after {
  content: "";
  display: inline-block;
  width: .5em; height: .5em;
  margin-left: .45em;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg) translateY(-1px);
  transition: var(--ease);
}
.link_normal:hover::after { margin-left: .75em; }

/* -------- 5. Top utility line ------------------------------------------ */
#top_line {
  height: 30px;
  font-size: 13px;
  border-bottom: 1px solid rgba(0, 0, 0, .1);
  background: var(--white);
  overflow: hidden;
  transition: height .2s ease;
}
#top_line .container { display: flex; align-items: center; justify-content: space-between; height: 30px; }
#tag_line { color: var(--steel); }
#top_links { display: flex; gap: 14px; list-style: none; margin: 0; padding: 0; font-weight: 600; }
#top_links li + li { border-left: 1px solid rgba(0, 0, 0, .12); padding-left: 14px; }
#top_links a:hover { color: var(--hazard-ink); }

/* The call link FILLS the utility line in BOTH states, not only the scrolled one.
   The reference site applied this fill under `.sticky` alone, which left the
   at-rest target at the link's own text height — measured 18px at desktop and
   21px in the 768-991 touch band, against WCAG 2.5.8's 24px floor. Since this is
   the only persistent phone action anywhere above 767px, the state a reader meets
   first should not be the weakest one. Filling unconditionally gives 30px at rest
   and 26px scrolled, so both clear the floor; the comfortable 44px target is the
   fixed call bar, which takes over below 768. */
#top_links, #top_links li { height: 100%; }
#top_links a { color: var(--ink); display: flex; align-items: center; height: 100%; }

/* Scrolled state. This line carries the site's only phone link in persistent
   chrome, so it compresses rather than collapses: the tag line is the part worth
   losing, the call link is not. Collapsing the whole line to height:0/opacity:0
   left two defects — no phone action anywhere in fixed chrome above the mobile
   call bar's breakpoint, and a still-focusable link inside an invisible box. The
   tag line is removed with `display: none`, so nothing is merely transparent. */
.sticky #top_line { height: 26px; }
.sticky #top_line .container { height: 26px; justify-content: flex-end; }
/* The fill itself is now unconditional — see section 5 above. */
/* #top_line clips to animate its height, which also clipped the focus ring on the site's only
   persistent phone action. The ring is drawn outside the box by definition, so the clip is lifted
   once the line has finished collapsing. */
.sticky #top_line { overflow: visible; }
.sticky #tag_line { display: none; }

/* -------- 6. Header + navigation --------------------------------------- */
header {
  width: 100%;
  padding: 0 0 0 0;
  background: var(--white);
  position: relative;
  z-index: 9999;
}
header.sticky {
  position: fixed; top: 0; left: 0;
  box-shadow: var(--shadow-sticky);
}
body.has-sticky { padding-top: var(--header-h, 92px); }

.header-inner {
  display: flex; align-items: center; justify-content: space-between;
  /* Side padding must match .container's 0 20px. This is the same element, and a
     shorthand of `14px 0` silently reset it, leaving the header the only row on the
     page whose content did not share the page's left edge. */
  padding: 14px 20px;
  transition: padding .2s ease;
}
.sticky .header-inner { padding: 8px 20px; }

/* Brand logo. Driven by WIDTH with height:auto so the aspect ratio can never be
   squashed by a narrow container, and the header height stays governed by its own
   padding rather than by the artwork. */
#logo a { display: inline-block; line-height: 0; }
.brand-logo {
  display: block;
  width: 176px;
  height: auto;
  max-width: 100%;
  transition: opacity .2s ease;
}
#logo a:hover .brand-logo { opacity: .82; }
.sticky .brand-logo { width: 150px; }
.brand-logo--footer { width: 184px; }

.nav-toggle {
  display: none;
  background: none; border: 0; cursor: pointer;
  padding: 10px; color: var(--ink);
  font: inherit; font-weight: 700;
  min-width: 44px; min-height: 44px;   /* comfortable tap target */
}
.nav-toggle span {
  display: block; width: 24px; height: 2px; background: var(--ink); margin: 5px 0;
  transition: background-color .2s ease, transform .2s ease;
}

/* Off-canvas panel header. It exists only at the mobile breakpoint, where the
   panel it closes exists. Above that there is no panel, so the control would be
   inert: it is not rendered, not focusable, and not in the accessibility tree. */
.menu-head { display: none; }
/* The panel's call action needs the same base state, and not having one was a real defect: with
   no rule outside its breakpoint it fell back to `display: inline` and rendered as unstyled text
   in the desktop header — visible, focusable at tab position 9, and inflating the header by 30px.
   A control scoped to a breakpoint must be switched OFF everywhere else, not merely styled on. */
.menu-call { display: none; }

.main-menu ul {
  display: flex; align-items: center; gap: 26px;
  list-style: none; margin: 0; padding: 0;
}
.main-menu a {
  color: var(--ink);
  font-size: 14px; font-weight: 500;
  padding: 6px 0;
  position: relative;
}
.main-menu a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 3px; background: var(--hazard);
  transform: scaleX(0); transform-origin: left; transition: transform .2s ease;
}
.main-menu a:hover::after, .main-menu a[aria-current="page"]::after { transform: scaleX(1); }
.main-menu .nav-cta { margin-left: 4px; }
.main-menu .nav-cta a { color: var(--ink); background: var(--hazard); padding: 11px 20px; border-radius: 2px; font-weight: 700; }
.main-menu .nav-cta a::after { display: none; }
.main-menu .nav-cta a:hover { background: var(--ink); color: var(--white); }

#menu-overlay {
  display: none;
  position: fixed; inset: 0; background: rgba(0, 0, 0, .5); z-index: 9998;
}
body.menu-open #menu-overlay { display: block; }

/* -------- 7. Hero (template carousel slide, single static frame) ------- */
.hero {
  position: relative;
  overflow: hidden;
  background: var(--graphite);
  min-height: 520px;
  display: flex; align-items: center;
}

/* Full-bleed cover photography. Rendered as a real <img> rather than a CSS
   background so it gets srcset, intrinsic dimensions and LCP priority. */
picture { display: contents; }
.cover-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: 0;
}
/* Bordered inset art, matching the template's bordered inline images. */
.inset, .card picture img {
  border: 1px solid var(--rule);
  padding: 4px;
  background: var(--white);
  width: 100%;
}
.card picture img { aspect-ratio: 4 / 3; object-fit: cover; margin-bottom: 16px; }
.inset { aspect-ratio: 4 / 3; object-fit: cover; }
.hero__mask {
  position: absolute; inset: 0;
  z-index: 1;
  background: linear-gradient(90deg, rgba(18, 20, 22, .88) 0%, rgba(18, 20, 22, .72) 52%, rgba(18, 20, 22, .5) 100%);
}
.hero .container { position: relative; z-index: 2; padding-top: 70px; padding-bottom: 70px; }
.hero__text { max-width: 660px; color: var(--white); }
.hero h1 {
  color: var(--white);
  font-size: clamp(30px, 5vw, 46px);
  font-weight: 900;
  letter-spacing: -.015em;
  line-height: 1.08;
  margin-bottom: 18px;
}
.hero__sub {
  font-size: 18px; color: rgba(255, 255, 255, .9);
  margin-bottom: 26px; max-width: 52ch;
}
.hero__meta {
  display: flex; flex-wrap: wrap; gap: 10px 22px; align-items: center;
  margin-bottom: 28px;
  font-size: 13px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: rgba(255, 255, 255, .82);
}
.hero__meta span { display: inline-flex; align-items: center; gap: 8px; }
.hero__meta svg { width: 15px; height: 15px; flex: none; }
.hero__cta { display: flex; flex-wrap: wrap; gap: 12px; }

/* -------- 8. Sub header (template #sub_header) ------------------------- */
#sub_header {
  background: var(--graphite);
  color: var(--white);
  position: relative;
  overflow: hidden;
  padding: 54px 0 46px;
}
#sub_header::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: rgba(18, 20, 22, .72);
}
#sub_header.sub_header--plain::before { background: none; }
/* No photograph behind it means nothing to lift the title off, so the plate goes too. */
#sub_header.sub_header--plain .main_title { background: none; padding-left: 0; padding-right: 0; }
#sub_header .container { position: relative; z-index: 2; display: flex; flex-wrap: wrap; gap: 20px; align-items: flex-end; justify-content: space-between; }
.main_title { background: rgba(0, 0, 0, .34); padding: 18px 20px; max-width: 720px; }
.main_title h1 { color: var(--white); font-size: 26px; font-weight: 700; margin: 0 0 6px; }
/* Template motif: '//' before the page title */
.main_title h1::before { content: "//"; color: var(--hazard); margin-right: 8px; }
.main_title p { margin: 0; font-size: 14px; color: rgba(255, 255, 255, .88); }

#position { list-style: none; margin: 0; padding: 0; font-size: 13px; display: flex; gap: 8px; color: rgba(255, 255, 255, .75); }
#position a { color: rgba(255, 255, 255, .95); }
#position a:hover { color: var(--hazard); }
#position li + li::before { content: "/"; margin-right: 8px; color: rgba(255, 255, 255, .5); }

/* -------- 9. Get-quote band (template #get_quote) ---------------------- */
#get_quote {
  background: var(--hazard);
  background-image: repeating-linear-gradient(
    45deg, rgba(0, 0, 0, .045) 0 2px, transparent 2px 9px);
  padding: 30px 0;
  color: var(--ink);
}
#get_quote .container { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 20px; }
#get_quote h2 { color: var(--ink); font-size: 22px; margin: 0; max-width: 46ch; }

/* -------- 10. Feature boxes (template .box_feat) ----------------------- */
.box_feat {
  background: var(--graphite);
  color: rgba(255, 255, 255, .82);
  text-align: center;
  padding: 28px 24px 24px;
  border-radius: 10px;
  height: 100%;
}
.box_feat .box_icon {
  width: 54px; height: 54px; margin: 0 auto 14px;
  color: var(--hazard);
}
.box_feat h3 { color: var(--hazard); font-size: 19px; }
/* Template motif: '/' flanking the feature heading */
.box_feat h3::before { content: "/"; color: rgba(255, 255, 255, .55); margin-right: 10px; }
.box_feat h3::after { content: "/"; color: rgba(255, 255, 255, .55); margin-left: 10px; }
.box_feat p { font-size: 14px; margin: 0; }

/* -------- 11. Service cards (template 3-up image + read more) ---------- */
.card > img {
  border: 1px solid var(--rule);
  padding: 4px;
  background: var(--white);
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  margin-bottom: 16px;
}
.card h3 { margin-bottom: 10px; }
.card p { margin-bottom: 12px; font-size: 14.5px; }

/* -------- 12. Tick lists (template .list_ok) --------------------------- */
.list_ok { list-style: none; margin: 0 0 25px; padding: 0; }
.list_ok li { position: relative; padding-left: 26px; margin-bottom: 9px; }
.list_ok li::before {
  content: ""; position: absolute; left: 2px; top: .45em;
  width: 10px; height: 6px;
  border-left: 2px solid var(--ink);
  border-bottom: 2px solid var(--ink);
  transform: rotate(-45deg);
}

/* Numbered process steps — template's numbered rhythm */
.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--gutter); grid-template-columns: repeat(4, 1fr); }
.steps li { border-top: 3px solid var(--hazard); padding-top: 16px; }
.steps .num { display: block; font-size: 12px; font-weight: 900; letter-spacing: .18em; color: var(--hazard-ink); margin-bottom: 6px; }
/* The hazard ink is the on-white version of the hue and only reaches 2.61:1 on
   the dark band, where the steps also appear. There it takes the hue itself
   (9.10:1 on graphite, and still 5.4:1 over the lightest area a masked cover
   photo can produce). The on-white case above is untouched. */
.bg_content .steps .num { color: var(--hazard); }
.steps h3 { font-size: 17px; margin-bottom: 8px; }
/* 2-up variant for in-body explanatory blocks (4 items read better as 2x2
   than as a thin 4-across strip, and it gives long prose a visual break) */
.steps--2up { grid-template-columns: repeat(2, 1fr); margin: 34px 0 0; }
.steps--2up .num { text-transform: uppercase; letter-spacing: .14em; }
.steps p { font-size: 14px; margin: 0; }

/* -------- 13. Full-bleed content band (template .bg_content) ----------- */
.bg_content {
  background: var(--graphite);
  position: relative;
  overflow: hidden;
  padding: 80px 0;
  text-align: center;
  color: var(--white);
}
.bg_content::before { content: ""; position: absolute; inset: 0; z-index: 1; background: rgba(18, 20, 22, .78); }
.bg_content .container { position: relative; z-index: 2; }
.bg_content h2 { color: var(--white); }
/* Template motif: rules flanking the band heading */
.bg_content h2::before, .bg_content h2::after {
  content: ""; display: inline-block; width: 34px; height: 2px;
  background: var(--hazard); vertical-align: middle; margin: 0 16px;
}
.bg_content p { color: rgba(255, 255, 255, .88); max-width: 62ch; margin: 0 auto 25px; }
.bg_content .band-action { margin-top: 6px; }

/* -------- 14. FAQ ------------------------------------------------------ */
.faq { max-width: 820px; margin: 0 auto; }
.faq details {
  border-bottom: 1px dotted var(--rule);
  padding: 16px 0;
}
.faq summary {
  cursor: pointer; font-weight: 700; color: var(--ink); font-size: 16.5px;
  list-style: none; display: flex; justify-content: space-between; gap: 16px; align-items: flex-start;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ""; flex: none; width: 9px; height: 9px; margin-top: 6px;
  border-right: 2px solid var(--hazard-dark); border-bottom: 2px solid var(--hazard-dark);
  transform: rotate(45deg); transition: transform .2s ease;
}
.faq details[open] summary::after { transform: rotate(-135deg); }
.faq details p { margin: 12px 0 0; font-size: 15px; }

/* -------- 15. Forms ---------------------------------------------------- */
.form-note {
  background: var(--concrete);
  border-left: 3px solid var(--hazard);
  padding: 14px 18px;
  font-size: 14px;
  margin-bottom: 25px;
}
.form-group { margin-bottom: 18px; }
label { display: block; font-size: 13px; font-weight: 700; color: var(--ink); margin-bottom: 6px; }
label .req { color: var(--hazard-ink); }
.form-control {
  width: 100%;
  font-family: inherit;
  font-size: 15px;
  color: var(--ink);
  padding: 11px 13px;
  border: 1px solid var(--rule);
  border-radius: 3px;
  background: var(--white);
  transition: var(--ease);
}
/* The tinted glow is the field's hover/active affordance, not its focus indicator: on its own it
   measured well under the 3:1 a focus indicator needs. The real ring is kept rather than
   suppressed, so a keyboard user sees the same indicator here as everywhere else. */
.form-control:focus { border-color: var(--hazard); box-shadow: 0 0 0 3px rgba(242, 195, 0, .25); }
.form-control:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
textarea.form-control { min-height: 130px; resize: vertical; }
.hp { position: absolute; left: -9999px; }

/* -------- 15b. Location map (template's bordered-image treatment) ------- */
.map-block { margin: 0 0 25px; }
.map-frame {
  border: 1px solid var(--rule);
  padding: 4px;
  background: var(--white);
  line-height: 0;
}
.map-frame iframe {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  border: 0;
}
/* The lazy iframe has nothing behind it until it loads, so an unloaded map read as an empty
   bordered rectangle rather than as a map arriving. The footer instance had a placeholder; the
   body instance did not, which is the same defect the footer rule's own comment describes. */
.map-frame, .map-frame iframe {
  background-color: var(--concrete);
  background-image: repeating-linear-gradient(45deg,
    rgba(0, 0, 0, .035) 0 10px, transparent 10px 20px);
}
.map-caption {
  margin: 10px 0 0;
  font-size: 13.5px;
  color: var(--steel);
}
@media (max-width: 767px) {
  .map-frame iframe { aspect-ratio: 4 / 3; }
}

/* The footer instance sits on graphite, so the body treatment's white mat and
   light rule would read as a pasted-on panel. Same component, footer surface. */
.map-block--footer { margin: 18px 0; }
.map-block--footer .map-frame {
  border-color: rgba(255, 255, 255, .22);
  padding: 3px;
}
/* Placeholder ground. The embed is held back until the footer is near the
   viewport, so for the first paint this is an empty bordered rectangle, which
   reads as a render that failed rather than one that has not happened yet. A
   footer surface tone carrying the CTA band's hatch gives the space something
   deliberate to hold; the map paints straight over it on load. It is set on the
   iframe as well because a frame with no document paints its own box. */
.map-block--footer .map-frame,
.map-block--footer .map-frame iframe {
  background-color: var(--graphite-soft);
  background-image: repeating-linear-gradient(
    45deg, rgba(255, 255, 255, .05) 0 2px, transparent 2px 9px);
}
/* Shallower than the body instance: at desktop this map is what makes the third
   footer column tower over the two beside it, and a locator does not need the
   height to do its job. */
.map-block--footer .map-frame iframe { aspect-ratio: 16 / 7; }
@media (max-width: 767px) {
  /* The reference sets 4:3 here, on the stated premise that "two columns at this
     width leave the frame narrow". That premise does not hold in this footer:
     it collapses to a SINGLE column below 768, so the frame is already the full
     column width (measured 327px at 375). Inheriting 4:3 anyway made the map
     245px tall and added roughly 100px to the mobile footer for no gain, so the
     shallower desktop ratio is kept. Inheriting a rule whose reason does not
     apply is how a family site accumulates defects that look intentional. */
  .map-block--footer .map-frame iframe { aspect-ratio: 16 / 9; }
}

/* -------- 16. Contact detail block ------------------------------------- */
.detail-list { list-style: none; margin: 0; padding: 0; }
.detail-list li { display: flex; gap: 12px; margin-bottom: 16px; align-items: flex-start; }
.detail-list svg { width: 18px; height: 18px; flex: none; color: var(--hazard-dark); margin-top: 3px; }
.phone-xl {
  display: inline-block;
  font-size: clamp(26px, 4vw, 34px); font-weight: 900; letter-spacing: -.01em;
  color: var(--ink);
}
.phone-xl:hover { color: var(--hazard-ink); }

address { font-style: normal; }

/* -------- 17. Footer --------------------------------------------------- */
footer {
  background: var(--graphite);
  color: rgba(255, 255, 255, .78);
  padding: 50px 0 30px;
  font-size: 14.5px;
}
footer h3 { font-size: 17px; font-weight: 700; color: var(--white); margin-bottom: 16px; }
footer a { color: rgba(255, 255, 255, .88); }
footer a:hover { color: var(--hazard); }
footer ul { list-style: none; margin: 0; padding: 0; }
footer ul li { margin-bottom: 9px; }
/* Legal links sit under the brand column, not under the map. The map column ran far taller than
   the other two, leaving a wide band of empty graphite beneath them; moving this list shortens the
   tall column and lengthens a short one at once. */
.footer-legal { margin-top: 18px; }
.footer-wordmark { margin-bottom: 16px; }
.footer-wordmark .wordmark { color: var(--white); font-size: 22px; }
.footer-wordmark .wordmark-sub { color: rgba(255, 255, 255, .6); }

#copy {
  background: var(--hazard);
  color: var(--ink);
  padding: 12px 0;
  text-align: center;
  font-size: 12.5px;
  font-weight: 500;
}
#copy a { color: var(--ink); text-decoration: underline; }

/* -------- 18. Back to top ---------------------------------------------- */
#toTop {
  position: fixed; right: 16px; bottom: 16px; z-index: 9997;
  width: 42px; height: 42px;
  border: 0; border-radius: 2px;
  background: rgba(0, 0, 0, .6);
  color: var(--white);
  cursor: pointer;
  display: none;
  align-items: center; justify-content: center;
}
#toTop.is-visible { display: flex; }
#toTop:hover { background: var(--hazard); color: var(--ink); }
#toTop svg { width: 16px; height: 16px; }

/* -------- 19. Sticky mobile call bar ----------------------------------- */
#call_bar { display: none; }

/* -------- 20. Scroll reveal — DELIBERATELY NOT INHERITED ----------------
   The reference site reveals blocks on scroll. This site does not, and the
   rules were removed rather than left in place selecting nothing: no page here
   emits [data-cue]. Beyond being dead weight, a reveal system is what makes
   full-page QA captures photograph content in its hidden state, so not having
   one means every screenshot of this site is evidence without a preparation
   step. The matching JS was removed from site.js at the same time. */

/* -------- 20b. POINT-SPECIFIC: local conditions module ------------------
   This component has no equivalent in the design reference and is not
   inherited from it. It exists because the Queens building stock genuinely
   changes how a demolition job runs — attached party walls, no driveway, co-op
   alteration rules, walk-ups — and because the copy benchmark found that not
   one ranking competitor publishes any of it. See DESIGN-DNA.md §3.

   Deliberately NOT another row of equal cards: the section standard warns
   against turning every block into one, and these four items are unequal in
   weight. A definition-list rhythm on a concrete ground reads as reference
   material, which is what it is. */
.conditions { display: grid; gap: 0; margin: 0; }
.conditions > div {
  padding: 22px 0;
  border-top: 1px dotted var(--rule);
  display: grid;
  grid-template-columns: minmax(180px, 1fr) 2.2fr;
  gap: 8px var(--gutter);
  align-items: start;
}
.conditions > div:last-child { border-bottom: 1px dotted var(--rule); }
.conditions dt {
  font-weight: 700;
  color: var(--ink);
  font-size: 17px;
  line-height: 1.3;
}
/* The hazard rule is the only brand colour in this block. It marks each term
   without tinting a ground, which is the structural role the family gives the
   hue. */
.conditions dt::before {
  content: "";
  display: block;
  width: 26px; height: 3px;
  background: var(--hazard);
  margin-bottom: 10px;
}
.conditions dd { margin: 0; font-size: 15px; }

/* -------- 20c. POINT-SPECIFIC: three-up service presentation ------------
   The reference site carries two services as unequal job-ticket blocks. This
   Point carries three, each earning its page on its own evidence, so they are
   presented as equal peers. Composition differs; the card geometry, border
   treatment and "read more" language are the family's. */
.svc { height: 100%; display: flex; flex-direction: column; }
.svc h3 { margin-bottom: 10px; }
.svc p { font-size: 14.5px; margin-bottom: 14px; }
.svc .link_normal { margin-top: auto; align-self: flex-start; }

/* -------- 21. Responsive ----------------------------------------------- */
@media (max-width: 991px) {
  .col-4 { grid-column: span 6; }
  .col-5, .col-7 { grid-column: 1 / -1; }
  .steps { grid-template-columns: repeat(2, 1fr); }
  .hero { min-height: 440px; }

  /* ---- Off-canvas navigation -----------------------------------------
     This breaks at 991, not at the 767 mobile breakpoint, because the
     horizontal bar needs roughly 930px to fit the logo, six links and the
     quote button on one line. Between 768 and 930 it did not: four items
     wrapped to a second row, the logo ran into the first link, and the
     button label printed outside its fill. The panel is the correct
     treatment for the whole range below the width the bar actually needs.
     site.js matches the same 991px query to drive `inert`; the two must
     stay identical, or the closed panel stays focusable in the gap. */
  .nav-toggle { display: block; }

  .main-menu {
    position: fixed; top: 0; right: 0; bottom: 0;
    width: min(320px, 86vw);
    background: var(--graphite);
    z-index: 9999;
    transform: translateX(100%);
    /* The closed panel leaves layout entirely. `visibility: hidden` keeps a layout
       box, so the links stay measurable as nominally tabbable even though a
       conforming browser skips them; `display: none` removes that ambiguity for
       browsers, assistive tech and tooling alike. `allow-discrete` plus
       @starting-style keeps the slide, and older engines simply show it instantly. */
    /* display is class-driven and immediate. It is NOT transitioned: an
       accessibility-critical state must not depend on a transition completing,
       and a discrete transition leaves the panel in the tree while it plays. The
       slide-in still animates via @starting-style; closing is instant. */
    display: none;
    transition: transform .25s ease;
    padding: 22px;
    overflow-y: auto;
  }
  body.menu-open .main-menu {
    display: block;
    transform: translateX(0);
  }
  @starting-style {
    body.menu-open .main-menu { transform: translateX(100%); }
  }
  /* Panel head: brand left, close right, the same pairing the header itself
     uses, so the panel reads as the site rather than as a bare control. */
  .main-menu .menu-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 18px; }
  /* The panel is inside <header>, so the header's own logo rules — including the
     sticky shrink — match this image too. Deliberately more specific than all of
     them: the panel logo is a fixed size and must not resize with scroll state. */
  .main-menu .menu-head .brand-logo--menu { width: 148px; }
  /* The panel is a touch surface wherever it exists, so its close control carries a touch target
     rather than the 31x34 it inherited from the desktop-era markup. */
  .menu-close {
    background: none; border: 0; color: var(--white); font-size: 26px; line-height: 1; cursor: pointer;
    min-width: 44px; min-height: 44px; padding: 4px 8px;
    display: flex; align-items: center; justify-content: center;
  }
  /* A call action inside the panel. Opening the menu is the one moment the reader is looking for
     a way to act and the header's own phone link is behind the overlay. */
  /* Selector is .main-menu .menu-call, not .menu-call: the panel sets
     `.main-menu a { color: var(--white) }`, which is (0,1,1) and beats a bare class. Losing that
     cascade printed white on hazard yellow at 1.67:1 and silently defeated the min-height reset. */
  .main-menu .menu-call {
    display: block; margin-top: 18px; text-align: center;
    background: var(--hazard); color: var(--ink);
    font-weight: 900; font-size: 16px; padding: 13px 16px; border-radius: 2px;
  }
  .main-menu .menu-call:hover, .main-menu .menu-call:focus { background: var(--white); color: var(--ink); }
  .main-menu ul { flex-direction: column; align-items: stretch; gap: 0; }
  .main-menu li { border-bottom: 1px solid rgba(255, 255, 255, .12); }
  .main-menu a { display: block; color: var(--white); padding: 14px 0; font-size: 16px; }
  .main-menu a::after { display: none; }
  .main-menu a[aria-current="page"] { color: var(--hazard); }
  .main-menu .nav-cta { margin: 20px 0 0; border: 0; }
  .main-menu .nav-cta a { text-align: center; padding: 14px 20px; }
  /* Panel ground is graphite, so the ring takes the hazard hue here as it does
     on every other dark surface. */
  .main-menu { --focus: var(--hazard); }
}

@media (max-width: 767px) {
  :root { --section: 44px; --gutter: 24px; }

  /* readability floor for small screens */
  body { font-size: 16px; }

  /* Below this width the call bar carries the phone action, so the utility line
     is not needed. `display: none` rather than a collapse: nothing that is not
     rendered should stay in the tab order. */
  #top_line { display: none; }

  .brand-logo { width: 150px; }
  .sticky .brand-logo { width: 134px; }
  .brand-logo--footer { width: 164px; }

  .row { grid-template-columns: 1fr; }
  .col-4, .col-5, .col-6, .col-7, .col-8, .col-12 { grid-column: 1 / -1; }
  .steps { grid-template-columns: 1fr; }

  h1, h2 { font-size: 25px; }
  h3 { font-size: 20px; }

  .hero { min-height: 0; }
  .hero .container { padding-top: 52px; padding-bottom: 52px; }
  .hero__mask { background: rgba(20, 22, 24, .74); }

  #get_quote .container { flex-direction: column; align-items: flex-start; }
  #get_quote h2 { font-size: 20px; }

  #sub_header { padding: 34px 0 30px; }
  #sub_header .container { flex-direction: column; align-items: flex-start; }
  .main_title h1 { font-size: 22px; }

  .bg_content { padding: 54px 0; }
  .bg_content h2::before, .bg_content h2::after { width: 20px; margin: 0 10px; }

  /* The two-column term/definition split needs roughly 640px to keep the
     definition column wider than its term. Below that it stacks, and the
     hazard rule above each term carries the hierarchy on its own. */
  .conditions > div { grid-template-columns: 1fr; padding: 18px 0; }

  /* A one-line question is a 26px target for a thumb. The padding that made the
     row's rhythm moves from details onto summary, where it is tappable, so the
     control reaches 44px without the block growing. The chevron keeps its
     flex-start alignment against the first line of the question. */
  .faq details { padding: 6px 0; }
  .faq summary { min-height: 44px; padding: 9px 0; }

  /* Sticky call bar: on a phone, calling is the first useful action */
  #call_bar {
    display: block;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 9996;
    background: var(--hazard);
    box-shadow: 0 -2px 12px rgba(0, 0, 0, .18);
  }
  #call_bar a {
    display: block; text-align: center;
    padding: 14px 16px;
    color: var(--ink); font-weight: 900; font-size: 16px; letter-spacing: .01em;
  }
  /* B-07. The copy strip and the call bar are both --hazard. Clearing the 54px bar
     with 64px of padding left a 10px WHITE sliver between two identical yellow bands
     at the document end, and elementFromPoint at the midpoint returned BODY. The
     clearance now matches the bar exactly, and the page ground behind it is painted
     hazard so any sub-pixel rounding cannot reopen the seam. */
  /* The call bar measures ~53.6px (14px padding, a 25.6px line, 14px padding), so a
     clearance of exactly 54 could still round open. 50px lets the copy strip's own
     bottom padding tuck under the bar instead. Both are --hazard, the bar is opaque,
     and no text is covered: only the strip's padding passes beneath it.

     Painting `body` hazard was the other option and is wrong here, because
     `body.has-sticky` carries a padding-top for the fixed header and that region
     would have turned yellow at the top of every page. */
  body { padding-bottom: 50px; }

  /* B-07, second half. At bottom: 64px the 42px dark button landed exactly on the
     yellow copy strip and read as a notch punched out of it. It now sits clear of
     both the call bar and the strip. */
  #toTop { bottom: 108px; }

  /* Phone links are the money action. Inline prose links are exempt from the
     target-size rule, but a tappable number should never be a 20px hairline. */
  a[href^="tel:"] {
    display: inline-block;
    min-height: 44px;
    line-height: 44px;
    font-weight: 700;
  }
  .detail-list a[href^="tel:"] { font-size: 18px; }
  #top_links a[href^="tel:"], #call_bar a, .btn[href^="tel:"], .main-menu .menu-call { min-height: 0; line-height: inherit; }
  .btn[href^="tel:"] { line-height: 1.4; }
}

@media (max-width: 480px) {
  .hero h1 { font-size: 26px; }
  .btn { display: block; width: 100%; }
  .hero__cta { flex-direction: column; align-items: stretch; }
}

/* -------- 22. Motion / print ------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

@media print {
  header, footer, #get_quote, #toTop, #call_bar, #copy { display: none; }
  body { color: #000; }
}

/* -------- 22. Touch band: off-canvas nav without a phone-sized viewport ---
   Moving the off-canvas nav up to 991 made 768-991 a touch surface, but the
   touch affordances stayed behind at 767: the call bar was absent and tappable
   phone numbers were still 17-26px hairlines. Conversion and target size follow
   the interaction model, not the device name. */
@media (min-width: 768px) and (max-width: 991px) {
  /* Deliberately NO bottom call bar here. This band already keeps the phone link in the sticky
     utility line, and adding the bar gave the same number three persistent homes at once — the
     probe caught it as one action repeated five times. Below 768 the utility line is hidden and
     the bar is the only persistent action, which is why it exists there and not here. */
  a[href^="tel:"] { display: inline-block; min-height: 44px; line-height: 44px; font-weight: 700; }
  .detail-list a[href^="tel:"] { font-size: 18px; }
  /* The FAQ toggle is the most-tapped control in its block, and raising phone links here while
     leaving it at 26px would have applied this band's own principle to half the page. */
  .faq details { padding: 6px 0; }
  .faq summary { min-height: 44px; padding: 9px 0; }
  /* The sticky utility line and the buttons set their own height deliberately. */
  #top_links a[href^="tel:"], .btn[href^="tel:"], .main-menu .menu-call { min-height: 0; line-height: inherit; }
  .btn[href^="tel:"] { line-height: 1.4; }
}


/* -------- 22. Coverage treatment --------------------------------------
   Every covered area is presented identically. An area with a local destination
   carries a link and one without does not, and nothing here makes the linked one
   look more served: same weight, same colour, same spacing. Link state is a
   portfolio fact, and a reader would only ever misread a visual difference as
   being half-served. */
.coverage-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 8px 18px;
}
.coverage-list li {
  font-size: 15px; line-height: 1.5;
  color: inherit;
}
.coverage-list a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.coverage-list a:hover, .coverage-list a:focus-visible { color: var(--hazard); }

/* On the area page it reads as a set of places rather than a footer utility row. */
.coverage-list--wide { gap: 12px 28px; margin-top: 18px; }
.coverage-list--wide li { font-size: 17px; font-weight: 500; }

footer .coverage-list li { color: rgba(255, 255, 255, .88); }
footer .footer-h3--spaced { margin-top: 26px; }

/* Card media for the three-up service block. Fixed ratio so three cards of
   unequal copy length still present a level top edge. */
.svc-grid { margin-top: 26px; row-gap: 30px; }
/* .cover-img is `position: absolute; inset: 0` because it exists for full-bleed hero
   photography, and `picture` is `display: contents`. A card thumbnail therefore needs
   its own positioned box with a real height, or the anchor collapses to its border
   and becomes a 3px target. */
.svc__media {
  display: block; position: relative; height: 190px;
  margin-bottom: 16px; overflow: hidden;
  border-bottom: 3px solid var(--hazard);
}
@media (max-width: 991px) { .svc__media { height: 220px; } }
.svc h3 a { color: var(--ink); text-decoration: none; }
/* --hazard-dark is a BORDER and FILL colour, never text on white: it measures
   2.16:1 there (2.08:1 before the 2026-08-25 hue correction), so this service
   heading dropped from 12.59:1 to about a fifth of the AA floor the instant a
   pointer touched it. SC 1.4.3 applies to every state of the text, not only the
   resting one. --hazard-ink is the hue MADE LEGIBLE as text and measures 6.06:1
   on white, which is what this line always wanted. Found while re-measuring the
   pairs the hue correction touches; it was wrong before that change too. */
.svc h3 a:hover, .svc h3 a:focus-visible { color: var(--hazard-ink); }
.svc-after { margin-top: 30px; }

/* A plain list of routes. No checkmark markers: on the 404 they read as
   confirmations of something that just failed. */
.link-list { list-style: none; margin: 0 0 25px; padding: 0; }
.link-list li { position: relative; padding-left: 18px; margin-bottom: 9px; }
.link-list li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 8px; height: 2px; background: var(--hazard-dark);
}

/* ==========================================================================
   THE REFINEMENT PASS — 2026-08-25
   ==========================================================================
   Measured on the served build rather than judged from the source:

     - getComputedStyle over every element of the home page returned exactly ONE
       distinct box-shadow, and it is an `inset` outline on a button state. So
       nothing on any page sat above the page.
     - ZERO animations and ZERO reveal targets. The `js` flag is set inline
       before first paint and nothing used it.
     - Every section was 60px top and bottom.

   WHAT WAS ALREADY RIGHT, AND IS LEFT ALONE. The hero mask is already
   directional — `linear-gradient(90deg, .88 -> .5)`. The `js` flag is already
   set inline in <head>. The brand tokens were corrected earlier today, along
   with a service-heading hover that measured 2.16:1 on white.

   THE DIAGONAL HERE RUNS AT 45 DEGREES, like the Bronx property and unlike
   Staten Island's 135. `#get_quote` has always hatched at 45 and this pass does
   not rotate a site's existing axis to match a sibling. `--hatch-step` is
   therefore 12.728px (9 / cos 45), NOT the 39.598px that belongs to a 28px
   period.

   THE DRAWN RULE IS CENTRED, because `.section-title` is `text-align: center`
   here. A rule that grows from the left under a centred heading points at
   nothing.
   ========================================================================== */

/* -------- QN.1 Tokens the pass needs ------------------------------------ */
:root {
  --lift-1: 0 1px 2px rgba(28, 24, 16, .05), 0 2px 8px -3px rgba(28, 24, 16, .09);
  --lift-2: 0 2px 5px rgba(28, 24, 16, .06), 0 12px 26px -8px rgba(28, 24, 16, .16);
  --lift-3: 0 4px 10px rgba(28, 24, 16, .07), 0 26px 52px -14px rgba(28, 24, 16, .26);

  --ease-out: cubic-bezier(.22, .61, .36, 1);

  /* One period of the 45deg / 9px hatching measured along the x axis:
     9 / cos(45deg) = 12.728px. Translating by exactly this lands the pattern on
     pixel-identical content, which is what makes the drift seamless. */
  --hatch-step: 12.728px;

  /* A hairline in the hue, for a surface being pointed at. */
  --hazard-line: #f7d97e;
}

/* -------- QN.2 Surfaces, which could not say they were above ------------ */
.svc, .box_feat, .faq details, .coverage-item {
  box-shadow: var(--lift-1);
  transition: transform .3s var(--ease-out), box-shadow .3s var(--ease-out), border-color .2s ease;
}
.svc:hover, .svc:focus-within {
  /* Up and to the right, along this site's own 45 degree axis. */
  transform: translate3d(3px, -6px, 0);
  box-shadow: var(--lift-3);
  border-color: var(--hazard-line);
}
a.coverage-item:hover, a.coverage-item:focus-visible {
  transform: translate3d(2px, -4px, 0);
  box-shadow: var(--lift-2);
  border-color: var(--hazard-line);
}
.faq details[open] { box-shadow: var(--lift-2); }

/* The photograph moves under its own crop. `.svc__media` is already
   `overflow: hidden` with a positioned `.cover-img` inside it, so the crop is
   the frame and the image can move within it without touching the layout. */
.svc__media .cover-img { transition: transform .66s var(--ease-out); }
.svc:hover .svc__media .cover-img,
.svc:focus-within .svc__media .cover-img { transform: scale(1.06); }

/* -------- QN.3 The cut, drawn — and CENTRED ----------------------------
   THE REST STATE IS SCOPED TO `.js`, AND THAT IS THE WHOLE SAFETY ARGUMENT.
   An unscoped `scaleX(0)` is a mark only a script can restore, so it would be
   silently ABSENT for a reader whose JavaScript never ran. `.js` is already set
   inline in <head> before first paint on this site. */
.section-title h2::after {
  content: ""; display: block;
  width: 64px; height: 4px; margin: 16px auto 0;
  background: var(--hazard);
  /* The cut, mirrored to this site's own 45 degree axis. */
  clip-path: polygon(4px 0, 100% 0, calc(100% - 4px) 100%, 0 100%);
  transform-origin: center;
  transition: transform .78s var(--ease-out) .12s;
}
.js .section-title h2::after { transform: scaleX(0); }
.js .section-title.is-in h2::after { transform: scaleX(1); }

/* -------- QN.4 The hero opens out --------------------------------------
   `.cover-img` is a real <img> with srcset, so the element itself scales. Six
   per cent of scale across 34 seconds is below the rate at which movement is
   seen AS movement, so the band never appears to animate; a reader who scrolls
   back up simply finds the frame has opened out.

   Scoped to `.hero .cover-img` so the card thumbnails, which use the same class
   inside `.svc__media`, are NOT dragged into a 34-second animation they have no
   business in. That is a real hazard on this site specifically: `.cover-img`
   exists for full-bleed hero photography AND is reused for card media. */
@keyframes hero-open {
  from { transform: scale(1.015); }
  to   { transform: scale(1.075) translate3d(-1%, -.8%, 0); }
}
.hero > picture > .cover-img { animation: hero-open 34s var(--ease-out) infinite alternate; will-change: transform; }

/* -------- QN.5 The band's hatching drifts along its own axis ------------
   Seamless by construction: the layer is one full period wider than the band and
   travels exactly one period, so the frame it ends on is pixel-identical to the
   one it started on and there is no reset to see.

   `overflow: hidden` is required because the layer now starts one period LEFT of
   a full-bleed element; without it every width would gain a horizontal
   scrollbar. Nothing in the band sits at a negative offset, so clipping is safe. */
#get_quote { position: relative; overflow: hidden; background-image: none; }
#get_quote > .container { position: relative; z-index: 1; }
#get_quote::before {
  content: ""; position: absolute; top: 0; bottom: 0;
  left: calc(var(--hatch-step) * -1);
  width: calc(100% + var(--hatch-step));
  background-image: repeating-linear-gradient(45deg, rgba(0, 0, 0, .045) 0 2px, transparent 2px 9px);
  animation: hatch-drift 1.6s linear infinite;
  pointer-events: none;
}
@keyframes hatch-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(var(--hatch-step), 0, 0); }
}

/* -------- QN.6 Controls press ------------------------------------------- */
/* -------- The variant ring, composed rather than overwritten ------------
   A box-shadow does not cascade additively. The elevation block below sits AFTER
   .btn--ghost and .btn--outline, so a plain box-shadow there silently ERASED
   the inset rule that was those variants' only edge — leaving white text on a
   photograph with nothing around it. Equal specificity, later in the file, and
   nothing in the markup to show for it.

   So the ring travels as a custom property and every elevation state composes it
   back in. A variant declares its edge once and keeps it through rest, hover and
   press. The fallback matters: an unset custom property would invalidate the whole
   declaration at computed-value time and take the elevation with it. */
.btn {
  box-shadow: var(--btn-ring, 0 0 0 0 rgba(0, 0, 0, 0)), 0 1px 2px rgba(28, 24, 16, .12);
  transition: var(--ease), transform .2s var(--ease-out), box-shadow .2s var(--ease-out);
}
.btn:hover, .btn:focus-visible { transform: translateY(-2px); box-shadow: var(--btn-ring, 0 0 0 0 rgba(0, 0, 0, 0)), var(--lift-2); }
.btn:active { transform: translateY(0); box-shadow: var(--btn-ring, 0 0 0 0 rgba(0, 0, 0, 0)), 0 1px 2px rgba(28, 24, 16, .12); }

/* The read-more arrow travels and cannot orphan: a non-breaking space has no
   line-break opportunity in front of it, and the travel is on margin-left so the
   glyph moves without the layout moving. */
.link_normal::after { transition: margin-left .24s var(--ease-out); }
.link_normal:hover::after, .link_normal:focus-visible::after,
.svc:hover .link_normal::after { margin-left: 5px; }

/* -------- QN.7 Section rhythm -------------------------------------------
   Every section was 60px top and bottom. The two moments meant to land harder
   get more room than the sections that merely continue. */
.bg_content { padding: calc(var(--section) * 1.45) 0; }
#get_quote { padding: 38px 0; }

/* -------- QN.8 The motion vocabulary ------------------------------------
   This site had NO reveal of any kind, on a page whose `js` flag was already set
   and unused. Four families now arrive differently, and the difference says what
   kind of thing is arriving:

     a section title          rises a little, quickly, and gets out of the way
     a card, tile or stage    arrives along the cut, up and to the right
     a photograph             rises and settles out of a very slight scale
     the full-bleed band      enters from the left

   NOTHING IS HIDDEN UNTIL A SCRIPT HAS CONFIRMED IT CAN UNHIDE IT. Every rest
   state is scoped to `.js`. */
.js [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 20px, 0);
  transition: opacity .62s var(--ease-out), transform .62s var(--ease-out);
}
.js .section-title[data-reveal] {
  transform: translate3d(0, 12px, 0);
  transition-duration: .46s;
}
.js .box_feat[data-reveal],
.js .step[data-reveal],
.js .coverage-item[data-reveal],
.js .faq details[data-reveal] { transform: translate3d(-10px, 26px, 0); }
.js figure[data-reveal],
.js .inset[data-reveal] { transform: translate3d(0, 24px, 0) scale(.985); }
.js .bg_content[data-reveal] { transform: translate3d(-26px, 0, 0); transition-duration: .8s; }
.js [data-reveal].is-in { opacity: 1; transform: none; }

/* AND THE COLLISION THAT CREATES, RESTATED RATHER THAN LEFT TO LUCK.
   `.js [data-reveal].is-in { transform: none }` is (0,3,0) and
   `a.coverage-item:hover` is (0,2,1). A coverage tile is itself a reveal target,
   so the FINISHED REVEAL OUTRANKS THE LIFT on every tile a reader can actually
   point at, and the hover does nothing while looking perfectly correct in the
   source. Declared at a weight that wins, next to the rule it loses to.

   `.svc` is deliberately NOT in this list and carries no reveal rules at all.
   It renders in exactly one place on this site - the homepage showcase - and a
   card in a carousel must never carry `data-reveal`, because behaviours.js
   clones the set at runtime and a clone would sit at opacity 0 for ever. Checked
   by counting: three `.svc` elements on the whole site, all of them in the
   track. Rules for a state it can never reach would be dead code. */
.js a.coverage-item[data-reveal].is-in:hover,
.js a.coverage-item[data-reveal].is-in:focus-visible { transform: translate3d(2px, -4px, 0); }

/* ==========================================================================
   QN.9 THE SERVICES SHOWCASE — SiteKit animated-carousel-v1, 2026-08-25
   ==========================================================================
   Declared at project.ui_contracts.services_showcase on explicit operator
   instruction, and the operator's instruction for THIS site was specifically
   "declare it and re-derive the widths". That is because of the paragraph below,
   which is the whole reason this file's arithmetic differs from every sibling's.

   THIS SITE PUBLISHES THREE SERVICES, NOT FOUR, AND THAT CHANGES EVERYTHING.
   behaviours.js measures the ORIGINAL cards and gives a set that already fits
   the track no clones, no controls and NO MOTION — deliberately, because arrows
   that cannot act read as broken. Measured on the served build BEFORE this
   change:

       3 cards x 347px + 2 gutters x 30px  =  1101px
       track (1140 container - 40px padding) =  1100px

   One pixel of overflow. The set filled the track EXACTLY. Ported at the sibling
   widths — 29% of the track, tuned for four cards — three cards would have
   measured 3 x 319 + 60 = 1017 against a 1100 track and FITTED WITH ROOM TO
   SPARE, and this carousel would have been contract-compliant, validator-clean
   and permanently still. Every gate would have gone green on a component that
   never moved.

   So the visible-card count has to come DOWN rather than up. Four-card sites show
   about 3.2 cards; a three-card site cannot, because 3.2 of 3 is all of them.
   This one shows about 2.4, which is the widest set that still leaves most of a
   card pitch of real travel.
   ========================================================================== */

.showcase { position: relative; }

/* -------- QN.9.1 The controls ------------------------------------------
   Real <button>s with real accessible names. Graphite on white, not hazard: this
   page already carries a filled-hazard primary action in the header, the hero and
   the get-quote band, and yellow arrows beside the service cards would compete
   with the call button for the same attention. */
.showcase-nav { display: flex; justify-content: flex-end; gap: 10px; margin: -14px 0 18px; }
.cbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;
  border: 1px solid var(--rule); border-radius: 2px;
  background: var(--white); color: var(--ink);
  font-size: 17px; line-height: 1; cursor: pointer;
  box-shadow: var(--lift-1);
  transition: var(--ease), transform .22s var(--ease-out), box-shadow .22s var(--ease-out);
}
.cbtn:hover { background: var(--hazard); border-color: var(--hazard); color: var(--ink); box-shadow: var(--lift-2); }
/* The travel matches the direction the control moves the track. */
.cbtn[data-carousel-prev]:hover { transform: translateX(-2px); }
.cbtn[data-carousel-next]:hover { transform: translateX(2px); }
.cbtn:active { transform: translateY(1px); box-shadow: var(--lift-1); }
.cbtn:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

/* -------- QN.9.2 The track ----------------------------------------------
   `overflow-x: auto` computes overflow-y to auto as well, so a card that lifts on
   hover is clipped by its own track unless the track is padded. The negative
   inline margin puts the cards back on the container's own edge. */
.showcase-track {
  gap: var(--gutter);
  margin: 0 -8px;
  padding: 10px 8px 26px;
  list-style: none;
  /* The arrows are the affordance, and a scrollbar under a track that is always
     drifting is a control that appears to move on its own. Finger, trackpad and
     keyboard scrolling are untouched. */
  scrollbar-width: none;
}
.showcase-track::-webkit-scrollbar { display: none; }
.showcase-item { display: flex; }
.showcase-item > .svc { width: 100%; }

/* -------- QN.9.3 How many cards a width shows — THREE, not four ---------
   The arithmetic is 3 x basis + 2 x 30 must exceed the track W. Percentages
   resolve against the track, so the break-even basis is (W - 60) / 3W, which is
   **31.5%** — anything at or below that is a still carousel. Every value below
   clears it by a wide margin, and each carries its own figure.

   Note how much larger these are than a four-card site's. That is not a
   preference: with three cards the only way to overflow is a genuinely wider
   card, and a wider card is also the right composition for a three-service
   showcase. */
.showcase-item { flex: 0 0 84%; }                                   /* 3 x 84% = 252% of W */

@media (min-width: 560px) { .showcase-item { flex-basis: 72%; } }   /* 216% of W */
@media (min-width: 768px) { .showcase-item { flex-basis: 62%; } }   /* 186% of W */
@media (min-width: 992px) { .showcase-item { flex-basis: 52%; } }   /* 156% of W */

@media (min-width: 1200px) {
  /* 42% of the 1100px content width is 462px, so the set measures
     3 x 462 + 2 x 30 = 1446 against a 1100 track: **346px of overflow**, which is
     most of a card pitch of real travel. Roughly 2.4 cards are visible, which is
     also the composition that tells a reader the row continues.

     THE BREAK-EVEN IS 31.5% AND THE SIBLINGS USE 29%. Never copy a four-card
     site's widths into this one, and never narrow these without re-doing the
     arithmetic above. The failure is silent: a still carousel that passes every
     gate. */
  .showcase-item { flex-basis: clamp(320px, 42%, 480px); }
}

/* -------- QN.9.4 The pace ------------------------------------------------
   data-carousel-speed="28", in px/second, inside the behaviour's 10-70 clamp.
   The card pitch at the widest breakpoint is 462 + 30 = 492px, so a card advances
   one place every 17.6 seconds — LONGER than the eight-to-fourteen the stage
   asks for, and that is a consequence of the card being wider rather than a
   different pace. The number a reader actually perceives is the speed, and 28px/s
   is the same on this site as on every sibling, so the showcase reads as moving
   at the family's pace. Raising the speed to bring the pitch time down would make
   this site's showcase visibly faster than its siblings' for no reason a reader
   could name.

   NO data-reveal ON A CARD IN THE TRACK. behaviours.js clones the set at runtime
   and the reveal observer only watches what it queried at boot, so a cloned card
   would sit at opacity 0 for ever. The reveal is on the showcase root, which is
   never cloned. `.svc` renders in exactly ONE place on this
   site, the homepage showcase, so unlike every sibling there is no static grid
   of the same component to keep in step with. */


/* -------- QN.9.5 One link per card, and the whole card is it ------------
   The heading anchor is the one link, the one tab stop and the one crawlable
   destination; its ::after covers the whole card so a pointer can still click
   the photograph. See the note in scripts/build-site.mjs for why the
   aria-hidden image link this replaced cannot live inside a carousel track.

   The "What this involves" line becomes a <span aria-hidden> rather than a
   second anchor: two links to one destination inside one card is exactly the
   duplicate the contract means by "reachable exactly once", and the stretched
   link already makes the whole card that destination. It still reads as the
   affordance it always was.

   The known cost of a stretched link is that card text cannot be drag-selected.
   That is the accepted trade for three service cards whose body is a summary. */
.svc--linked { position: relative; }
.svc--linked .svc-stretch::after { content: ""; position: absolute; inset: 0; z-index: 1; }
.svc--linked .svc-stretch:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }
.svc--linked .link_normal { color: var(--hazard-ink); font-weight: 500; }
/* The controls sit above the stretched links so they stay clickable. */
.showcase-nav { position: relative; z-index: 2; }
/* .svc__media is a <span> now rather than an <a>, so it needs the block display
   the anchor used to give it or its positioned .cover-img has nothing to sit in. */
.svc__media { display: block; }

/* ==========================================================================
   THE HERO LEAD FORM — 2026-08-26
   ==========================================================================
   Operator instruction: the form goes in the hero, on every property in the
   cluster.

   THE HERO IS NOW TWO COLUMNS at desktop, statement left and form right, and the
   mask on this site was ALREADY directional — heaviest where the copy is and
   clearing to the far side. The treatment that was already correct for the
   photograph turns out to be correct for a light card sitting on it.

   BELOW 62rem THE FORM GOES UNDER THE STATEMENT rather than beside it. A form
   squeezed into half a phone screen is a form nobody completes.
   ========================================================================== */
.hero__form, .hero_form { width: 100%; }
@media (min-width: 62rem) {
  /* SCOPED TO A HERO THAT HAS A FORM. The form is on the homepage only, and every
     other page shares this same hero template. An unscoped two-column rule would
     leave those pages holding an empty right-hand column, which reads as a broken
     container rather than as a decision. */
  .hero .container:has(.lead-form), .hero .opacity-mask > .container:has(.lead-form) {
    display: grid;
    grid-template-columns: minmax(0, 1.12fr) minmax(0, .88fr);
    gap: 52px;
    align-items: center;
  }
  .hero__form, .hero_form { justify-self: end; max-width: 26.5rem; }
}
@media (max-width: 61.99rem) {
  .hero__form, .hero_form { margin-top: 32px; max-width: 30rem; }
}

/* -------- The form is a card, because it is genuinely a surface ----------
   This is the one place on the page where a surface really IS above another: a
   light panel on a photograph. It takes the top of the elevation scale and a 4px
   hazard edge, so the brand owns the moment rather than the browser's default
   white. */
.lead-form {
  background: var(--white, #fff);
  border-top: 4px solid var(--hazard, var(--primary, #f6bd19));
  border-radius: 3px;
  box-shadow: var(--lift-3, 0 4px 10px rgba(28,24,16,.07), 0 26px 52px -14px rgba(28,24,16,.26));
  padding: 24px 24px 20px;
  color: var(--ink, #343333);
  text-align: left;
}
.lead-form-heading { font-size: 1.14rem; margin: 0 0 15px; color: var(--ink, #343333); }

.lead-row { display: grid; gap: 0 13px; }
@media (min-width: 30rem) { .lead-row { grid-template-columns: 1fr 1fr; } }

.lead-field { margin: 0 0 13px; }
.lead-field label {
  display: block; font-size: .8rem; font-weight: 700; letter-spacing: .02em;
  color: var(--ink, #343333); margin-bottom: 6px;
}
.lead-optional { font-weight: 400; color: var(--steel, var(--body, #565a5c)); letter-spacing: 0; }
.lead-field input,
.lead-field textarea {
  width: 100%; box-sizing: border-box;
  font: inherit; font-size: .95rem; color: var(--ink, #343333);
  background: var(--white, #fff);
  border: 1px solid var(--rule, #dddcd8); border-radius: 2px;
  padding: 10px 12px;
  /* 44px minimum, like every other control. A field a thumb misses is a lead
     lost at the last step. */
  min-height: 44px;
  transition: border-color .18s ease, box-shadow .18s ease;
}
.lead-field textarea { min-height: 64px; resize: vertical; }
.lead-field input:hover,
.lead-field textarea:hover { border-color: #c9c9c6; }
.lead-field input:focus-visible,
.lead-field textarea:focus-visible {
  outline: 3px solid var(--focus, var(--hazard-ink, var(--primary-ink, #7a5f00)));
  outline-offset: 2px;
  border-color: var(--hazard, var(--primary, #f6bd19));
}

/* THE HONEYPOT IS NOT `display: none`. A field a bot can detect as hidden is a
   field it learns to skip, and some assistive technology skips display:none too,
   which is why it also carries aria-hidden and tabindex="-1". Positioned out of
   the flow instead: no space, and unreachable by keyboard, pointer or screen
   reader. */
.lead-hp {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.lead-actions { margin: 2px 0 0; }
.lead-actions .btn, .lead-actions button { width: 100%; text-align: center; }

/* `role="status" aria-live="polite"`, so it is announced when it changes and
   silent when empty. It reserves no space until it has something to say, because
   an empty gap under a button reads as a missing element. */
.lead-status { margin: 11px 0 0; font-size: .88rem; line-height: 1.45; }
.lead-status:empty { display: none; }
.lead-status[data-state="busy"]  { color: var(--steel, var(--body, #565a5c)); }
.lead-status[data-state="ok"]    { color: #1c6b3a; font-weight: 700; }
.lead-status[data-state="error"] { color: #a3261c; font-weight: 700; }

.lead-noscript { margin: 0 0 11px; font-size: .88rem; color: var(--steel, var(--body, #565a5c)); }
.lead-consent { margin: 11px 0 0; font-size: .78rem; line-height: 1.5; color: var(--steel, var(--body, #565a5c)); }

/* -------- Two primaries in one region, and why that is allowed -----------
   The hero now carries a filled call button and a filled submit. The probe reads
   two competing primaries and that is a fair reading of the pixels, but they are
   one action per REGION: the statement column offers the call, the form column
   offers the send, and the card's elevation is what makes the second region a
   region rather than a loose second button.

   The submit is deliberately not a ghost. A send control quieter than the button
   beside it steers the reader away from the thing just added. */


/* ==========================================================================
   THE HERO CLIP — 2026-08-26
   ==========================================================================
   Operator instruction: the hero carries a video, as the baseline's does.

   THE ELEMENT IS EMITTED ONLY WHEN A CLIP ACTUALLY EXISTS. The build checks for
   the file and, finding none, renders the hero exactly as it renders today and
   says so on the console. A <video> pointing at a 404 is a broken element that
   every check passes, which is the worst of the three possible states. Drop a
   clip at the path the build names and it appears with no further edit.

   THE POSTER IS NOT A FALLBACK, IT IS THE HERO. It paints immediately, carries
   the LCP, and is the whole hero for a reader who never receives the clip: on a
   reduced-motion preference, on a metered or 2G connection, before load fires,
   and wherever autoplay is declined. The clip is layered ON it at the same
   stacking position, so there is no reflow and nothing to see if it never comes.

   NO KEN BURNS ON A HERO THAT CARRIES A CLIP. Scaling a still under its own
   playback is two motions arguing, and it is why the baseline leaves its hero as
   the one section its refinement pass gave no drift.
   ========================================================================== */
.hero { position: relative; }
.hero-video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: 0;
  pointer-events: none;
}
/* The clip sits UNDER everything the hero says. Both the masked and unmasked
   hero shapes in this cluster are covered, because a z-index on a static element
   does nothing and the omission would put the clip over the headline. */
.hero > .hero__mask { z-index: 1; }
.hero > .container,
.hero > .opacity-mask,
.hero__form { position: relative; z-index: 2; }


/* ==========================================================================
   QN.10 THE REDUCED-MOTION PATH, RESTATED
   ==========================================================================
   THIS BLOCK IS LOAD-BEARING AND MUST STAY LAST IN THIS FILE.

   The stylesheet already resets motion under the preference further up, and that
   block does NOT reset `opacity` or `transform`. Every reveal above arms itself
   at `opacity: 0`, so without this a reader who asked for less motion would get a
   page whose content was permanently invisible, with the transition meant to
   reveal it switched off — and the probe captures WITH the preference emulated,
   so the screenshots meant to prove this design would have been screenshots of
   the defect.

   `transform: none` is the identity transform, which is what the drawn rule
   wants: a bar that never animates is a bar at full width. */
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal],
  .js .section-title[data-reveal],
  .js .box_feat[data-reveal],
  .js .step[data-reveal],
  .js .coverage-item[data-reveal],
  .js .faq details[data-reveal],
  .js figure[data-reveal],
  .js .inset[data-reveal],
  .js .bg_content[data-reveal] { opacity: 1 !important; transform: none !important; }

  .js .section-title h2::after { transform: none !important; }

  .hero > picture > .cover-img,
  #get_quote::before { animation: none !important; transform: none !important; }

  /* And the hover travel, consistently. Suppressing transforms on reveal targets
     already killed the card and tile lift; the buttons, the card photographs and
     the carousel controls are not reveal targets and would otherwise still
     displace themselves while their neighbours did not. Nothing about the
     affordance is lost: every one still answers a pointer with its elevation and
     its colour. */
  .btn:hover, .btn:focus-visible, .btn:active,
  .svc:hover, .svc:focus-within,
  .svc:hover .svc__media .cover-img, .svc:focus-within .svc__media .cover-img,
  .cbtn:hover, .cbtn:active { transform: none !important; }
}
