/* ==========================================================================
   Breakara Demolition Bronx
   Design language adapted from the local Envato "Removals" v2.5 template
   (Ansonika). Composition, hierarchy, density, navigation, CTA language,
   component treatment and motion character are carried forward; the 2016-era
   implementation (Bootstrap, jQuery, Owl, Magnific, LayerSlider, remote
   Google Fonts) is not. See DESIGN-DNA.md.
   ========================================================================== */

/* -------- 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. `assets-src/brand/logo.png` is byte-identical to
   the mark the other Points ship — same 656x168 lockup, same file — and sampling
   it with sharp at alpha >= 200 gives, as its dominant saturated pixel and its
   dominant neutral:

       #f6bd19 (3863 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,
   which are the cluster's canonical values rather than a fresh invention.
   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 with
   it. */
: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; }

/* 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-3 { grid-column: span 3; }
.col-4 { grid-column: span 4; }
.col-6 { grid-column: span 6; }
.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;
  /* 13px padding against a 14px font landed the box at 43px, one short of the
     44px touch minimum on every button on the site. Set the floor explicitly
     rather than tuning padding, so a future type change cannot reintroduce it. */
  min-height: 44px;
  line-height: 18px;
}
.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); }
/* .btn--ghost is a DARK-GROUND button: white text on a white inset rule. Placed on
   a light section it renders white on near-white and disappears completely. This is
   the light-ground twin, and it is the one to use anywhere outside the hero and the
   dark bands. */
.btn--outline {
  background: transparent;
  color: var(--ink);
  --btn-ring: inset 0 0 0 2px var(--ink);
  box-shadow: var(--btn-ring);
}
.btn--outline:hover, .btn--outline:focus { background: var(--ink); color: var(--white); }
/* Compact variant for dense furniture such as the footer contact column. Stays at
   44px minimum so it remains a legitimate touch target. */
.btn--sm { padding: 12px 18px; font-size: 14px; min-height: 44px; line-height: 20px; }
/* Two actions side by side, wrapping to a stack on narrow widths without losing
   the gap between them. */
.btn-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
/* A closing action under a block of cards, so a long scroll is never left without
   a way to act. Centred, with the prompt and the control on one line where there
   is room and stacked where there is not. */
.main_title__action { margin: 18px 0 0; }
.section-action {
  display: flex; flex-wrap: wrap; gap: 14px;
  align-items: center; justify-content: center;
  margin: 34px 0 0; color: var(--steel);
}

/* 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 { color: var(--ink); }
#top_links a:hover { color: var(--hazard-ink); }

/* 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 call link fills the compressed line rather than sitting inside it, so the sticky target is
   the full 26px. That clears WCAG 2.5.8's 24px minimum while keeping the scrolled header compact;
   the comfortable 44px target is the fixed call bar, which takes over below 768. */
.sticky #top_links, .sticky #top_links li { height: 100%; }
.sticky #top_links a { display: flex; align-items: center; height: 100%; }
/* #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;
  padding: 14px 0;
  transition: padding .2s ease;
}
.sticky .header-inner { padding: 8px 0; }

/* 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; }
#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 h3 a { color: inherit; }
.card h3 a:hover, .card h3 a:focus-visible { color: var(--hazard-ink); }
.card p { margin-bottom: 12px; font-size: 14.5px; }
/* The whole card image is a link to the same destination as its heading. It carries
   tabindex="-1" and aria-hidden so it is not a second tab stop announcing the same
   thing, and the heading link remains the single accessible route. */
.card > a[aria-hidden="true"] { display: block; }

/* Definition split: a term and its answer, used where a reader is being sorted
   between services rather than read a list. */
.split { margin: 0; }
.split dt {
  font-weight: 700;
  color: var(--ink);
  padding-top: 14px;
  border-top: 1px solid var(--rule);
  margin-top: 14px;
}
.split dt:first-child { border-top: 0; padding-top: 0; margin-top: 0; }
.split dd { margin: 6px 0 0; color: var(--steel); font-size: 15px; }

/* -------- 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(--hazard-dark);
  border-bottom: 2px solid var(--hazard-dark);
  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; }
/* Single column, for a sequence read inside a half-width column rather than across
   a full-width band. */
.steps--stack { grid-template-columns: 1fr; margin: 0 0 26px; gap: 22px; }
.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) {
  /* Two columns at this width leave the frame narrow; 4:3 keeps it usable
     without pushing the footer taller than the columns beside it. */
  .map-block--footer .map-frame iframe { aspect-ratio: 4 / 3; }
}

/* -------- 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; }

/* -------- 16b. Coverage tiles ------------------------------------------
   Every covered area renders through this one component. A linked area and an
   unlinked area differ ONLY in the element used (<a> vs <span>): same box, same
   type, same padding, same prominence. Nothing here encodes "second-class",
   because no covered area is. `is-here` marks the page you are already on. */
.coverage {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.coverage__tile {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 56px;
  padding: 12px 14px;
  text-align: center;
  border: 1px solid var(--rule);
  background: #fff;
  color: var(--ink);
  font-weight: 700;
  font-size: 15px;
  line-height: 1.2;
  transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.coverage__tile svg { width: 18px; height: 18px; flex: 0 0 auto; color: var(--hazard); }
a.coverage__tile:hover,
a.coverage__tile:focus-visible {
  border-color: var(--hazard);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .09);
  transform: translateY(-2px);
  text-decoration: none;
}
.coverage__tile.is-here { border-color: var(--hazard); background: var(--concrete); }

/* On a dark band the tiles invert, keeping the same geometry. */
.bg_content .coverage__tile,
.band-dark .coverage__tile {
  background: rgba(255, 255, 255, .06);
  border-color: rgba(255, 255, 255, .22);
  color: #fff;
}
.bg_content a.coverage__tile:hover,
.band-dark a.coverage__tile:hover { border-color: var(--hazard); background: rgba(255, 255, 255, .12); }

/* -------- 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 links are a common tap-target failure: 14.5px text in a 9px-spaced list
   gives a ~20px target. Padding lifts every footer link to 44px without changing
   the visual rhythm, because the padding is vertical and the row spacing absorbs it. */
footer ul li { margin-bottom: 0; }
footer ul li > a,
footer ul li > span { display: inline-block; padding: 11px 0; min-height: 22px; }
.footer-note { margin-top: 14px; color: rgba(255, 255, 255, .62); font-size: 13.5px; }
.footer-note a { color: rgba(255, 255, 255, .88); }
.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 (template data-reveal character) --------------
   Gated on html.js. Without JavaScript nothing is hidden, so a failed or
   blocked script can never leave the page's content invisible. */
.js [data-reveal] { opacity: 0; transform: translateY(18px); transition: opacity .5s ease, transform .5s ease; }
.js [data-reveal="zoomIn"] { transform: scale(.95); }
.js [data-reveal="fadeIn"] { transform: none; }
.js [data-reveal].is-in { opacity: 1; transform: none; }

/* -------- 21. Responsive ----------------------------------------------- */
@media (max-width: 991px) {
  .col-3, .col-4 { grid-column: span 6; }
  .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-3, .col-4, .col-6, .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; }

  /* 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;
  }
  /* clears the 54px sticky call bar with room to spare */
  body { padding-bottom: 64px; }
  #toTop { bottom: 64px; }

  /* 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, .main-menu .menu-call { min-height: 0; line-height: inherit; }
  /* A .btn sizes itself and already clears 44px. Exempt it from the tel rule's
     line-height only — zeroing its min-height here is what previously left the
     primary call button at 43px on every mobile page. */
  .btn[href^="tel:"] { line-height: 18px; }
}

@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; }
  .js [data-reveal] { opacity: 1; transform: none; }
}

@media print {
  header, footer, #get_quote, #toTop, #call_bar, #copy { display: none; }
  body { color: #000; }
}

/* -------- 21b. Touch targets ------------------------------------------
   Below 992 the site is a touch surface, and three families of link were
   rendering as 18-27px hairlines: the service card headings, which are the
   primary route into every service page; the `link_normal` read-on links,
   which are real navigation and not decoration; and the footer note links.
   Height is added with padding rather than by growing the type, so the visual
   rhythm is unchanged and only the hit area moves. */
@media (max-width: 991px) {
  .card h3 a,
  .link_normal,
  .footer-note a,
  #position a,
  .list_ok a,
  .split dd a {
    display: inline-block;
    min-height: 44px;
    padding-top: 10px;
    padding-bottom: 10px;
  }
  /* The heading already carries its own margin, so the added padding would
     double the gap under a card title. Pull it back by what was added. */
  .card h3 { margin-bottom: 0; }
  .card h3 a { padding-top: 0; }
  .list_ok li:has(> a) { margin-bottom: 0; }
  #position a { padding-top: 6px; padding-bottom: 6px; min-height: 32px; }
}

/* -------- 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:"], .main-menu .menu-call { min-height: 0; line-height: inherit; }
  .btn[href^="tel:"] { line-height: 18px; }
}

/* ==========================================================================
   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 only two
       distinct box-shadows, and BOTH are `inset` outlines on button states. So
       nothing on any page sat above the page: not a card, not a bordered image,
       not a tile.
     - ZERO animations.
     - Every section was 60px top and bottom apart from one at 80px.
     - Fourteen [data-reveal] elements shared ONE reveal, at one distance and one
       speed, so nothing arrived differently from anything else.

   WHAT WAS ALREADY RIGHT, AND IS LEFT ALONE. More here than on any sibling.
   The hero mask is already directional — `linear-gradient(90deg, .88 -> .5)`.
   The `js` flag is already set inline before first paint. The reveal already has
   an IntersectionObserver, a `data-delay` stagger AND a 1.5-second failsafe that
   shows anything an observer missed, which is a stronger guarantee than any
   sibling shipped. `.container > .measure:only-child` is already centred. The
   brand tokens were corrected earlier today. This pass adds weight, depth and
   movement to a site that was already carefully built.

   THE MOTIF IS THE DIAGONAL, AND THIS SITE'S DIAGONAL RUNS AT 45 DEGREES.
   `#get_quote` has always hatched at 45, and the map placeholders at 45. Staten
   Island's runs at 135 because that is the angle of the slice in its own logo,
   and this pass does NOT rotate a site's existing axis to match a sibling: it
   would be a change with no defect behind it and it would leave every hatch on
   the page disagreeing with the band it came from. Bronx keeps its own axis, and
   everything the pass adds runs along it.
   ========================================================================== */

/* -------- BX.1 Tokens the pass needs -----------------------------------
   Warm rather than neutral black: this brand is a warm yellow on a warm
   graphite, and a pure-black shadow under a warm surface reads as dirt. */
: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 the pattern by exactly this lands it
     on pixel-identical content, which is what makes the drift seamless and why
     there is no reset to see. NOT 39.598px — that is the figure for a 28px
     period, which is Staten Island's hatch and not this one. */
  --hatch-step: 12.728px;

  /* A hairline in the hue, for a surface being pointed at. The palette had no
     such value, so a hover that wanted to warm a border had to reach for the
     full-strength hazard. */
  --hazard-line: #f7d97e;
}

/* -------- BX.2 Surfaces, which could not say they were above ------------
   The template's signature image treatment is a bordered inset: a 1px rule, 4px
   of white padding, and the photograph inside it. That frame is the surface on
   this site, and it is what lifts. */
.card > img, .card picture img, .inset {
  box-shadow: var(--lift-1);
  transition: transform .4s var(--ease-out), box-shadow .34s var(--ease-out), border-color .2s ease;
}
.card:hover > img, .card:focus-within > img,
.card:hover picture img, .card:focus-within picture img {
  /* 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);
}
.card h3, .card .link_normal { transition: color .2s ease; }

.box_feat, .faq details, .coverage-item, .stat {
  box-shadow: var(--lift-1);
  transition: transform .3s var(--ease-out), box-shadow .3s var(--ease-out), border-color .2s ease;
}
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); }

/* -------- BX.3 The cut, drawn — and CENTRED, because the heading is -----
   `.section-title` is `text-align: center` on this site. A rule that grows from
   the left under a centred heading points at nothing and reads as a mistake, so
   this one grows from its CENTRE, which is where the heading's own axis is. The
   Staten Island property draws its rules from the left because its sideheads are
   left-aligned. Same idea, following the composition it is actually in.

   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); }

/* -------- BX.4 The hero opens out ---------------------------------------
   `.cover-img` is a real <img> with srcset and LCP priority, so the element
   itself scales and no background-image trick is needed. 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.

   `.hero` already declares `overflow: hidden`, so the scaled frame is contained. */
@keyframes hero-open {
  from { transform: scale(1.015); }
  to   { transform: scale(1.075) translate3d(-1%, -.8%, 0); }
}
.hero .cover-img { animation: hero-open 34s var(--ease-out) infinite alternate; will-change: transform; }

/* -------- BX.5 The band's hatching drifts along its own axis -------------
   `#get_quote` has always carried a 45 degree hatch and sat perfectly still. It
   is the brand's own mark, so it moves in the brand's own direction: one hatch
   period along the 45 degree axis, seamless by construction because the layer is
   one full period wider than the band and travels exactly one period.

   `overflow: hidden` is required because the layer now starts one period LEFT of
   a full-bleed element, and 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); }
}

/* -------- BX.6 Controls press --------------------------------------------
   A button that changes colour on hover and nothing else reads as a link with a
   background. `--ease` on this site is already a list of named properties rather
   than `all`, which is the correction the hub needed, so transform is simply
   added to it. */
/* -------- 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,
.card:hover .link_normal::after { margin-left: 5px; }

/* -------- BX.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; }

/* -------- BX.8 Four reveal families, where there was one ----------------
   Fourteen [data-reveal] elements shared a single fade at one distance and one
   speed. 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
     a full-bleed band        enters from the left

   THE MECHANISM IS THIS SITE'S OWN AND IS NOT REPLACED. It already has an
   IntersectionObserver, a data-delay stagger and a 1.5-second failsafe that
   shows anything an observer missed. That failsafe is stronger than any sibling
   ships and it stays. Only the ATTRIBUTE changed, from the private `data-reveal` to
   the Factory's sanctioned `data-reveal`, so that safeguards.css's reduced-motion
   floor covers this site too. */
.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 .card[data-reveal],
.js .box_feat[data-reveal],
.js .step[data-reveal],
.js .coverage-item[data-reveal],
.js .stat[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.

   The card's hover moves its IMAGE, not the card, and the image is not a reveal
   target, so it is unaffected — but it is written out here so the next person
   does not have to work that out. */
.js a.coverage-item[data-reveal].is-in:hover,
.js a.coverage-item[data-reveal].is-in:focus-visible { transform: translate3d(2px, -4px, 0); }

/* ==========================================================================
   BX.9 THE SERVICES SHOWCASE — SiteKit animated-carousel-v1, 2026-08-25
   ==========================================================================
   Declared at project.ui_contracts.services_showcase on explicit operator
   instruction. Before it the four services were a static four-up grid and this
   repository was a legacy candidate, which preflight --post-refine correctly
   failed on the UI-contract rule as migration debt.

   SiteKit's behaviours.js owns the endless loop, the clone sets on both sides,
   the arrows and every hold on automatic motion, and carries SITEKIT-CAROUSEL-V1
   which production-integrity.mjs requires of any page declaring
   [data-carousel-autoplay]. safeguards.css already makes the track a native
   scroll container, already sets every child to flex: 0 0 auto, and already
   exempts an autoplaying track from snapping for its whole life.

   THE CARD WIDTHS DECIDE WHETHER IT MOVES AT ALL. behaviours.js measures the
   ORIGINAL cards, so a set that already fits gets no clones, no controls and NO
   MOTION — deliberately, because arrows that cannot act read as broken. The
   container is 1140px with 20px of padding each side, so the widest track is
   1100px and the old four-up grid fitted inside it comfortably.
   ========================================================================== */

.showcase { position: relative; }

/* -------- BX.9.1 The controls ------------------------------------------
   Real <button>s with real accessible names: an icon-only control that answers
   only a click is a control for a pointer and for nobody else.

   Graphite on white, not hazard. This page already carries a filled-hazard
   primary action in the header, in the hero and on the get-quote band; a pair of
   yellow arrows beside the service cards would compete with the call button for
   the same attention and win. */
.showcase-nav { display: flex; justify-content: flex-end; gap: 10px; margin: -18px 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; }

/* -------- BX.9.2 The track ----------------------------------------------
   `overflow-x: auto` computes overflow-y to auto as well, so a bordered image
   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 > .card { width: 100%; display: flex; flex-direction: column; }
.showcase-item > .card p { flex: 1 1 auto; }

/* -------- BX.9.3 How many cards a width shows ---------------------------
   4 x basis + 3 x 30 must exceed the track. Percentages resolve against the
   track, so anything at or above 26% overflows on its own. */
.showcase-item { flex: 0 0 84%; }                                   /* 4 x 84% = 336% of W */

@media (min-width: 560px) { .showcase-item { flex-basis: 62%; } }   /* 248% of W */
@media (min-width: 768px) { .showcase-item { flex-basis: 46%; } }   /* 184% of W */
@media (min-width: 992px) { .showcase-item { flex-basis: 40%; } }   /* 160% of W */

@media (min-width: 1200px) {
  /* 29% of the 1100px content width is 319px, so the set measures
     4 x 319 + 3 x 30 = 1366 against a 1100 track: 266px of overflow, which is
     most of a card pitch of real travel. Roughly 3.2 cards are visible, which is
     also the composition that tells a reader the row continues.

     NEVER narrow these without re-checking the overflow, and never widen the
     container without doing the same. The failure is silent: a still carousel
     that passes every gate. */
  .showcase-item { flex-basis: clamp(280px, 29%, 336px); }
}

/* -------- BX.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 319 + 30 = 349px, so a card advances
   one place every 12.5 seconds — inside the eight to fourteen the refine stage
   asks for.

   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. The card is otherwise identical to the one in the static grid it
   still renders in elsewhere, and the two must not drift apart. */


/* -------- BX.9.5 One link per card, and the whole card is it ------------
   THE STATIC GRID AND THE TRACK NEEDED DIFFERENT ANSWERS, and the gate is what
   found it. Everywhere else on this site a card wraps its photograph in
   `<a tabindex="-1" aria-hidden="true">` so the image is clickable for a pointer
   without becoming a second tab stop announcing the same destination as the
   heading beside it. That is a good pattern and it stays on /404.html,
   /about/ and /where-we-work/.

   Inside the carousel it fails the contract. production-integrity.mjs reads every
   anchor in [data-carousel-track] and rejects any that carries tabindex="-1" or
   aria-hidden, because in a track those are the marks of a CLONE and a clone must
   never be the reachable card. The check cannot tell a deliberately-hidden
   sibling link from a clone, and it is right not to try: "every service reachable
   exactly once" is the guarantee.

   So the track uses a stretched link instead. The heading anchor is the one link,
   the one tab stop and the one crawlable destination, and its ::after covers the
   whole card so a pointer can still click the photograph. One link, one tab stop,
   whole card clickable.

   The known cost is that text inside the card cannot be selected by dragging.
   That is the accepted trade for this pattern and it applies to four service
   cards whose body is a summary, not a passage anybody copies. */
.card--linked { position: relative; }
.card--linked .card-stretch::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
}
/* The controls sit above the stretched link so they stay clickable, and the
   focus ring belongs to the anchor rather than to the box it covers. */
.showcase-nav { position: relative; z-index: 2; }
.card--linked .card-stretch:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

/* ==========================================================================
   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; }


/* ==========================================================================
   BX.10 THE REDUCED-MOTION PATH, RESTATED
   ==========================================================================
   THIS BLOCK IS LOAD-BEARING AND MUST STAY LAST IN THIS FILE.

   Section 22 of this stylesheet already resets `[data-reveal]` under the
   preference, and this site's own script also adds `is-in` to everything
   immediately when the preference is set — two guarantees where most siblings
   had none. Neither of them resets the transforms this pass introduces, and
   every one of those is declared LATER in the cascade at equal or higher
   specificity, so without this block a reader who asked for less motion would
   get a page permanently displaced 26px on the diagonal.

   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 .card[data-reveal],
  .js .box_feat[data-reveal],
  .js .step[data-reveal],
  .js .coverage-item[data-reveal],
  .js .stat[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 .cover-img,
  #get_quote::before { animation: none !important; transform: none !important; }

  /* And the hover travel, consistently. Suppressing transforms on reveal targets
     already killed the coverage-tile lift; the buttons, the card images 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,
  .card:hover > img, .card:focus-within > img,
  .card:hover picture img, .card:focus-within picture img,
  .cbtn:hover, .cbtn:active { transform: none !important; }
}
