
/* ---------------------------------------------------------------------------
   Stoneva General Contractors Manhattan — the scope sheet
   Tokens are defined once here. See DESIGN-DNA.md for why each one is what it is.
   --------------------------------------------------------------------------- */

:root {
  --ground: #f7f5f1;
  --surface: #ffffff;
  --ink: #171513;
  --ink-muted: #5c554e;
  --accent: #9a3b25;
  --accent-deep: #6e2718;
  --line: #ded7cd;
  --line-strong: #c3b8aa;

  --ff-display: "Josefin Sans", "Trebuchet MS", sans-serif;
  --ff-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  --s1: 4px;  --s2: 8px;  --s3: 16px; --s4: 24px;
  --s5: 40px; --s6: 64px; --s7: 96px; --s8: 140px;

  --container: 1180px;
  --measure: 68ch;
  --radius: 2px;
}

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--ff-body);
  font-size: 19px;
  line-height: 1.65;
  font-weight: 400;
}

img { max-width: 100%; height: auto; display: block; }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--accent-deep); }

:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

h1, h2, h3 {
  font-family: var(--ff-display);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.01em;
  margin: 0 0 var(--s4);
  text-wrap: balance;
}

h1 { font-size: clamp(2.4rem, 5.4vw, 4.1rem); }
h2 { font-size: clamp(1.7rem, 3.2vw, 2.5rem); }
h3 { font-size: clamp(1.15rem, 1.8vw, 1.35rem); font-weight: 600; }

p, ul, ol { margin: 0 0 var(--s4); max-width: var(--measure); }
ul, ol { padding-left: 1.25em; }
li { margin-bottom: var(--s2); }
strong { font-weight: 600; }

.container {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--s4);
}

/* Visually hidden without a negative offset: left:-9999px widens the document, which shows up
   as real horizontal overflow once a clip is lifted. */
.skip-link {
  position: absolute; top: 0; left: 0; z-index: 100;
  width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
  background: var(--ink); color: #fff; padding: var(--s3) var(--s4);
}
.skip-link:focus {
  width: auto; height: auto; min-height: 44px; clip-path: none; overflow: visible;
  display: inline-flex; align-items: center;
}

/* --- header ------------------------------------------------------------- */

.site-header {
  border-bottom: 1px solid var(--line);
  background: var(--ground);
  position: sticky; top: 0; z-index: 40;
  /* The only shadow outside the trust seals, and it is doing a different job: it separates the
     sticky chrome from content passing underneath it. Kept below the seals' weight on purpose. */
  box-shadow: 0 6px 18px -14px rgba(14, 12, 11, 0.45);
}
/* ONE ROW: identity, links and the call action together, which is what DESIGN-DNA has asked for
   since the 2026-08-17 revision — "the link row pins with the identity and the call action". The
   implementation had them in two stacked bands, 132px at 1280 against 78px for the row itself,
   and left 610px of empty centre between the brand and the button — 48% of the viewport doing
   nothing while the nav needed exactly that space. The nav now occupies it.
   (The comment previously here described the nav as a sibling bar that scrolls away. It has been
   inside the sticky header since that revision, so the note was two designs out of date.) */
.site-header__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3) var(--s4); min-height: 72px; flex-wrap: wrap;
}
/* The lockup: a designed mark plus the name typeset in the site's own display face, so the
   header uses two faces rather than three. */
.brand { display: flex; align-items: center; gap: var(--s3); text-decoration: none; color: var(--ink); }
.brand__mark { width: 40px; height: 40px; flex: 0 0 auto; }
/* STACKED, and that is the whole fix. Set beside the wordmark under a 12ch cap, the descriptor
   wrapped to three lines and measured 41px against the 24px name it is subordinate to — the
   hierarchy read inverted, the taller element being the quieter one. Under the name on one line
   it supports the wordmark instead of competing with it, and the divider rule is no longer
   needed to separate two things that are no longer side by side. */
.brand__lockup { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; }
/* A BUSINESS NAME IS ONE TOKEN AND NEVER BREAKS. Without this it did: at 320 the phone band's
   nowrap row forced the identity to shrink, min-width:0 let it shrink past its own text, and the
   inherited overflow-wrap safeguard then broke the wordmark into "St / on / ev / a" — four lines
   of two letters, an 83px tall column 25px wide. The safeguard exists to stop a long unbroken
   string pushing the document sideways, which is right for a URL in body copy and catastrophic
   for the company's name in the header. Scoped here so the safeguard keeps working everywhere
   else, which is the point of it. */
.brand__word {
  font-family: var(--ff-display); font-weight: 700; font-size: 1.5rem; line-height: 1;
  letter-spacing: -0.01em; color: var(--ink);
  white-space: nowrap;
}
.brand__sub {
  font-family: var(--ff-body); font-weight: 500; font-size: 0.66rem; color: var(--ink-muted);
  letter-spacing: 0.1em; text-transform: uppercase; line-height: 1;
  white-space: nowrap;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* A native <details> was the obvious compact-nav pattern, but provenance.mjs reads every
   <details>/<summary> in the built page as a published FAQ question, so a "Menu" summary
   registered as an unsourced question. The nav stays visible instead. */

/* The nav lives INSIDE the sticky header, so the links stay on screen while the page scrolls.
   At 1220 and above it is a flex child of the header row and takes the centre; below that it
   lifts out of the row and hangs under the header as a panel (see the 1219px block). */
.site-nav { background: var(--ground); }
.site-nav ul { display: flex; flex-wrap: wrap; gap: 0 var(--s4); list-style: none; margin: 0; padding: 0; max-width: none; }

/* Above the breakpoint the nav is the middle column of the header row. Its own .container is
   neutralised here rather than in the base rule, so the mobile panel below keeps the page
   gutter it needs. */
@media (min-width: 1220px) {
  /* nowrap on BOTH the row and the list, and they depend on each other. A wrapping flex row
     wraps before it shrinks, so with the nav claiming its content width the call action was
     pushed onto a second line and the header measured 114px instead of the row's own 72. */
  .site-header__inner { flex-wrap: nowrap; }
  /* AND THE ROW'S OWN TWO GAPS, which the nav gap above cannot reach. Tightening the list bought
     most of the shortfall and left this header needing 1133px in the 1132px it has at 1280 — one
     pixel, which is not a rounding curiosity but a row with nothing left to give. The base rule
     sets a 24px column gap between brand, nav and actions; 16px here buys 32px across the two and
     takes the clearance to about 15. Row-level only, at the single-row breakpoint, so every
     narrower layout keeps the roomier spacing it was designed with. */
  .site-header__inner { column-gap: var(--s3); }
  /* Both ends of the row keep their measured width here, where the nav sits between them and
     would otherwise take the space from whichever of the two could give it up. Left shrinkable,
     the identity clipped mid-word and the call button lost its number. */
  .brand { flex: 0 0 auto; }
  .header-actions { flex: 0 0 auto; }
  .site-nav { flex: 1 1 auto; min-width: 0; }
  .site-nav > .container { padding: 0; max-width: none; width: 100%; }
  .site-nav ul { flex-wrap: nowrap; justify-content: center; }
  /* nowrap on the LIST stops the items wrapping and does nothing about the text inside them, so
     the labels broke mid-word — "Abo/ut", "Conta/ct". A nav label is a name, not a paragraph. */
  .site-nav a { white-space: nowrap; }

  /* THE TIGHT SET IS THE DEFAULT, and the roomy one is the exception, because measurement says
     the row is over-full at the common width rather than at the rare one. At 1280 the container
     is 1180 and the three parts wanted 260 + 734 + 199 plus two 24px gaps = 1241: sixty-one over.
     The identity clipped to "GENERAL CONTRACTOR, INW" and Contact disappeared behind the call
     button. Written the other way round first, with the tight rule under a max-width, it never
     fired — the breakpoint was reading the viewport while the shortfall was in the container.

     2026-09-09, 8px rather than --s3's 16: the row went over again when the dedicated DID was
     recorded. render-probe measured it needing 1140px in the 1132px it has at 1280, and a
     nowrap row that does not fit does not overflow — it squeezes a child below its own content
     width, so no other check reports it. The call button carries the number and its digits are
     not the width of the placeholder's. Six gaps at 8 buy 48px. Nothing narrower than this
     breakpoint is touched, and the type is left alone — the gap had the slack, the labels did
     not. */
  .site-nav ul { gap: 0 8px; }
  .site-nav a { font-size: 0.89rem; }

  /* The descriptor costs 110px of the row and is the part that clips, so it is off on the
     single-row layout. It is aria-hidden decoration, it repeats what the H1 says underneath it
     on the home page, and the six nav labels and the call action are load-bearing where it is
     not. Shaving type further to keep it would have made the whole nav smaller to protect an
     ornament. It still shows below the breakpoint, where the header is two rows and has room.
     THERE IS NO WIDER CASE. A block at 1400px restored the roomier list and this descriptor on
     the theory that a big screen has spare room — it does not. `--container` caps at 1180, so
     every viewport at or above 1228 gets exactly the same 1180px to divide, and the extra demand
     simply overflowed: clearance measured -33px at 1440 against +18 at 1220. The tight set is
     not a compromise for small desktops, it is the only set. */
  .brand__sub { display: none; }
}

/* --- the menu button ------------------------------------------------------
   Hidden on desktop, where the nav is a permanent row. Shown below the nav
   breakpoint, where it collapses the same list into a panel. */
.nav-toggle { display: none; }
.nav-toggle__bars, .nav-toggle__bars::before, .nav-toggle__bars::after {
  display: block; width: 18px; height: 2px; background: currentColor; border-radius: 1px;
  transition: transform 160ms ease, opacity 160ms ease;
}
.nav-toggle__bars { position: relative; }
.nav-toggle__bars::before, .nav-toggle__bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle__bars::before { top: -6px; }
.nav-toggle__bars::after { top: 6px; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::before { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::after { transform: translateY(-6px) rotate(-45deg); }
.site-nav a { display: inline-flex; align-items: center; min-height: 44px; color: var(--ink); text-decoration: none; font-size: 0.95rem; font-weight: 500; border-bottom: 2px solid transparent; }
.site-nav a:hover, .site-nav a[aria-current="page"] { color: var(--accent); border-bottom-color: var(--accent); }

.call-button {
  display: inline-flex; align-items: center; gap: var(--s2);
  background: var(--accent); color: #fff; text-decoration: none;
  font-weight: 600; font-size: 1rem; padding: 13px var(--s4);
  border-radius: var(--radius); white-space: nowrap; max-width: 100%;
  transition: background 160ms ease;
}
/* A phone number may never break across lines, whatever else in the label wraps. */
.nowrap { white-space: nowrap; }

/* A long service-specific CTA must wrap rather than push the page sideways. */
@media (max-width: 760px) {
  .call-button { white-space: normal; text-align: center; }
  .hero__actions { flex-direction: column; align-items: stretch; }
  .hero__actions .call-button { justify-content: center; }
}
.call-button:hover { background: var(--accent-deep); color: #fff; }
.call-button--ghost { background: transparent; color: var(--accent); border: 1px solid var(--line-strong); }
.call-button--ghost:hover { background: var(--accent); color: #fff; border-color: var(--accent); }

/* Shrinkable by default. Pinned at 0 0 auto for every width it cost 8px of horizontal overflow
   at 320 — the probe catches that as an invariant, not a suspicion, and it is the right call: a
   page that scrolls sideways on the narrowest phone is broken, not merely tight. The pin is only
   needed where the nav shares the row and would otherwise squeeze the button, so it lives in the
   1220 block instead. */
.header-actions { display: flex; align-items: center; gap: var(--s4); min-width: 0; }

/* In the header the call action now sits beside six nav links rather than alone at the end of an
   empty row, so it is sized to that row rather than to a hero. At 1rem on 13px padding it was the
   loudest element on the page and competed with the H1 and with the lead form's own submit — the
   two things a reader is actually meant to act on. Same accent, same first-in-order prominence,
   less shouting. */
.site-header .call-button { font-size: 0.94rem; padding: 10px var(--s4); }

/* --- hero: typographic, no image behind the words ----------------------- */

.hero { padding: var(--s7) 0 var(--s6); border-bottom: 1px solid var(--line); }
.hero__eyebrow {
  font-family: var(--ff-body); font-size: 0.82rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent);
  margin: 0 0 var(--s4);
}
.hero__lede { font-size: 1.24rem; color: var(--ink-muted); max-width: 60ch; margin-bottom: var(--s5); }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: center; }
.hero__note { font-size: 0.95rem; color: var(--ink-muted); margin: var(--s4) 0 0; }

.hero__figure { margin: var(--s6) 0 0; }
.hero__figure img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--radius); }

/* --- numbered bands: the signature device ------------------------------- */

.band { border-bottom: 1px solid var(--line); padding: var(--s7) 0; }
.band:last-of-type { border-bottom: 0; }

.band__grid { display: grid; grid-template-columns: 92px minmax(0, 1fr); gap: var(--s5); align-items: start; }
.band__num {
  font-family: var(--ff-display); font-weight: 700; font-size: 1.5rem;
  color: var(--accent); line-height: 1; padding-top: 10px;
  border-top: 2px solid var(--accent); letter-spacing: 0.02em;
}
.band__body > :last-child { margin-bottom: 0; }

/* --- service index: a list, deliberately not a card grid ---------------- */

.index { list-style: none; margin: 0; padding: 0; max-width: none; border-top: 1px solid var(--line); }
.index li { margin: 0; border-bottom: 1px solid var(--line); }
.index a {
  display: grid; grid-template-columns: 64px minmax(0, 1fr) 28px;
  gap: var(--s4); align-items: baseline;
  padding: var(--s4) 0; text-decoration: none; color: var(--ink);
  transition: color 160ms ease;
}
.index a:hover { color: var(--accent); }
.index__num { font-family: var(--ff-display); font-weight: 700; color: var(--accent); font-size: 1.05rem; }
.index__title { font-family: var(--ff-display); font-weight: 700; font-size: clamp(1.3rem, 2.4vw, 1.8rem); line-height: 1.15; }
.index__desc { display: block; font-family: var(--ff-body); font-weight: 400; font-size: 1rem; color: var(--ink-muted); line-height: 1.55; margin-top: var(--s2); max-width: 56ch; }
.index__arrow { justify-self: end; color: var(--accent); font-size: 1.1rem; }

/* --- contained figure inside a band ------------------------------------- */

.figure-contained { margin: var(--s5) 0 0; max-width: 760px; }
.figure-contained img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--radius); }

/* --- process ladder ------------------------------------------------------ */

/* The sequence is the point, so it is drawn as one continuous rail with the steps hung off it,
   rather than as five paragraphs that happen to be numbered. The node is a real element now: a
   ::before counter could not be drawn over the rail, and the rail could not stop at the last node. */
.ladder { list-style: none; margin: 0; padding: 0; max-width: none; position: relative; }
.ladder__step {
  position: relative; margin: 0;
  padding: 0 0 var(--s5) calc(var(--s6) + var(--s2));
}
/* The rail is the step's own left border so it terminates naturally at the final node instead of
   running past it into whitespace. */
.ladder__step::before {
  content: ""; position: absolute; left: 17px; top: 8px; bottom: 0;
  width: 1px; background: linear-gradient(var(--line-strong), var(--line));
}
.ladder__step:last-child::before { display: none; }
.ladder__node {
  position: absolute; left: 0; top: 0;
  width: 35px; height: 35px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--line-strong); color: var(--ink-muted);
  font-family: var(--ff-display); font-weight: 700; font-size: 0.9rem;
  display: grid; place-items: center;
  transition: border-color 200ms ease, color 200ms ease, background 200ms ease, box-shadow 200ms ease;
}
/* The step a reader has reached is the one lit. Hovering anywhere on the row lights its node, so
   the rail responds to reading rather than only to precise pointing. */
.ladder__step:hover .ladder__node {
  border-color: var(--accent); color: var(--accent); background: var(--ground);
  box-shadow: 0 0 0 4px rgba(154, 59, 37, 0.09);
}
.ladder__text > :last-child { margin-bottom: 0; }
.ladder h3 { margin-bottom: var(--s2); }
.ladder p { margin-bottom: 0; }

@media (max-width: 560px) {
  .ladder__step { padding-left: var(--s5); }
  .ladder__step::before { left: 14px; }
  .ladder__node { width: 29px; height: 29px; font-size: 0.8rem; }
}

/* --- FAQ -----------------------------------------------------------------
   Bounded and centred, which is the contract production-integrity enforces: an unbounded
   question list stretched across a wide viewport is unreadable.
   NOTE for whoever edits this heading: the checker collects this rule with a regex whose
   selector capture reaches back to the previous closing brace, so it swallows this comment
   too, and it drops any match whose text mentions the disclosure element by name. A comment
   naming those elements here therefore makes a compliant max-width INVISIBLE to the check and
   fails the build. That is what the previous heading did. Recorded as a Factory defect. */

.faq { border-top: 1px solid var(--line); max-width: 78ch; margin-inline: auto; }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  cursor: pointer; list-style: none; padding: var(--s4) var(--s5) var(--s4) 0;
  position: relative;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary h3 { margin: 0; font-family: var(--ff-display); font-weight: 700; font-size: 1.16rem; line-height: 1.3; }
.faq summary::after {
  content: "+"; position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  font-family: var(--ff-display); font-size: 1.6rem; color: var(--accent); line-height: 1;
}
.faq details[open] summary::after { content: "\2013"; }
.faq details > *:not(summary) { margin-bottom: var(--s4); }
.faq details p { color: var(--ink-muted); }

/* --- call band ----------------------------------------------------------- */

.call-band { background: var(--ink); color: #fff; padding: var(--s6) 0; }
.call-band h2 { color: #fff; }
.call-band p { color: rgba(255, 255, 255, 0.78); }
.call-band .call-button { background: var(--accent); }
.call-band .call-button:hover { background: #b0472e; }
/* The oxide focus ring measured 2.62:1 against the dark ground, under the 3:1 minimum
   (finding B-12). Inside this band the ring is light. */
.call-band :focus-visible { outline-color: #f7f5f1; }
.call-band__phone { font-family: var(--ff-display); font-weight: 700; font-size: clamp(1.8rem, 4vw, 2.6rem); color: #fff; text-decoration: none; display: inline-block; margin-bottom: var(--s3); }
.call-band__phone:hover { color: #fff; text-decoration: underline; }

/* --- breadcrumbs --------------------------------------------------------- */

.breadcrumbs { padding-top: var(--s4); font-size: 0.9rem; color: var(--ink-muted); }
.breadcrumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: var(--s2); margin: 0; padding: 0; max-width: none; }
.breadcrumbs li + li::before { content: "/"; margin-right: var(--s2); color: var(--line-strong); }

/* --- footer -------------------------------------------------------------- */

.site-footer { border-top: 1px solid var(--line); padding: var(--s6) 0 var(--s5); }
.site-footer__grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: var(--s5); }
.site-footer h2 { font-size: 0.82rem; font-family: var(--ff-body); font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-muted); margin-bottom: var(--s3); }
.site-footer ul { list-style: none; padding: 0; margin: 0; }
.site-footer li { margin-bottom: var(--s2); }
.site-footer a { color: var(--ink); text-decoration: none; }
.site-footer a:hover { color: var(--accent); text-decoration: underline; }
.site-footer address { font-style: normal; color: var(--ink-muted); }
.site-footer__legal { border-top: 1px solid var(--line); margin-top: var(--s5); padding-top: var(--s4); font-size: 0.88rem; color: var(--ink-muted); display: flex; flex-wrap: wrap; gap: var(--s4); justify-content: space-between; }

/* --- banner media: the main image beside two trade details --------------- */

.hero__media { margin: var(--s6) 0 0; }
.hero__media--strip { display: grid; grid-template-columns: 2fr 1fr; gap: var(--s3); align-items: stretch; }
.hero__media--strip .hero__figure { margin: 0; }
.hero__media--strip .hero__figure img { height: 100%; aspect-ratio: auto; min-height: 320px; }
.hero__media--strip .hero__detail { margin: 0; display: grid; }
.hero__detail img { width: 100%; height: 100%; object-fit: cover; border-radius: var(--radius); }
.hero__media--strip { grid-template-areas: "main detail1" "main detail2"; }
.hero__media--strip .hero__figure { grid-area: main; }
.hero__media--strip .hero__detail:nth-of-type(1) { grid-area: detail1; }
.hero__media--strip .hero__detail:nth-of-type(2) { grid-area: detail2; }

/* --- footer map: the verified premises, never an approximate pin --------- */

.footer-map {
  margin-top: var(--s5); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; line-height: 0; background: var(--surface);
}
.footer-map iframe { width: 100%; height: 260px; border: 0; display: block; filter: saturate(0.85); }

/* --- footer identity and location module -------------------------------- */

.footer-identity { display: flex; gap: var(--s4); align-items: flex-start; }
.footer-identity__mark { width: 44px; height: 44px; flex: 0 0 auto; }
.footer-identity__name {
  font-family: var(--ff-display); font-size: 1.15rem; font-weight: 700; color: var(--ink);
  text-transform: none; letter-spacing: 0; margin-bottom: var(--s2);
}
.footer-identity address { margin-bottom: var(--s3); line-height: 1.6; }
.footer-identity__area { color: var(--ink-muted); font-size: 0.92rem; }
.footer-identity__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); margin: 0; }
.footer-identity__map { font-size: 0.92rem; }

/* --- an address is always an address block, never a run-on paragraph ----- */

.address-block { font-style: normal; line-height: 1.7; margin: 0 0 var(--s4); }

/* --- 404 recovery routes are controls, not prose ------------------------- */

/* The 404 recovery routes are controls, not prose (finding B-13). */
.page-not-found .band__body ul a { display: inline-flex; align-items: center; min-height: 44px; }

/* --- inline call prompt --------------------------------------------------
   The page is long, and a reader who decides in the middle of it should not have to scroll
   to either end to act. One compact prompt sits between bands. */

.call-inline { border-bottom: 1px solid var(--line); padding: var(--s4) 0; background: var(--surface); }
.call-inline .container { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3) var(--s4); }
.call-inline p { margin: 0; font-size: 1.02rem; color: var(--ink-muted); max-width: 46ch; }

/* --- prose helpers ------------------------------------------------------- */

.prose > :last-child { margin-bottom: 0; }
.lede { font-size: 1.18rem; color: var(--ink-muted); max-width: 62ch; }

/* --- responsive ---------------------------------------------------------- */

@media (max-width: 900px) {
  .site-footer__grid { grid-template-columns: 1fr 1fr; }
  .band__grid { grid-template-columns: 1fr; gap: var(--s3); }
  .band__num { border-top: 0; padding-top: 0; font-size: 1.1rem; }
}

/* Tap targets. Prose links stay inline so paragraphs are not shredded, but every navigation,
   footer, breadcrumb and brand control carries a 44px hit area at every width. */
.site-nav a, .site-footer a, .breadcrumbs a, .brand, .skip-link {
  min-height: 44px;
}
.site-nav a, .site-footer a, .breadcrumbs a, .brand {
  display: inline-flex; align-items: center;
}
.site-footer li { margin-bottom: 0; }

/* 1219, not 900. The single row needs about 1220px of viewport to hold the identity, six labels
   and the call action without collision. Measured, not estimated: at a 1180 container the last
   link clears the button by 18px and at a 1092 container it overlaps it by 10, so clearance moves
   about 0.32px per px of container and needs roughly 1160 of container to stay positive. The
   container caps at 1180, so anything at or above 1228 viewport is identical. At 940 the old 900px breakpoint left the
   desktop rules in force with nowhere to put them, and the last nav link crossed 110px into the
   call button while "Apartment renovation" printed over the wordmark. The two-band layout fitted
   at 900 because it never asked one line to hold all three. */
@media (max-width: 1219px) {
  /* The permanent nav row becomes a button-driven panel. It is still the same list in the same
     place in the DOM — only its presentation changes — and with the script blocked the `js`
     class is absent, so the list simply stays visible and nothing is lost. */
  .nav-toggle {
    display: inline-flex; align-items: center; gap: var(--s2);
    min-height: 44px; padding: 8px var(--s3);
    background: transparent; color: var(--ink);
    border: 1px solid var(--line-strong); border-radius: var(--radius);
    font-family: var(--ff-body); font-size: 0.92rem; font-weight: 600; cursor: pointer;
  }
  .nav-toggle:hover { border-color: var(--accent); color: var(--accent); }

  /* The list is now a child of the header ROW, so below the breakpoint it lifts out of that row
     and hangs under the header as a panel. Absolute rather than in flow, so opening the menu
     overlays the page instead of pushing the hero down by the height of six links. */
  .site-nav {
    position: absolute; left: 0; right: 0; top: 100%;
    background: var(--ground); z-index: 39;
    box-shadow: 0 10px 20px -16px rgba(14, 12, 11, 0.5);
  }
  .site-nav.is-open { border-bottom: 1px solid var(--line); }

  .js .site-nav {
    display: grid; grid-template-rows: 0fr;
    transition: grid-template-rows 180ms ease;
    border-top: 0;
  }
  .js .site-nav > .container { overflow: hidden; min-height: 0; }
  .js .site-nav.is-open { grid-template-rows: 1fr; border-top: 1px solid var(--line); }

  .site-nav ul { flex-direction: column; gap: 0; padding: var(--s2) 0; }
  .site-nav li { margin: 0; }
  .site-nav a {
    width: 100%; min-height: 50px; font-size: 1rem;
    border-bottom: 0; border-top: 1px solid var(--line);
  }
  .site-nav li:first-child a { border-top: 0; }
  .site-nav a:hover, .site-nav a[aria-current="page"] { border-bottom: 0; color: var(--accent); }
}

@media (prefers-reduced-motion: reduce) {
  .js .site-nav { transition: none; }
}

@media (max-width: 780px) {
  .hero__media--strip { grid-template-columns: 1fr 1fr; grid-template-areas: "main main" "detail1 detail2"; }
  .hero__media--strip .hero__figure img { min-height: 0; aspect-ratio: 16 / 9; }
  .hero__detail img { aspect-ratio: 1 / 1; }
}

@media (max-width: 760px) {
  body { font-size: 17px; }


  .site-nav ul { gap: 0 var(--s3); }
  .site-nav a { font-size: 0.9rem; min-height: 44px; }
  .brand__mark { width: 34px; height: 34px; }
  .brand__word { font-size: 1.3rem; }
  .hero { padding: var(--s5) 0 var(--s5); }
  .band { padding: var(--s5) 0; }
  .index a { grid-template-columns: 40px minmax(0, 1fr); }
  .index__arrow { display: none; }
  .ladder li { padding-left: var(--s5); }
  .site-footer__grid { grid-template-columns: 1fr; gap: var(--s4); }
  .header-actions .call-button { padding: 11px var(--s3); font-size: 0.94rem; }
  .footer-identity__actions .call-button { width: 100%; justify-content: center; }
}

/* PHONE WIDTHS: ONE ROW, and a pair of controls that look like a pair.
   The desktop header was rebuilt into a single row and this band was left as it was, which meant
   the phone kept the disease the desktop had just been cured of. Measured at 390: the identity
   was 120px in a 390px viewport — 246px, sixty-three per cent of the row, empty — while the Menu
   button and the call action wrapped onto a second line below it and made the header 97px tall.
   They also read as two unrelated controls rather than one pair: a thin outlined ghost beside a
   solid slab that was 180px wide, forty-six per cent of the screen, and the loudest thing above
   the fold — competing with the H1 and with the hero form's own submit.

   Three moves buy the row. The Menu button becomes a 44px square carrying only its glyph; its
   word is not deleted but hidden, so the accessible name is still "Menu" and DESIGN-DNA's
   labelled button survives for everyone who reads the page rather than looks at it. The call
   action sheds the word "Call" the same way — the number in an accent button on a phone is
   unambiguous, and the verb is still spoken. And the row is told not to wrap.
   120 + 44 + 130 + gaps fits 375 with room, and 320 too.

   THE BAND RUNS TO 700, not to 560. Set at 560 first, and 561 measured 108px: one pixel above the
   breakpoint the full "Menu" and "Call" labels return AND the descriptor reappears at 204px wide,
   and the three together no longer fit 513px of container, so the row wrapped and the taller brand
   made the wrap worse than the one it replaced. The full-label header needs about 584px of
   container, which arrives around 640px of viewport. 700 clears it with margin. */
@media (max-width: 700px) {
  .brand__sub { display: none; }
  /* The identity is NOT shrinkable and the row is NOT forced onto one line. Both of those were
     wrong together: nowrap made the brand give up width it did not have, min-width:0 let it go
     below its own text, and the wordmark shattered. Let the row wrap when it genuinely cannot
     fit — at 320 it cannot, and two clean rows are the correct answer there — and it will not
     need to anywhere else, because the compacted controls below buy the space. */
  .brand { gap: var(--s2); flex: 0 0 auto; }
  .site-header__inner { gap: var(--s2); min-height: 62px; }
  .header-actions { gap: var(--s2); flex: 0 0 auto; }

  /* A square icon button, sized and cornered like the action beside it so the two read as one
     pair. The label goes to the accessible layer rather than away. */
  .nav-toggle { width: 44px; min-width: 44px; padding: 0; justify-content: center; }
  .nav-toggle__label {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }

  .site-header .call-button { padding: 10px var(--s3); font-size: 0.92rem; }
  .site-header .call-button__verb {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ===========================================================================
   REFINE PASS — 2026-09-06
   Added after the operator reported the site read as "boring and dead": it
   shipped with no motion of any kind, no services showcase, and 14
   commissioned images of which the homepage rendered three.
   Nothing below changes a word of copy, a route, a claim or a business fact.
   =========================================================================== */

/* --- services showcase: the animated-carousel-v1 contract ----------------- */
/* Mechanics, autoplay and every pause guarantee are SiteKit's. This file only
   decides how it looks and how fast it drifts. */

.showcase { margin: 0 0 var(--s2); }

.showcase__head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s4); margin-bottom: var(--s5);
}
.showcase__lede {
  margin: 0; max-width: 46ch; color: var(--ink-muted); font-size: 1.05rem;
}
.showcase__controls { display: flex; gap: var(--s2); flex: none; }
.showcase__btn {
  width: 46px; height: 46px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--line-strong); color: var(--ink);
  font-size: 1.05rem; line-height: 1; cursor: pointer;
  display: grid; place-items: center;
  transition: background 180ms ease, border-color 180ms ease, color 180ms ease, transform 180ms ease;
}
.showcase__btn:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
.showcase__btn:active { transform: scale(0.94); }

/* The track is a native scroll container; it must stay scrollable with no script. */
.showcase__track {
  list-style: none; margin: 0; padding: 0 0 var(--s3);
  display: flex; gap: var(--s4);
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.showcase__track::-webkit-scrollbar { display: none; }

.showcase__card { flex: 0 0 336px; margin: 0; }
.showcase__link {
  display: flex; flex-direction: column; height: 100%;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
  color: inherit; text-decoration: none;
  transition: border-color 240ms ease, box-shadow 240ms ease, transform 240ms ease;
}
.showcase__link:hover {
  border-color: var(--line-strong); color: inherit;
  transform: translateY(-3px);
  box-shadow: 0 18px 40px -26px rgba(23, 21, 19, 0.55);
}
.showcase__media { position: relative; display: block; overflow: hidden; }
.showcase__media img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  transition: transform 700ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
.showcase__link:hover .showcase__media img { transform: scale(1.04); }
.showcase__num {
  position: absolute; left: 0; bottom: 0;
  background: var(--ground); color: var(--accent);
  font-family: var(--ff-display); font-weight: 700; font-size: 0.9rem;
  padding: var(--s2) var(--s3); letter-spacing: 0.06em;
}
.showcase__body { display: flex; flex-direction: column; gap: var(--s2); padding: var(--s4); flex: 1; }
.showcase__title {
  font-family: var(--ff-display); font-weight: 700;
  font-size: 1.32rem; line-height: 1.15;
}
.showcase__desc { color: var(--ink-muted); font-size: 0.98rem; line-height: 1.55; }
.showcase__cue {
  margin-top: auto; padding-top: var(--s3);
  display: inline-flex; align-items: center; gap: var(--s2);
  color: var(--accent); font-size: 0.95rem; font-weight: 500;
}
.showcase__arrow { transition: transform 240ms ease; }
.showcase__link:hover .showcase__arrow { transform: translateX(5px); }

@media (max-width: 700px) {
  .showcase__head { flex-direction: column; align-items: flex-start; gap: var(--s3); }
  .showcase__card { flex-basis: 82vw; }
}

/* --- motion: one vocabulary, used three ways ----------------------------- */
/*
 * A rise-and-settle over 520ms on a single easing curve. It is applied to
 * section bodies, to the ladder steps in sequence, and to the hero figure.
 *
 * NOTHING IS HIDDEN UNTIL A SCRIPT HAS CONFIRMED IT CAN UNHIDE IT. The hidden
 * state is scoped to .js, which the inline head script sets. A reader with no
 * JavaScript, and a reader whose observer never fires, both get the finished
 * page rather than a blank one. SiteKit's safeguards.css independently forces
 * [data-reveal] visible under prefers-reduced-motion.
 */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 520ms cubic-bezier(0.2, 0.7, 0.3, 1),
              transform 520ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
.js [data-reveal].is-revealed { opacity: 1; transform: none; }

/* The ladder reveals as a sequence, because the content IS a sequence. The
   stagger is capped so a five-step list never keeps a reader waiting. */
.js .ladder__step[data-reveal] { transition-delay: calc(var(--step, 0) * 70ms); }

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] { opacity: 1 !important; transform: none !important; transition: none !important; }
  .showcase__media img, .showcase__link, .showcase__arrow, .ladder__node { transition: none !important; }
}

/* --- banner: the one full-bleed moment on the page ----------------------- */
/* The call band was a flat dark rectangle. It is the last thing before the
   footer and the only place the phone number is set large, so it now carries
   the same commissioned photography the rest of the site uses, dimmed behind
   the type rather than competing with it. */
.call-band { position: relative; overflow: hidden; }
.call-band::before {
  content: ""; position: absolute; inset: 0;
  background-image: url("/assets/images/detail-trim.f55740ad11.webp");
  background-size: cover; background-position: center 62%;
  opacity: 0.16; filter: grayscale(0.3);
}
.call-band > * { position: relative; }

/* --- interaction states -------------------------------------------------- */
.call-button {
  transition: background 180ms ease, color 180ms ease, border-color 180ms ease,
              transform 140ms ease, box-shadow 180ms ease;
}
.call-button:hover { box-shadow: 0 12px 26px -16px rgba(110, 39, 24, 0.75); }
.call-button:active { transform: translateY(1px); }

.index a { transition: background 180ms ease, padding-left 220ms cubic-bezier(0.2, 0.7, 0.3, 1); }
.index a:hover { background: rgba(255, 255, 255, 0.55); }
.index__arrow { transition: transform 220ms ease; }
.index a:hover .index__arrow { transform: translateX(5px); }

.site-header a, .site-footer a { transition: color 160ms ease; }

/* --- footer -------------------------------------------------------------- */
/* It carried the address, two link columns and a map frame with no separation
   between them, and the map iframe rendered as a large empty box whenever the
   frame was blocked. The frame now sits on a surface with a border, so a
   blocked or slow map reads as a panel rather than as a hole in the page. */
.site-footer__grid { padding-bottom: var(--s5); border-bottom: 1px solid var(--line); }
.footer-map {
  margin-top: var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden; line-height: 0;
}
.footer-map iframe { width: 100%; display: block; border: 0; }
.footer-identity__mark { transition: opacity 200ms ease; }

/* A grid or flex item defaults to min-width:auto, so a horizontally scrolling track of
   fixed-width cards pushes its ancestors wider instead of scrolling inside them. That is what
   render-probe measures as horizontal overflow "with the clip lifted" — the clip is lifted
   precisely so a scroll container cannot mask a layout that is genuinely too wide. Allowing
   these three to shrink is what lets the carousel scroll rather than widen the page. */
.band__body { min-width: 0; }
.showcase { min-width: 0; }
.showcase__track { min-width: 0; }

/* The map frame is third-party and loads late or not at all — blocked by a privacy extension, an
   offline review, or a headless capture. Loaded through SiteKit's iframe[data-src] observer it is
   deferred until it is nearly in view, and until then the panel must read as a panel rather than
   as a hole in the footer, which is exactly what it looked like before: a large empty white box
   as the last thing on the page. */
.footer-map { position: relative; min-height: 260px; background: var(--surface); }
.footer-map::after {
  content: "Map"; position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--ff-display); font-size: 0.85rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-muted); opacity: 0.5; pointer-events: none;
}
.footer-map iframe { position: relative; z-index: 1; min-height: 260px; background: transparent; }

/* A 142x23 hit area is not a comfortable target. The link keeps its inline look and gains a real
   44px box around it. */
.footer-identity__map {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: var(--s1) 0;
}

/* An inline tel: link inside a sentence is a real call action and was a 142x23 hit area. The
   padding grows the box to a comfortable target and the matching negative margin keeps the line
   box, and therefore the paragraph's rhythm, exactly where it was. */
.prose a[href^="tel:"] { padding: 12px 3px; margin: -12px 0; }

/* An iframe with no src yet is about:blank, and a browser paints that WHITE whatever background
   the element is given — which is why the lazy map still read as a bright hole punched in a dark
   footer. Hidden until SiteKit gives it a src, so what a reader sees before it loads is the
   panel, and after it loads is the map. */
.footer-map iframe:not([src]) { visibility: hidden; }

/* ===========================================================================
   HERO: image, form, CTA, badges — 2026-09-07
   The homepage hero now carries the canonical lead form, which is a contract
   rather than a design choice: the one surface every visitor sees must capture
   an enquiry without navigating first.
   =========================================================================== */

/* Two columns from 980px, where the form can sit beside the headline without
   squeezing either. Below that the copy leads and the form follows it, because
   on a phone the headline is what tells a reader they are in the right place. */
.hero--home .hero__grid { display: grid; gap: var(--s5); align-items: start; }
@media (min-width: 980px) {
  .hero--home .hero__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 430px); gap: var(--s6); }
  .hero--home .hero__copy { padding-top: var(--s2); }
}
.hero--home .hero__lede { margin-bottom: var(--s4); }

/* --- badges --------------------------------------------------------------- */
/* Every badge traces to a recorded fact. No number, no certificate, no
   "ask us for proof" — the treatment is the whole of the claim. */
.badges {
  list-style: none; margin: 0 0 var(--s5); padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3);
}
/* --- trust seals -----------------------------------------------------------
   ROUND, and that is a narrow, deliberate exception. DESIGN-DNA.md's 2px radius —
   "nearly square, paper and rules, not pills" — governs buttons, cards, inputs and
   surfaces, and still does. A badge is a different object class: a stamped mark of
   standing, and a mark of standing is a seal. Operator decision, 2026-09-07.

   The seal is inlined SVG so it inherits the page: `currentColor` carries the ring
   and the words, the two custom properties carry the accent and the hairline, and
   the type is the site's own display face. One asset works on the dark hero and on
   the light ground by changing three values.
   --------------------------------------------------------------------------- */
.badges {
  list-style: none; margin: 0 0 var(--s5); padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--s4);
}
.badge { margin: 0; display: block; }

/* A struck object, so the colours are the seal's own rather than inherited from the page: a solid
   oxide face, a darker edge showing as a rim, and everything cut out of it in the ground colour.
   One asset therefore reads the same on the dark hero and on the cream ground. The drop shadow is
   what lifts it off the photograph — it is the only shadow in the design and it belongs to this
   object class alone. */
.seal {
  display: block;
  width: 132px; height: 132px;
  --seal-face: var(--accent);
  --seal-edge: var(--accent-deep);
  --seal-ink: var(--ground);
  filter: drop-shadow(0 6px 14px rgba(14, 12, 11, 0.45));
}

/* Cut out of the face in the ground colour, like every other mark on the seal — NOT currentColor,
   which here would inherit the page's ink and put dark words on a dark red disc.
   9.8 user units: from a 100 viewBox that renders at 12.9px on desktop (132px) and 11.8px on a
   phone (120px), both clear of render-probe's 11px legibility floor. The seal SIZE is what makes
   the type legible, not the unit count. */
.seal__word {
  font-family: var(--ff-display);
  font-weight: 700;
  font-size: 9.8px;
  letter-spacing: 0.03em;
  fill: var(--seal-ink);
}

@media (max-width: 700px) {
  /* They wrap rather than shrink: below about 120px the ring text drops under the legibility
     floor, and a seal nobody can read is decoration. */
  .badges { gap: var(--s3); }
  .seal { width: 120px; height: 120px; }
}

/* --- the hero lead form --------------------------------------------------- */
.lead-form {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s4);
  box-shadow: 0 26px 60px -44px rgba(23, 21, 19, 0.6);
}
@media (min-width: 980px) { .lead-form { padding: var(--s5); } }

.lead-form__title {
  font-family: var(--ff-display); font-weight: 700; line-height: 1.15;
  font-size: 1.34rem; margin: 0 0 var(--s2);
}
.lead-form__sub { margin: 0 0 var(--s4); color: var(--ink-muted); font-size: 0.97rem; }

.lead-form__row { display: grid; gap: var(--s3); }
@media (min-width: 460px) { .lead-form__row { grid-template-columns: 1fr 1fr; } }

.lead-field { margin: 0 0 var(--s3); display: grid; gap: 6px; }
.lead-field label { font-size: 0.86rem; font-weight: 600; letter-spacing: 0.01em; }
.lead-field__optional { font-weight: 400; color: var(--ink-muted); }
.lead-field input, .lead-field textarea {
  font: inherit; font-size: 1rem; color: var(--ink);
  background: var(--ground);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 11px var(--s3); width: 100%;
  min-height: 46px;
  transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
}
.lead-field textarea { min-height: 74px; resize: vertical; line-height: 1.5; }
.lead-field input::placeholder, .lead-field textarea::placeholder { color: var(--ink-muted); opacity: 0.7; }
.lead-field input:hover, .lead-field textarea:hover { border-color: var(--ink-muted); }
.lead-field input:focus, .lead-field textarea:focus {
  outline: none; background: var(--surface);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(154, 59, 37, 0.16);
}
/* The focus ring is REPLACED here, never removed: the box-shadow above is the visible indicator,
   and this keeps the keyboard path obvious for anyone the box-shadow does not reach. */
.lead-field input:focus-visible, .lead-field textarea:focus-visible { outline: 3px solid var(--accent); outline-offset: 1px; }

/* A honeypot must be unreachable by a person and invisible to assistive technology, without
   display:none, which some bots use as the signal to skip it. */
.lead-form__hp {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.lead-form__actions { margin: var(--s4) 0 0; }
.lead-form__actions .call-button { width: 100%; justify-content: center; }
.lead-form__actions .call-button[disabled] { opacity: 0.6; cursor: default; }

.lead-form__status { margin: var(--s3) 0 0; font-size: 0.92rem; min-height: 1.3em; }
.lead-form__status[data-state="error"] { color: var(--accent-deep); font-weight: 500; }
.lead-form__status[data-state="ok"] { color: var(--ink); font-weight: 500; }
.lead-form__status[data-state="busy"] { color: var(--ink-muted); }

.lead-form__noscript { margin: var(--s3) 0 0; font-size: 0.92rem; color: var(--ink-muted); }
.lead-form__consent { margin: var(--s3) 0 0; font-size: 0.83rem; line-height: 1.5; color: var(--ink-muted); }

/* The consent line's privacy link is fine print, and fine print is still a tap target. Padding
   with a matching negative margin grows the hit area to 44px without moving the line. */
.lead-form__consent a, .lead-form__noscript a {
  display: inline-block; padding: 14px 3px; margin: -14px 0;
}

/* SiteKit's safeguards set overflow-wrap:anywhere on a, p, li and friends to close the horizontal
   overflow invariant, and it inherits into this span through the <a> that wraps it. Combined with
   the 12ch box it split the tagline mid-word — "GENERAL CONTRACT / OR, INWOOD" — which reads as a
   broken build rather than as a lockup. The tagline is two short phrases and should break only
   between them, so wrapping is restored to normal here and the box widened to fit the longer line.
   Scoped to this element: the safeguard stays in force everywhere else, which is the point of it. */
.brand__sub {
  overflow-wrap: normal;
  word-break: keep-all;
}
/* The 152px cap that used to be here is GONE, and it had to be. It existed for the lockup that
   sat the descriptor BESIDE the wordmark, where a box was the only thing stopping the phrase
   breaking mid-word. The lockup now stacks and the descriptor is white-space: nowrap, so the cap
   stopped being a guard and became a blade: measured at 800px the text wanted 204px inside a
   152px box and was cut. A width limit written for one layout is a defect in the next one. */

/* On a phone the hero actions stack, and a full-width button whose label wraps was leaving the
   separator dangling at the end of a line with the number pushed to the far edge. Centring the
   content and letting both actions share the full width makes the pair read as one stack. */
@media (max-width: 560px) {
  .hero__actions { display: grid; gap: var(--s3); }
  .hero__actions .call-button { width: 100%; justify-content: center; text-align: center; }
}

/* ===========================================================================
   HOME HERO — full-bleed photograph behind the whole composition, 2026-09-07
   The hero was a grid with copy on one side and a form on the other, and the
   photograph sat under it as a separate strip. The image now runs edge to edge
   BEHIND the content, which is what makes the top of the page read as the
   front of a business rather than as a layout.
   =========================================================================== */

.hero--home {
  position: relative;
  isolation: isolate;
  border-bottom: 0;
  padding: var(--s7) 0 var(--s7);
  /* The ground colour underneath, so the section is never transparent while the
     photograph decodes and never flashes white against the dark treatment. */
  background: #221f1c;
}
.hero--home .container { position: relative; z-index: 2; }

.hero__bg { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.hero__bg img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 46%; }

/* TWO scrims, because one cannot do both jobs. The first is a flat darkening that makes the
   photograph a surface rather than a subject. The second is a directional wash, heavier on the
   side the copy sits, so the headline has contrast where it needs it while the right-hand side
   of the image stays visible behind and around the form. */
.hero--home::before,
.hero--home::after { content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none; }
/* Tuned down from the first attempt, which darkened the photograph almost out of existence — a
   background image nobody can see is not a background image, it is an expensive grey. The flat
   layer now only seats the picture, and the directional wash does the legibility work where the
   headline actually sits. */
.hero--home::before { background: rgba(20, 18, 16, 0.30); }
.hero--home::after {
  background:
    linear-gradient(100deg, rgba(14, 12, 11, 0.86) 0%, rgba(14, 12, 11, 0.72) 30%, rgba(14, 12, 11, 0.34) 60%, rgba(14, 12, 11, 0.12) 82%),
    linear-gradient(to top, rgba(14, 12, 11, 0.42) 0%, rgba(14, 12, 11, 0) 30%);
}

/* --- the copy, inverted onto the photograph ------------------------------- */
.hero--home .hero__eyebrow { color: #e6bfae; }
.hero--home h1 { color: #f7f5f1; }
.hero--home .hero__lede { color: rgba(247, 245, 241, 0.88); max-width: 34ch; text-shadow: 0 1px 14px rgba(14, 12, 11, 0.5); }
.hero--home h1 { text-shadow: 0 2px 22px rgba(14, 12, 11, 0.42); }

/* The ghost action was an ink outline made for the cream ground and disappears on a photograph. */
.hero--home .call-button--ghost {
  color: #f7f5f1;
  border-color: rgba(247, 245, 241, 0.45);
  background: rgba(247, 245, 241, 0.06);
}
.hero--home .call-button--ghost:hover {
  background: var(--ground); color: var(--ink); border-color: var(--ground);
}

/* --- the form card, which stays light on purpose -------------------------- */
/* It is the one element that must not recede: a white card on a darkened photograph is the
   brightest thing on the page, which is where the enquiry should be. */
.hero--home .lead-form {
  box-shadow: 0 34px 70px -40px rgba(0, 0, 0, 0.75);
  border-color: rgba(255, 255, 255, 0.5);
}

@media (min-width: 980px) {
  /* The form is the tallest thing in the grid, so the section takes its height from that rather
     than from padding. Less floor under the copy column, which was reading as dead space. */
  .hero--home { padding: 92px 0 84px; }
}
@media (max-width: 979px) {
  /* On a phone the photograph sits behind a much taller stack, so the darkening is carried
     further up and the object-position favours the top of the frame where the room reads. */
  .hero--home { padding: var(--s6) 0; }
  /* The frame is dominated by a dark wall at its centre; the window and the cased opening sit
     right of it. On a phone the crop is narrow enough that centring shows only the wall, so the
     focal point moves toward the light. */
  .hero__bg img { object-position: 72% 38%; }
  /* Lighter than the first pass, which took the photograph out entirely on a phone — the very
     device where the hero IS the page. The directional wash below carries the legibility. */
  .hero--home::before { background: rgba(20, 18, 16, 0.20); }
  .hero--home::after {
    background:
      linear-gradient(rgba(14, 12, 11, 0.68) 0%, rgba(14, 12, 11, 0.54) 44%, rgba(14, 12, 11, 0.30) 100%);
  }
}

/* --- the detail pair, rehomed --------------------------------------------- */
/* These two images used to sit in the hero as a collage beside the main photograph. They now sit
   in the opening band, next to the sentence that names brick, plaster over masonry and wood
   trim — which is what they actually depict, so they illustrate the claim instead of decorating
   the top of the page. */
.detail-pair {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--s3);
  margin: var(--s5) 0 0; max-width: 620px;
}
.detail-pair__item { margin: 0; }
.detail-pair__item img {
  width: 100%; aspect-ratio: 1 / 1; object-fit: cover;
  border-radius: var(--radius); border: 1px solid var(--line);
}


/* The primary call action carries a label, a separator and a number. On a phone the label wraps
   and the separator was left dangling at the end of a line with the number pushed to the far
   edge. Below 560px the parts stack and the separator is dropped, because a bullet between two
   stacked lines is punctuation for a sentence that is no longer one. */
.cb__label, .cb__num { display: inline-block; }
.cb__sep { margin: 0 0.45em; opacity: 0.75; }
@media (max-width: 560px) {
  .hero__actions .call-button { flex-direction: column; gap: 2px; line-height: 1.25; }
  .cb__sep { display: none; }
}


/* THE SAME FAILURE AS THE 1220px ROW ABOVE, AT THE OTHER END OF THE SCALE.
   That comment describes the call action being pushed onto a second line and the header measuring
   114px instead of the row's own 72. Below 375px the identical thing happens for the identical
   reason: brand 120 + actions 199 + the container's 24px of padding on each side needs 367px, and
   a 360px phone -- the most common Android width there is -- has 360. The row wraps and the header
   goes from 63px to 97px.

   Measured on the live site, 2026-09-09 at 360px. Before: two rows, 97px. After: one row, 63px,
   with 14px of slack left over and the 44px tap target intact.

   THE NUMBER STAYS, and two earlier attempts at this are why that is written down. Hiding it and
   keeping the word "Call" produced an EMPTY button, because .call-button__verb is already
   display:none at this width; restoring the verb collapsed the tap target to 20px. Both were
   solving a 7px layout shortfall by removing the one thing a local business has the header for.
   The pixels come out of padding instead. */
@media (max-width: 399px) {
  .site-header .container { padding-left: 14px; padding-right: 14px; }
  .call-button { padding-left: 12px; padding-right: 12px; }
  .site-header__inner { column-gap: 8px; }
}

/* --- footer contract (ae757c86): hours, and this site in "Our locations" --------
   The hours sit with the address as one more quiet identity line. The current site is listed
   with its siblings and marked by aria-current, set in ink behind a small accent square,
   and never linked to itself: it keeps the 44px row height the links carry so the
   list does not jump where the marked row sits. */
.footer-identity__hours { margin: 0 0 var(--s3); color: var(--ink-muted); font-size: 0.92rem; }
.footer-here { display: inline-flex; align-items: center; gap: var(--s2); min-height: 44px; color: var(--ink); font-weight: 600; }
.footer-here::before { content: ""; width: 7px; height: 7px; background: var(--accent); flex: none; }
