/* =====================================================================
   Paraone Residential: one-page site.

   The design language is GOLD ON BLACK, LIKE HIS SIGNS: the signwriter's
   site sign and business card (Halvo Signs, 24 Sep 2026) scaled up. Black
   fields carry gold display type and white contact lines; white paper
   bands carry the reading matter and the photographs. Every value here is
   measured off the logo files or computed from them; DESIGN-LANGUAGE.md
   has the numbers and the reasons.

   The words are Karan's, locked (Smoko art-build-sop). This file owns
   only how they are set.

   Rules everything below obeys:
   - TWO GROUNDS AND ONE GOLD. No third value. Cards on black are ruled
     cells, not tinted panels (a tint measured 1.18:1, invisible).
   - Gold is text ONLY on black (11.30:1). On paper it is a device: the
     bar in front of an eyebrow, an icon stroke, a button ground.
   - Every edge is square. Zero radius, zero shadow. Measured by check.mjs.
   - The devices are a closed registry (DESIGN-LANGUAGE.md 4 and 13): the
     gold bar, the rule, the shoulder, the lit edge, and the drawings (the
     frame retired, section 14). A new one is argued there first (R17).
     (Rewritten 2026-09-28, polish-1: this line said "three devices and no
     fourth", which the Eclipse and drawn passes had superseded.)
   - Montserrat (the logo's face) for display, Source Sans 3 for reading.
   - Mobile first: Buddy reviews this on his phone. Every number on the
     page is a tel: link with a 44px hit box.
   ===================================================================== */

@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('assets/fonts/montserrat-latin.woff2?v=1') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Source Sans 3';
  font-style: normal;
  font-weight: 200 900;
  font-display: swap;
  src: url('assets/fonts/source-sans-3-latin.woff2?v=1') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Measured (DESIGN-LANGUAGE.md 2) */
  --black: #111111;      /* the gold-on-black logo file's ground */
  --paper: #ffffff;      /* the black-on-white logo file's ground */
  --gold: #fdbd4f;       /* the transparent logo PNG, the one the site ships */
  --ink: #141414;        /* headings on paper, 18.42:1 */
  --body: #3a3a38;       /* paragraphs on paper, 11.40:1 */
  --quiet: #5e5d58;      /* small text on paper, 6.60:1 */
  --bone: #e8e4da;       /* paragraphs on black, 14.87:1 */
  --mist: #b8b4aa;       /* quiet text on black, 9.12:1 */
  /* ONE GOLD ON PAPER (polish-1 round 5, ethan-lens-5-1). gold-deep was the
     page's hairline gold on paper, and it put two golds in four of the five
     paper surfaces: the plain gold of the eyebrow bars, the sign-off bar, the
     stars and the SEND ground beside an ochre link rule, brown badges with a
     tan tint, brown pins and brown asterisks (census: about 3 plain / 2 deep,
     trust 1 / 2, map card 1 / 9, form card 1 / 5; only the reviews were one
     gold). R25 is the fleet's ruling on exactly this ("the darkened
     accessible amber just looks bad"). So on paper the gold appears only as
     MASS, in the plain gold: a filled mark (the stars, the badges, the
     headland), a rule at the bar's 3px (the link rule), a label at weight 700
     (the required asterisks). Each is 1.67:1 and ratified BY CLASS here, as
     R6 requires, never waived silently: .link-cta border, .drawing--badge,
     ~~.review stars~~ (band cut 2026-09-28 evening), .req, .places li span::after, and the map's plane
     outlines (on the map, not the paper). A LINE GLYPH on paper cannot carry
     the plain gold (a 1.75px stroke has no mass) and does not get a darker
     one: it takes INK, HG's construction (`.med`, "the glyph alone, in the
     ground's accent (ink on paper ...)", apps/hg-electrical styles.css),
     R56: the link arrow, the places' pins, the map facade's mark.
     gold-deep survives in ONE place: the focus state of a ruled field, a
     functional indicator WCAG 1.4.11 holds to 3:1 (4.06 here), shown only
     while a keyboard is on it. Reproduce the census:
       node static-sites/paraone-residential/measure/gold-census.mjs
     My call, from R25, R6 and R56 (DL section 2, amended); reversible by
     pointing the listed selectors back at var(--gold-deep). */
  --gold-deep: #a8740f;  /* the focus state on paper only, 4.06:1; never a word, never at rest */
  --error: #a3271c;      /* 6.9:1 on paper */

  --rule-dark: rgba(255, 255, 255, 0.14);   /* the rule on black */
  --rule-light: rgba(20, 20, 20, 0.14);     /* the rule on paper */
  --edge: rgba(20, 20, 20, 0.42);           /* form-control borders on the paper card */

  --display: 'Montserrat', ui-sans-serif, system-ui, sans-serif;
  --text: 'Source Sans 3', ui-sans-serif, system-ui, sans-serif;

  --container: 1680px;
  --gutter: 48px;
  /* THE BAND RHYTHM IS HG'S TOKEN, PORTED (polish-1 round 5,
     parity-inventory-5-2). It was four steps, 112 / 96 / 88 / 64px at the
     1240, 900 and 640 queries, and measured on the ink the eye takes as a
     band's first and last mark (text, media, a button's ground, a card's or
     strip's edge) every padded band read 107 to 113 above and 112 below at
     1440 and 1920, against HG's 93 to 106 and Eclipse's 75 to 80: even
     between siblings, one step looser than both referents. HG's --band is one
     fluid value, 52px at 320 to 96 at 1440 (apps/hg-electrical,
     src/app/(frontend)/styles.css), so this page takes it whole, range
     included (the file's 390-to-1920 default does not apply to this line).
     The critic's other half, bottoms 18 to 47px deeper than tops, is a
     text-only reading: the button grounds, the card edges and the trust
     strip's rule are the last ink, and on those the bottom is the token.
     Reproduce, with the site on :8395 (and the referents' URLs in place of
     it to measure them the same way):
       node static-sites/paraone-residential/measure/rhythm-round5.mjs http://127.0.0.1:8395/ 390,1440,1920
     My call, from R56, R11 and R8 (a retune is a design pass); reversible by
     writing 112px back here and the three --band-y values back into the
     1240, 900 and 640 queries. */
  --band-y: clamp(3.25rem, 2.464rem + 3.929vw, 6rem);
  --nav-h: 72px;

  /* THE HEAD-TO-BODY GAP: one value for every band, from the last line of a
     section head to the first thing the section shows (a rule, a strip, a
     photograph, the form card). My call, from R11: the four bands set four
     literals (40, 48, 32, 56) and the ink gaps measured 41, 44, 33 and 57 at
     1440, so contact sat 1.7 times further from its head than Our Work did.
     32px at 390 to 48px at 1440, rem-intercept form (the responsive skill).
     Reversible by restoring the four margin-top literals. */
  --head-gap: clamp(32px, 1.6286rem + 1.524vw, 48px);
}

/* EVERY FLUID VALUE IN THIS FILE IS IN SLOPE-INTERCEPT FORM (the responsive
   skill, section 1, RULED): clamp(min, intercept rem + slope vw, max), with the
   min reached at 390 and the max at 1920 unless its own comment says
   otherwise. Fourteen were written clamp(min, N vw, max), which sits flat on
   its min until N vw crosses it: the body was 17px at 390, 768, 1024 and 1440
   (flat below 1478) while the H2 moved 26 to 42 to 52, so at 1440 the service
   bodies read 17px under a 28px title. Now the body measures 17.0, 17.7, 18.2,
   19.1 and 20.0 at 390, 768, 1024, 1440 and 1920. Each token is
   slope = (max - min) / 1530, intercept = min - slope * 390; reproduce with
   node -e 'const [a,b]=[17,20],s=(b-a)/1530;console.log((a-s*390)/16+"rem + "+s*100+"vw")'
   and read getComputedStyle(document.body).fontSize in a Playwright page at
   each width. My call, from the responsive skill section 1 (ethan-lens-2-6);
   reversible per token from git. */

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

/* The canvas is black: both ends of the page are black (the header and hero
   above, the closing panel below), so the overscroll rubber-band paints black
   rather than flashing a white strip off either end. */
/* No tap-highlight square: the browser's default painted a blue box over the
   menu toggle in the first frame of every tap, open and close (it was unset,
   grep count 0). Focus stays visible through :focus-visible below.
   scrollbar-gutter keeps the width steady when the open drawer stops the
   page scrolling on a desktop with classic scrollbars. My call, from R29. */
html {
  background: var(--black);
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  scrollbar-gutter: stable;
  scroll-behavior: smooth;
  scroll-padding-top: var(--nav-h);
}

body {
  font-family: var(--text);
  font-size: clamp(17px, 1.0147rem + 0.1961vw, 20px);
  line-height: 1.6;
  color: var(--body);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; cursor: pointer; border: 0; background: none; }
input, select, textarea { font: inherit; color: inherit; }
ul { list-style: none; padding: 0; }

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 700;
  letter-spacing: -0.01em;
}

:focus-visible { outline: 3px solid var(--gold); outline-offset: 3px; }
/* On paper a gold ring lands at 1.67:1 and is invisible; the ring inverts with the ground. */
.band--paper :focus-visible,
.enquiry :focus-visible { outline-color: var(--ink); }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 12px 20px;
  background: var(--gold);
  color: var(--ink);
  font-family: var(--display);
  font-weight: 700;
}
.skip-link:focus { left: 0; }

/* ---- layout ---------------------------------------------------------- */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
/* THE CHROME TRACK (R36, the hourglass): the header and the footer run the
   full width with their ends tucked a gutter from the glass, while the
   reading bands keep the 1680 container. Same gutter, so below 1776px the
   chrome and the content share one edge and the difference is invisible;
   above it the logo and the controls stay in the corners instead of floating
   in 168px of black (measured at 1920 before this: logo at x=168, last
   control ending at 1752; both referents at 40 and 1880). My call, from R36;
   reversible by removing the class from the two chrome wrappers. */
.container--wide { max-width: none; }

.band { padding-block: var(--band-y); }
.band--paper { background: var(--paper); color: var(--body); }
.band--black { background: var(--black); color: var(--bone); }
/* PAPER MEETS PAPER at most joins since 2026-09-28 evening, Ethan: "make all sections light except for the statement, nav and footer"; the page used to alternate at
   every seam. Each such join takes one quiet full-width rule (R4, a tick not
   a fence), so a band still has an edge. My call; reversible by deleting
   these two lines. */
.band--paper + .band--paper,
.map-band + .close { border-top: 1px solid var(--rule-light); }
/* The black bands carry no mark on their ground (parity-inventory-3-9 weighed
   HG's enlarged brand shape at low opacity): a mark at a few percent white is
   a third value, which DL 2 rules out, and a device DL 4 does not list (R17).
   Ethan's to open. My call, from DL 2 and R17. */

.band__head { max-width: 100%; }
/* THE CENTRED HEAD, the Trust band's (index.html argues it at the site: five
   centred cells claim even pitch about the band's axis, R12, and HG sets its
   logo wall's head the same way). The HTML carried the class from round 1 and
   this rule never landed, so the head sat flush left over the strip, its ink
   centre 461px left of the strip's at 1440 and 532 at 1920 (karan-3-4). The
   eyebrow is a flex row, so its bar and words centre by justify-content.
   Measured with node static-sites/paraone-residential/measure/structure-round3.mjs
   (trustHead). My call, from R12 and R56; reversible by deleting both lines. */
.band__head--centre { text-align: center; }
.band__head--centre .eyebrow { justify-content: center; }
.band__foot { margin-top: 56px; }

/* THE SECTION HEADING, stepped down so the hero is the page's one loud moment.
   It was clamp(32px, 3.6vw, 68px), which put the H1:H2 ratio at 1.05 to 1.17
   at every width (34/32 at 390, 71.5/68 at 1920); both referents open at 1.36
   to 1.59 (Eclipse 72/48, HG 54/34 at 1920). Now 26px flat on a phone, rising
   from 640 to 52px at 1920, rem-intercept form. H1:H2 measured 1.42 (320),
   1.52 (390), 1.45 (768), 1.42 (1024), 1.65 (1440), 1.58 (1920), read as
   getComputedStyle(el).fontSize of .hero__title over #services .band__title
   in a Playwright page at each width, 900 tall. My call, from
   R29 (referent parity) and R34; reversible by restoring the old clamp.
   ROUND 3 (parity-inventory-3-3): SET LIGHTER, AND CAPPED AT 46. Every
   display heading on the page was Montserrat 700 or 800 (15 of 15 at 1920),
   where both referents set 100 percent of theirs at 400 or 500 (Eclipse's
   H2s 400 at 40 to 48px, HG's 500 at 34). The weight came across from the
   wordmark, where caps at 700 are a logo's weight; in title case at reading
   scale the same weight read as template type. The H2s and the H3s step to
   600, the H1 keeps its 800 so the two-tone line stays the one loud moment,
   and the H2 tops out at 46px, so H1:H2 lands between the referents: 26px at
   640 and under, 46 at 1920, slope 20/1280 (was 52 at 1920). Footer column
   heads are 13px labels in the eyebrow's voice and stay 700. Measured with
   node static-sites/paraone-residential/measure/structure-round3.mjs (type,
   h1h2). My call, from R56 and R34; reversible by restoring the 52px clamp
   and deleting the two font-weight lines. */
.band__title {
  font-size: clamp(26px, 1rem + 1.5625vw, 46px);
  font-weight: 600;
  line-height: 1.05;
  color: var(--ink);
  text-wrap: balance;
}
.band--black .band__title { color: var(--paper); }
/* A head that ENDS on its title (Trust) measured 4 to 5px closer to its body
   than a head ending on a paragraph, because a 1.05 line box is shorter than
   the display face's glyphs and the ink hangs below it. Measured on ink,
   --head-gap is the same gap either way with this made up. */
.band__head > .band__title:last-child { padding-bottom: 0.12em; }

.band__intro {
  margin-top: 20px;
  font-size: clamp(18px, 1.0453rem + 0.3268vw, 23px);
  line-height: 1.5;
  text-wrap: pretty;   /* see .hero__sub: reading matter wraps pretty */
}
.band--black .band__intro { color: var(--bone); }
/* THE CONTACT INTRO BREAKS BALANCED, so its line never ends on the hard
   hyphen of "no-obligation". A greedy break set "Free, no-" / "obligation
   quotes." at 320, from 641 to about 790 (centred, a two-word line under a
   line ending in a hyphen) and at 1024. Balanced it reads "Send us the
   details and we'll be / back in touch to talk it through. / Free,
   no-obligation quotes." at 320 and 1024, and two even lines at 768 and
   1440. Read as the text of each line box (a Range per character) in
   Playwright at 320, 390, 641 to 900 and 1024 to 1920. Balance was tried on
   the same break elsewhere and REFUSED: on the service titles it cures
   "Interior Fit-" / "Outs" at 320 and makes "Consent-" / "Ready Plans" at
   320 and 360, and on the centred step body at 1240 it keeps the hyphen
   break. Those need a non-breaking hyphen in the markup, which is index.html's
   (reported). The words are locked, so the break is set, not written. My
   call, from R34 (stranger-on-a-phone-3-7); reversible by deleting this
   rule. */
.contact__lead .band__intro { text-wrap: balance; }

/* ---- the eyebrow: the SOP's own register, set as ONE device ----------
   13px tracked caps ~~with the 28x3 gold bar in front~~. THE BAR IS GONE
   (Ethan, 2026-09-28 evening: "dont like the eyebrow accent"); the words
   stay, since the SOP locks them. The hero's eyebrow keeps its gold ink
   (.eyebrow--dark); one line flips it to bone if that was the accent meant. Ink on paper, gold on
   black. The bar is the only mark that says "a section starts here" and it
   appears nowhere else on the page. Named exception to R33: the SOP locks
   the eyebrow copy on every section.
   KEPT AS ONE DEVICE on 2026-09-28 (polish-1), against a critic's proposal to
   drop the bar on the paper bands and keep it on the hero only: the seven
   eyebrows exist whatever the device does (the words are locked), and a bar
   on some of them and not others is two eyebrow styles, which is the
   unevenness R11 and R61 rule out. Quieting them is Ethan's to call against
   R33. My call; reversible by adding .band--paper .eyebrow::before
   { display: none; }.
   RE-DECIDED THE SAME WAY in polish-1 round 3 (ethan-lens-1-14): since
   round 2 the device sits on six of eight headed bands, not every one (DL
   3), and the hero facts are four SOP labels in the label voice, which R7
   allows tracked caps. Quieting the eyebrows' words is Karan's (the SOP
   sets them in caps); the device stays one. My call, from R33, R11 and R7. */
.eyebrow {
  display: block;
  margin-bottom: 18px;
  font-family: var(--display);
  font-weight: 700;
  font-size: 13px;
  line-height: 1.4;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
}
.eyebrow--dark { color: var(--gold); }

/* ---- THE LIT EDGE ------------------------------------------------------
   Eclipse Air's signature (its language spec, device 1), translated: one 3px
   rule of gold along the LEADING edge of a dark mass, offset from it by a hair
   of the light ground, so it reads as light getting past a body rather than as
   a border. ~~Three~~ Four placements, one per screen: the top edge of Our
   Work, of the statement (round 5, Eclipse's No.8 carries it) and of
   the closing panel, and the leading edge of the About photograph (the
   service band carries none, the map card and the form card are paper and
   carry none; DL 4 has each reason, polish-1 round 2). It draws itself from one end on entry (transform only, nothing moves) and is
   simply present under reduced motion or without JS. */
.band--lit { position: relative; }
/* A HAIRLINE ABOVE A LIT EDGE, KEPT (round 4, stranger-on-a-phone-4-9): the
   trust strip's bottom rule and the map card's foot rule each stand 55px
   above the next band's gold at 390 (79 at 768, 60 to 103 at 1440). The rule
   closes its OBJECT (a strip ruled top and bottom, DL 14; a card), the gold
   opens the next BAND; at 55px or more they read as two edges of two things,
   not as one doubled line, which needs them within a few pixels. My call,
   from R4 and DL 14; reversible by dropping those two bottom rules. */
.band--lit::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -9px;          /* in the light ground above the band: 3px of gold, then 6px of paper, then the black */
  height: 3px;
  background: var(--gold);
  transform-origin: left center;
}
.is-armed [data-lit]::before { transform: scaleX(0); }
.is-armed [data-lit].is-in::before { transform: none; transition: transform 640ms cubic-bezier(0.2, 0.6, 0.2, 1); }

/* ---- icons: Lucide 0.469, one sprite, stroke 1.75 ------------------- */
.icon { width: 20px; height: 20px; flex: none; }

/* ---- buttons: three weights for one sentence ------------------------
   Painted rectangles, hard on all four corners like every other edge.
   The words are the SOP's and the SOP writes them in caps, as the wordmark
   is; nothing else on the page is capitalised. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  min-height: 56px;
  padding: 12px 30px;
  font-family: var(--display);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 0;
  transition: background-color 140ms ease, color 140ms ease, border-color 140ms ease;
}
/* EVERY HOVER ON THE PAGE sits behind @media (hover: hover), so a tap on a
   phone never leaves a hover state drawn (a tapped service row kept its 3px
   gold edge; 0 of 17 rules were guarded). R42 asks for exactly this guard.
   My call, from R42; reversible by unwrapping the one-line media blocks. */
.btn--solid { background: var(--gold); color: var(--ink); }
@media (hover: hover) { .btn--solid:hover { background: #f0ad35; } }
.btn--ghost { border: 1.5px solid rgba(255, 255, 255, 0.72); color: var(--paper); }
@media (hover: hover) { .btn--ghost:hover { border-color: var(--paper); background: rgba(255, 255, 255, 0.08); } }
.btn--ghost .icon { width: 18px; height: 18px; }
.btn--block { width: 100%; }

/* The header's button sits IN the bar rather than filling it. */
.btn--header { min-height: 44px; padding: 8px 18px; font-size: 14px; letter-spacing: 0.06em; white-space: nowrap; }

/* The link weight: display face, the arrow, a gold rule under it. Used inside
   prose (About) and directly above the form (Service Area, R57). */
.link-cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  margin-top: 32px;
  padding-bottom: 1px;
  /* the gold bar's own 3px, in the plain gold (round 5: was 2px gold-deep,
     with 2px of padding, so the box keeps its height; token comment above) */
  border-bottom: 3px solid var(--gold);
  font-family: var(--display);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  transition: border-color 140ms ease, gap 140ms ease;
}
@media (hover: hover) { .link-cta:hover { border-color: var(--ink); gap: 16px; } }
.link-cta .icon { width: 18px; height: 18px; color: var(--ink); }   /* a line glyph on paper: ink (round 5, token comment) */

/* ---- header: black, sticky, fused onto the hero --------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  height: var(--nav-h);
  background: var(--black);
  color: var(--paper);
}
.site-header__inner {
  height: 100%;
  display: flex;
  align-items: center;
  gap: 24px;
}
.brand { display: flex; align-items: center; min-height: 44px; flex: none; }
/* THE BAR'S LOCKUP is the client's own file cropped to the mark and the name
   (assets/img/logo-gold-name.png, rows 0 to 295 of logo-gold.png, nothing
   recoloured; README asset notes). The full stacked lockup set at 38px put its
   third line, RENOVATIONS & NEW BUILDS, at about 3px tall and the name at
   about 8px beside 14px nav links. At 44px the name's caps are about 12px.
   My call, from R34 (comparative mass), because the stacked lockup's third
   line cannot be read at chrome scale; reversible by pointing the two <img>
   back at logo-gold.png. */
.brand__mark { width: auto; height: 44px; transition: opacity 140ms ease; }
/* The logo is a link (back to the top), and it was the one visible control of
   62 probed at 1920 that did not answer the pointer. R42 names its effect: a
   quiet opacity dip on the mark. My call, from R42 and R61. */
@media (hover: hover) { .brand:hover .brand__mark { opacity: 0.82; } }

/* The navigation: the section names in the display face, a gold rule drawing
   under each on hover (the lit edge at chrome scale). The bar carries the
   number and the quote control; at 1240px and below (DL section 15) the list
   folds into a drawer under the bar and the number stays. */
.nav { display: flex; align-items: center; gap: 28px; margin-left: auto; }
/* THE LINK GROUP CLAIMS THE BAR'S CENTRE (R2, judged on ink; both referents
   centre theirs on the viewport at 1920: Eclipse 632 to 1288, HG 745 to 1175).
   It was packed against the right-hand controls by margin-left: auto, which
   landed within 4px of centre at 1440 and drifted 244px right of it at 1920
   as the chrome track widened (R36). Three tracks, the outer two 1fr with an
   AUTO minimum, so where the bar is roomy both sides are equal and the list
   sits on the centre, and where it is tight (1241 to about 1400) each side
   keeps its content and the list gives way rather than overlapping. Measured
   in measure/header.mjs as the list's ink centre against the viewport
   centre. My call, from R2, R36 and R56 (parity-inventory-2-4); reversible by
   deleting this query. */
/* ~~centred on the viewport~~ AMENDED 2026-09-28, polish-1 round 4
   (sibling-evenness-4-7): CENTRED BETWEEN ITS NEIGHBOURS' INK, equal voids.
   On the viewport's centre the group sat 245px from the logo and 38 from the
   number at 1440 (485 and 278 at 1920), because the right-hand cluster (the
   number and the quote control, about 385px) is more than twice the 178px
   logo, so at the widths Ethan reviews on the links read as attached to the
   controls. The middle track is now the free space between the two ends and
   the list centres in it, so the two voids are equal at every width from
   1241 (the referents centre on the viewport because their two ends are
   near one width, which makes the two the same construction there). Measured
   as the list's Range against the logo's box and .site-header__actions' box
   at 1440 and 1920 (measure/round4-layout.mjs navVoids). My call, from R2
   and R11, superseding DL 16's derived viewport centre; reversible by writing
   1fr auto 1fr back and deleting justify-self. */
/* ~~CENTRED BETWEEN ITS NEIGHBOURS' INK~~ BACK ON THE VIEWPORT'S CENTRE (Ethan,
   2026-09-28 evening: "nav menu isnt centered visually"). Equal voids put the
   list's ink centre 104px left of the bar's centre at 1440 and 104 at 1920
   (measured: ink centre 616 against 720, 856 against 960), because the right
   cluster is twice the logo's width, and his eye reads the bar's centre, not
   the voids. The outer tracks are equal again (1fr, auto minimum), so the
   list sits on the viewport centre wherever the bar is roomy and gives way
   where it is tight. Reversible by writing auto minmax(0, 1fr) auto back. */
@media (min-width: 1241px) {
  .site-header__inner { display: grid; grid-template-columns: minmax(auto, 1fr) auto minmax(auto, 1fr); }
  .site-header__inner .brand { grid-column: 1; justify-self: start; }
  .site-header__inner .nav { grid-column: 2; margin-left: 0; justify-self: center; }
  /* Column 3 by name, so the 404 bar (no nav) still puts its number at the
     right end rather than in the empty middle track. */
  .site-header__inner .site-header__actions { grid-column: 3; justify-self: end; gap: 18px; }
  .nav__cta { display: none; }
}
/* The drawer's number (index.html), a drawer-only row: the bar carries its
   own number above 1240. The email and area lines under it likewise. */
.nav__phone,
.nav__reach { display: none; }
.nav__list { display: flex; align-items: center; gap: 4px; }
.nav__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 12px;
  font-family: var(--display);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.02em;
  white-space: nowrap;
  color: var(--bone);
  transition: color 140ms ease;
}
.nav__link::after {
  content: '';
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 6px;
  height: 2px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 260ms cubic-bezier(0.2, 0.6, 0.2, 1);
}
@media (hover: hover) { .nav__link:hover { color: var(--paper); } }
@media (hover: hover) { .nav__link:hover::after { transform: none; } }
.site-header__actions { display: flex; align-items: center; gap: 10px; flex: none; }
/* At the desktop tier the number is a TEXT link beside the one gold control:
   two boxes in a bar read as two adverts. The ghost box returns on the
   phone, where the number is an icon and the box is its tap target. */
.site-header__phone { border: 0; padding: 0 6px; font-size: 16px; letter-spacing: 0.02em; }
@media (hover: hover) { .site-header__phone:hover { background: none; color: var(--gold); } }
.site-header__phone .icon { color: var(--gold); }
/* The 404 page has no navigation, so its actions take the bar's right edge alone. */
.site-header__actions--alone { margin-left: auto; }
.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  color: var(--paper);
  transition: color 140ms ease;
}
/* The menu toggle answers the pointer the way the bar's number does (it is a
   pointer control on any screen up to 1240px wide, and it had no hover).
   My call, from R42 and R61. */
@media (hover: hover) { .nav-toggle:hover { color: var(--gold); } }
.nav-toggle .icon { width: 24px; height: 24px; }
.nav-toggle__close { display: none; }
.nav-toggle[aria-expanded="true"] .nav-toggle__open { display: none; }
.nav-toggle[aria-expanded="true"] .nav-toggle__close { display: block; }

/* ---- 1. hero --------------------------------------------------------- */
/* THE COPY SITS ON THE FOOT (R38, the corner tuck; Eclipse's construction).
   It was align-items: center inside the 86vh floor, which split the slack
   above and below the copy, so the facts' hairline "at the hero's foot" stood
   135px above it at 1440x900, 196 at 1920x1080 and 350 at 2560x1440 over
   dimmed photograph. Flex-end puts the facts on the 56px bottom padding and
   gives all the slack to the photograph above the copy. Measured with
   .hero bottom minus .hero__facts bottom in Playwright at each size.
   My call, from R38 and R53; reversible by restoring align-items: center.
   svh, not vh (the responsive skill, section 6): the floor reaches iPad
   portrait and landscape, where vh is the glass with the URL bar retracted,
   so the facts' hairline could sit under the browser chrome. On a desktop
   the two are equal. My call (fe-2-10); reversible by writing vh back. */
.hero {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: flex-end;
  min-height: max(600px, 86svh);
  padding-block: 88px 56px;
  background: var(--black);
  color: var(--paper);
  overflow: hidden;
}
.hero__photo,
.hero__photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
}
.hero__photo img { object-fit: cover; object-position: 62% 50%; }
/* No settle-from-scale on the photograph (Eclipse's hero arrival was tried
   2026-09-28 and dropped): a scaled image measures wider than the viewport
   for the length of its transition and the shared responsive sweep reads that
   as an overflow. The lit edges are this page's arrival. */

/* Measured black from the left and the foot, so the copy sits on the palette
   rather than on whatever the photograph is doing behind it.
   AS LITTLE SCRIM AS PASSES (Ethan, 2026-09-28 evening: "as little scrim as
   possible to pass the check"). Every alpha below, in both tiers, is 0.8 of
   what it was: measure/hero-scrim.mjs sweeps the scrim's strength at 320,
   390, 768, 1024, 1240, 1440 and 1920 and reads the worst decile of each
   copy run against the pixels under it (4.5 for the sub, the eyebrow and
   the 13px caps facts, 3.0 for the display H1). 0.8 is the smallest step of
   0.02 that passes everywhere; at 0.78 the facts at 1440 read 4.47, over
   the lit roof timber. Reproduce:
     node static-sites/paraone-residential/measure/hero-scrim.mjs 1
   Reversible by multiplying the alphas by 1.25. */
/* ~~the side wash plus a top and foot band~~ REPLACED 2026-09-29 (cal-243, Karan 6:55: "maybe
   top the bottom could be nicer"; Ethan 8:04: with the buttons moved right "the gradient has to go
   up less"). ONE ramp, from the foot up, that stops where the words stop, so the top half of the
   photograph is untouched. Its stops are the smallest that pass measure/hero-scrim.mjs's floors
   (4.5 for the sub and the caps facts, 3.0 for the display H1 and the years); the sweep's numbers
   are in the commit. Ruled by Karan and Ethan on the call; the stops are my call, reversible here. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(0deg, rgba(17, 17, 17, var(--scrim-foot)) 0%, rgba(17, 17, 17, var(--scrim-mid)) var(--scrim-mid-at), rgba(17, 17, 17, var(--scrim-top)) var(--scrim-top-at));
  --scrim-foot: 0.88;
  --scrim-mid: 0.66;
  --scrim-mid-at: 44%;
  --scrim-top-at: 78%;
  --scrim-top: 0;
}
/* WHERE THE COPY REACHES THE BRIGHT HALF, THE SCRIM FOLLOWS IT. The ramp above
   was tuned for a copy column of about 60 percent; at 1240 and under the sub
   runs to 84 to 100 percent of the width and the facts sit two by two, so the
   right-hand fact and the end of the sub landed where the ramp had fallen to
   0.32 to 0.12, over the lit roof timber. Worst-decile contrast of bone on the
   bare scrimmed photograph, before: sub 3.8 and fact 3.4 at 390, sub 3.6 at
   768, fact 4.3 at 1024, fact 3.7 at 1240 (tracked caps at 12 to 13px need
   4.5). After, the worst line box at any of 320, 390, 768, 1000, 1024 and 1240
   holds 5.5 at its worst decile and 4.7 at its worst twentieth.
   Reproduce in a Playwright page at the width, 844 tall: read each Range
   client rect of .hero__sub and .hero__facts li span, set .hero__copy and
   .hero__facts to visibility: hidden, screenshot, and take the WCAG ratio of
   the span's computed colour against every second pixel inside each rect,
   sorted, at the 10th and 5th percentile. The photo
   keeps its crop. My call, from R38 (the copy sits on measured black) and the
   phone-first SOP (stranger-on-a-phone-2-1); reversible by deleting this
   query. */
/* Narrower, the words stack taller, so the same ramp climbs further. */
@media (max-width: 1000px) {
  .hero__scrim { --scrim-mid: 0.74; --scrim-mid-at: 58%; --scrim-top-at: 100%; }
}

.hero__inner { position: relative; }

/* A CONTAINER: the headline is sized off the column it lives in, so "Bay of
   Islands Builders." holds one line wherever the column is wide enough for
   display type, and stacks by composition where it is not (a phone). */
.hero__split {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 40px clamp(32px, 4vw, 72px);
}
.hero__copy {
  flex: 1 1 auto;
  max-width: 820px;
  container-type: inline-size;
  container-name: herocopy;
}
/* The right-hand column: the years over the two buttons, both right-aligned, its foot on the
   sub's foot so the pair reads as one line of the composition. */
.hero__side {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 28px;
  text-align: right;
}
/* ALLEN ROOFING'S YEARS, carried (apps/allen-roofing/src/blocks/Hero/Component.tsx): a big
   light numeral over a tracked-caps label, bone on the dark, right-aligned. Its sizes are this
   page's (Allen sets 96px over 30px at 1440 on a hero with nothing else on the right; here it
   shares the column with the buttons, so it is a step down). */
.hero__years {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  font-family: var(--display);
  font-weight: 300;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--bone);
  line-height: 1;
}
.hero__years-figure { font-size: clamp(56px, 2.5rem + 3vw, 96px); letter-spacing: 0.04em; margin-right: -0.04em; }
.hero__years-label { margin-top: 10px; font-size: clamp(14px, 0.7rem + 0.55vw, 20px); margin-right: -0.15em; }
/* 7cqw: "Bay of Islands Builders." measures 12.19em in Montserrat 800, so
   the first sentence holds one line in any column at least 12.2em wide, with
   a small margin (it was 6.1cqw, leaving 4em of the column unused). Capped at
   the language's own 100px (section 3). On a phone the size comes from the
   viewport instead (the 640 tier), since there the column is the screen. */
/* 800 KEPT (round 4, parity-inventory-4-8: both referents set their hero at
   400, and R38 asks the text to let the photo shine). The H1 is the
   wordmark's weight on the page's one loud moment (DL 3 and 18, which took
   every H2 and H3 to 600 so this line alone carries it), and R38 is already
   answered by the copy tucked to the hero's foot with the photograph taking
   the slack above. 700 is the one-step option if Ethan's eye wants it. My
   call, from DL 3 and DL 18; reversible by writing 700 here. */
/* SHRUNK (Ethan, 2026-09-28 evening: "can we shrink hero text"). ~~clamp(36px,
   7cqw, 100px)~~, which set 69.6px at 1440 and 82 at 1920 (1.65 and 1.58
   times the H2). Now the H2's own slope (clamp(26px, 1rem + 1.5625vw, 46px))
   times 1.45, the middle of the referents' 1.36 to 1.59: 46.4 at 1024, 55.8
   at 1440, 66 at 1920, so the H1:H2 ratio is 1.45 flat instead of drifting
   with the column. "Bay of Islands Builders." (12.19em) still holds one line
   wherever the column is at least 12.2em wide; the phone break below is
   unchanged. Between HG's 54 and Eclipse's 84 at 1440. Reversible by
   restoring the cqw clamp. */
/* DOWN A STEP, 2026-09-29 (cal-243, Ethan 8:09: "i can just size that down"): the H2's slope
   times 1.3, was 1.45. Reversible by writing the 1.45 line back. */
.hero__title {
  font-size: clamp(34px, 1.3rem + 2.0313vw, 60px);
  font-weight: 600;   /* ~~800~~ 600, 2026-09-28 evening, Ethan: "way too heavy, more like the other one we use": the H2s' weight */
  line-height: 1.02;
  color: var(--paper);
}
/* THE SIGNATURE: the second line in gold, the site sign's construction
   (white contact lines, gold name). Appears once on the page. Balanced, so
   a sentence that has to wrap on a phone wraps evenly ("Homes Built / to
   Last.") rather than leaving its last word alone.
   THE PHONE BUDGET, a named exception to R34's two lines: below about 500px
   the H1 is TWO AUTHORED LINES, EACH BROKEN ONCE, balanced, so four lines of
   two words or three, never a single-word widow. Measured word by word at
   320, 360, 390 and 430: "Bay of Islands / Builders. / Homes Built / to
   Last." Two lines on a phone would need about 28px (the first sentence is
   12.19em), which puts the H1 on the H2's 26px and spends the page's one loud
   moment. The words are locked, so the break is set, not written. My call,
   from R34 and R28 (never shrink type to escape the measure); reversible by
   dropping the 640 tier's clamp floor to 28px. */
.hero__line { display: block; text-wrap: balance; }
.hero__line--gold { color: var(--gold); }
/* THE PHONE BREAK, composed for BOTH sentences at once. Balance alone left a
   band of widths (column 356 to 488px, viewports about 400 to 527) where the
   first sentence wrapped and the shorter second did not, so the headline set
   as three lines, "Bay of Islands / Builders. / Homes Built to Last."
   (measured at 480). Wherever "Bay of Islands Builders." cannot hold one line
   (12.19em, 488px at the 40px phone size) each sentence is capped at 7.5em,
   which holds "Bay of Islands" (7.30em) and refuses "Homes Built to" (7.79em),
   so the phone reads "Bay of Islands / Builders. / Homes Built / to Last." at
   every width under 528. Widths measured as em of the rendered H1 in a
   Playwright page. My call, from R34 (a wrap is composed, never incidental);
   reversible by deleting this query. */
@container herocopy (max-width: 490px) {
  .hero__line { max-width: 7.5em; }
}

/* Reading matter wraps PRETTY, not balanced: balance shortens every line to
   match the shortest, which set the services intro at 273 of 350px at 390.
   Balance stays on the headings. My call, from R13 and R35; reversible by
   putting balance back on this rule and on .band__intro. */
.hero__sub {
  margin-top: 26px;
  font-size: clamp(18px, 1.0294rem + 0.3922vw, 24px);
  line-height: 1.5;
  color: var(--bone);
  text-wrap: pretty;
}
.hero__actions {
  display: flex;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 14px 18px;
}
/* Four facts on one hairline at the hero's foot, a mark each. THE STRIP SPANS
   ITS HAIRLINE (R3): the facts are distributed with the slack as equal
   gutters, first ink on the hairline's left end and last ink on its right.
   With four content-sized facts and a fixed gap the last fact's ink stopped
   97px short of the rule's end at 1440 and 316px short at 1920 and 2560. (For
   a while in round 1 there were two, and space-between threw them to the
   ends with 746px of bare rule between at 1440; the four are back, polish-1
   round 2, see the comment in index.html.) Ink ends now
   measure equal to the rule's ends at 390, 768, 1024, 1440, 1920 and 2560
   (a Range over each li against .hero__facts' box). My call, from R3 and R2;
   reversible by removing justify-content here and in the 2x2 query below. */
.hero__facts {
  margin-top: clamp(48px, 2.3627rem + 2.6144vw, 88px);
  padding-top: 22px;
  border-top: 1px solid rgba(255, 255, 255, 0.28);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 10px clamp(28px, 1.3039rem + 1.8301vw, 56px);
}
.hero__facts li {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 32px;
  font-family: var(--display);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bone);
}
.hero__facts .icon { color: var(--gold); width: 20px; height: 20px; }
/* The four facts are one line only where one line fits them: about 1236px
   of column (the four measure 1100px together, plus three gaps). Narrower
   than that they are TWO BY TWO, never three and an orphan: at 1024 the
   wrapping row put three facts over one, and at 768 two over two with the
   second row out of line with the first. My call, from R11 and R30 (the same
   odd-remainder class as the work grid's orphan); reversible by deleting
   this query. The phone tier below centres each cell. */
.hero__inner { container-type: inline-size; container-name: heroinner; }
/* The 2x2 spans the hairline too: the second column is pushed to its right
   end, so the pair of columns fills the rule rather than stopping 78px short
   of it at 768 (ink ended at 658 against a 736 container). */
/* ~~pushed to its right end~~ STRUCK 2026-09-28, polish-1 round 4
   (sibling-evenness-4-5): two max-content columns thrown to the rule's ends
   left the inner void at whatever the left column's labels measured, 196 on
   the first row and 106 on the second at 768, 420 and 330 at 1024, two
   columns at the far edges with an empty middle. The 2x2 is now the phone's
   construction (below 641): two equal halves of the rule, each fact centred
   in its half as one unit (mark and label), so the four marks pitch evenly
   about the hero's axis (R12) and both rows match. The mark stays beside the
   label here, where every label holds one line in its half (it goes above on
   the phone, where they wrap). Measured as each li's Range against
   .hero__facts' box at 768 and 1024 (measure/round4-layout.mjs facts).
   My call, from R12 and R30; reversible by restoring repeat(2, max-content)
   and space-between. */
@container heroinner (max-width: 1236px) {
  .hero__facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hero__facts li { justify-content: center; }
  /* AN ODD LAST FACT IS CENTRED ACROSS BOTH COLUMNS, 2026-10-02: the strip is
     three while the licence fact is off (index.html, the hero facts comment),
     and two over a left-hand orphan is the R11 remainder the 2x2 exists to
     avoid. Keyed on the count, not on today's three: with four facts the
     last child is even and this matches nothing. My call, from R11 and R12;
     reversible by deleting the rule. */
  .hero__facts li:last-child:nth-child(odd) { grid-column: 1 / -1; }
}
/* THREE FACTS HOLD ONE LINE FURTHER DOWN THAN FOUR DO, 2026-10-02. The 1236px
   above is where four stop fitting; the three measure 878px of ink together
   (202, 391 and 285 at 1440, measure/facts-work-drawer.mjs), and with their
   two gaps they need about 958px of column, so they stay on the hairline,
   thrown to its ends like the wide tier, down to a 980px column. Below that
   they take the grid above. Matches only a strip with no fourth fact, so the
   four are untouched when the licence fact returns. My call, from R3 (the
   strip spans its hairline); reversible by deleting this query. */
@container heroinner (min-width: 980px) and (max-width: 1236px) {
  .hero__facts:not(:has(li:nth-child(4))) { display: flex; }
}

/* ---- THE CUT GRID ----------------------------------------------------
   A band whose BODY (a photograph, the map) runs to the glass at any width
   while the copy stays on the container's measure. Two rails take the space
   outside the container (at least a gutter; more on a wide screen), the copy
   sits on the second track, the body spans from the fourth track to the
   right glass. A negative margin cannot do this: it reaches the container's
   edge, and on a screen wider than the container that edge is not the
   glass (Ethan, 2026-09-28: "this block image isnt touching the right of the
   screen"). Percentages here resolve against the band, which is full width. */
.cut {
  --cut-copy: 7fr;
  --cut-body: 5fr;
  display: grid;
  grid-template-columns:
    minmax(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)))
    minmax(0, var(--cut-copy))
    clamp(40px, 1.6078rem + 3.6601vw, 96px)
    minmax(0, var(--cut-body))
    minmax(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)));
  align-items: center;
}
.cut__copy { grid-column: 2; }
.cut__body { grid-column: 4 / 6; }

/* ---- 2. about -------------------------------------------------------- */
/* THE CUT: copy at the left on the paper, the photograph a BODY at the right
   running to the glass and to the band's top and foot, taller than the copy
   needs and cut by the band rather than fitted to it, with the lit edge on its
   leading (left) side. The copy is centred against the body. */
/* No padding on the copy: an 8px padding-block stacked on the band's own put
   About's first ink 8px lower than every sibling band's (eyebrow ink top less
   section top: 72, 97 and 121 at 390, 768 and 1440 against 64, 89 and 113 for
   Services and Trust), and its last ink 8px higher off the foot. The body
   still spans the copy plus both band paddings. My call, from R11
   (sibling-evenness-2-7); reversible by restoring padding-block: 8px. */
/* No min-height: the body's height is the copy's plus both band paddings
   (R53), which measured 772 to 856px at every width above 1180, so the old
   560px floor never bound and only claimed it might. */
.about__photo {
  position: relative;
  align-self: stretch;
  margin-top: calc(-1 * var(--band-y));
  margin-bottom: calc(-1 * var(--band-y));
}
.about__photo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 45%;
}
.about__photo::before {
  content: '';
  position: absolute;
  left: -9px;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--gold);
  transform-origin: center top;
  z-index: 1;
}
.is-armed .about__photo[data-lit]::before { transform: scaleY(0); }
.is-armed .about__photo[data-lit].is-in::before { transform: none; transition: transform 640ms cubic-bezier(0.2, 0.6, 0.2, 1); }
/* R41 ON THE SIDE-BY-SIDE TIER: A CONTENT IMAGE NEVER EXCEEDS ONE VIEWPORT
   (Ethan, 2026-08-08, of an About image: "thats a no no always"). The cut
   body is the copy plus both band paddings, 837 to 846px from 1181 to 1299
   and 785 to 795 from 1400 up, whatever the window's height, so on a laptop
   it could never be seen whole: 841 against 728px of glass under the bar at
   1240 x 800, 846 against 648 at 1280 x 720, 787 against 588 at R41's own
   audit floor, 1440 x 660, and against 718 at 1440 x 790. The stacked tier
   already capped it at the glass; this side of the breakpoint did not.
   WHERE THE CUT WOULD OUTGROW THE GLASS, THE BODY FITS THE COPY instead: no
   bleed into the band's paddings, centred on the copy, never taller than the
   glass. It still runs to the right glass (the half Ethan ruled, DL 13) and
   keeps its lit edge; it stops short of the band's top and foot, which was
   the DERIVED half (DL 5b). Two thresholds because the body is two heights:
   846 + 72 of bar = 918 below 1400, 795 + 72 = 867 from 1400. A cap that
   merely clamped the cut would leave 3 to 7px slivers of paper at the band's
   edges wherever the glass fell just short, so the switch is the whole
   construction or none. Read as .about__photo's box against innerHeight less
   --nav-h in Playwright at 1240 x 800, 1280 x 720, 1440 x 660, 790 and 900,
   1512 x 982 and 1920 x 1080. My call, from R41 (ethan-lens-3-4,
   ethan-lens-2-12, fe-2-8); DL 5b's line wants the same amendment (another
   partition's file). Reversible by deleting both queries. */
@media (min-width: 1181px) and (max-width: 1399px) and (max-height: 917px),
       (min-width: 1400px) and (max-height: 866px) {
  .about__photo {
    align-self: center;
    height: min(100%, calc(100svh - var(--nav-h)));
    margin-block: 0;
  }
}

.prose { display: grid; gap: 20px; }
.prose p { text-wrap: pretty; }   /* see .service__body */
.about .prose { margin-top: 28px; }
/* THE ANSWER TO THE FEAR, given weight where it already sits: "No cutting
   corners, no being treated as a number." is the SOP's answer to the
   homeowner's fear (DL section 1: one job on a big firm's list, the
   apprentice, the corners cut), and it was set at the body's weight and
   colour mid-paragraph, the same as the UK history before it. The reading
   face at 600 in ink; no word moved or repeated (section 11 refused a
   pull-quote for that reason). My call; reversible by deleting the strong. */
.prose__answer { font-weight: 600; color: var(--ink); }

/* His signature, in the display face, with the section's device above it:
   the one line of his own voice on the page. */
.signoff {
  margin-top: 32px;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(20px, 1.1544rem + 0.3922vw, 26px);
  color: var(--ink);
}
.signoff::before {
  content: '';
  display: block;
  width: 28px;
  height: 3px;
  margin-bottom: 14px;
  background: var(--gold);
}
/* THE LINK WEIGHT LEADS BY ONE GAP ON INK, in both its places. After the
   sign-off, a display line with its own half-leading, the 32px margin read
   46 to 48px from the sign-off's ink to the link's words, against 34 after
   the map card's places at every width (sibling-evenness-4-4). 20 here puts
   About at the Area value. Measured as the link text's Range top less the
   previous element's Range bottom at 390, 768, 1024, 1440 and 1920
   (measure/round4-layout.mjs linkLead). My call, from R11; reversible by
   deleting this rule. */
.about .link-cta { margin-top: 20px; }

/* ---- 3. services: seven cuts, the photograph swapping sides ---------- */
/* Ethan, 2026-09-28, the hand pass after the polish line: "i think we get
   some of the photos and keep the graphics so we can do a sort of
   alternating thing like allen roofing". RULED. Allen Roofing's services
   (apps/allen-roofing ServicesGrid, live at allenroofing.nz) are full-bleed
   halves: the photograph fills one half to the glass, the words sit centred
   in the other half, and the sides swap row by row, no rules between. Here
   that is THE CUT, the device About already carries (DL 4 and 11), run seven
   times and mirrored on alternate rows, so the band is this site's own
   construction rather than a port (R17, R56). The drawings stay: above the
   title as the row's mark on the five rows that carry a photograph, and at
   body scale in the photograph's slot on the two that have none (Bathrooms,
   Consent-Ready Plans), so every row has a body and every row carries its
   drawing exactly once. Which photograph stands beside which service is a
   MOCK (site-manifest `service-photos`): the pool is not labelled by
   service, and Karan's row already asks where the photographed builds were.
   The rounds 2 to 5 constructions (the ruled index, the 128px drawing unit
   and their measurements) are superseded by this ruling and live in git;
   abaf541d3 is the last commit carrying them. The --svc-drawing and
   --svc-gap tokens stay on #services because the steps' timeline below
   still sets its numeral column on them. */
#services {
  --svc-drawing: 128px;
  --svc-gap: clamp(32px, 1.5rem + 1.5vw, 56px);
}
.services { margin-top: var(--head-gap); }
.service {
  --cut-copy: 6fr;
  --cut-body: 6fr;
  position: relative;
}
/* THE MIRRORED CUT: the body runs to the LEFT glass and the copy sits on the
   fourth track. Odd rows take it, so the band opens against About's cut
   (body right) with a body left, and the eye walks Ethan's zigzag (DL 14) at
   band scale rather than row scale. */
.cut--left {
  grid-template-columns:
    minmax(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)))
    minmax(0, var(--cut-body))
    clamp(40px, 1.6078rem + 3.6601vw, 96px)
    minmax(0, var(--cut-copy))
    minmax(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)));
}
.cut--left .cut__body { grid-column: 1 / 3; }
.cut--left .cut__copy { grid-column: 4; }
/* The photograph is first in source (it is first on a phone), so on the
   body-right rows the grid's sparse placement would drop the copy, whose
   column is behind the cursor, to a second row: both cells are pinned to row
   one. Measured 2026-09-28 before the pin: even rows 641px against 454. */
.service .cut__body, .service .cut__copy { grid-row: 1; }
/* THE PHOTOGRAPH: 3:2, cover, no border and no rule (the frame, DL 4; the next
   row's photograph is its own edge, as on Allen). The row's height is the
   photograph's, a consequence of the column (R53), about 470px at 1440. */
.service__photo {
  position: relative;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: var(--black);
}
.service__photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* THE DRAWN BODY: the same box on black with the drawing centred at 46
   percent of it (320px at 1440, the largest these hairlines were drawn to
   carry, DL 14). */
.service__photo--drawn { display: grid; place-items: center; }
.service__photo--drawn .drawing { width: 46%; max-width: 360px; }
.drawing { width: 100%; height: auto; aspect-ratio: 160 / 110; color: var(--gold); }
.drawing--badge { width: 72px; max-width: 72px; height: 72px; aspect-ratio: 1; color: var(--gold); }   /* mass marks: the plain gold (round 5; the tint fill is gone from drawings/marks.mjs) */
.icon--service { width: 34px; height: 34px; color: var(--gold); }
/* THE MARK above the title: the drawing at half its drawn size (80 x 55). */
.service__text { max-width: 36rem; }
.service__mark { width: 80px; margin-bottom: 18px; }
.service__title {
  font-size: clamp(26px, 1.25rem + 1vw, 36px);
  font-weight: 600;
  line-height: 1.12;
  color: var(--ink);
}
.service__body { margin-top: 14px; font-size: clamp(17px, 1rem + 0.35vw, 20px); color: var(--body); line-height: 1.55; text-wrap: pretty; }
/* THE HALVES HOLD TO 901. The cut stacks at 1180 for About's sake (its body
   in a 5fr track was taller than the glass, the round-3 note at that tier),
   but a service row's 3:2 body in a 6fr track is 296px tall at 1024 and the
   stacked row would be 821, seven times over; so between 901 and 1180 the
   rows re-assert the two-column cut. Measured 2026-09-28 at 1024 and 1180
   before this block: rows 821 and 911px, the photograph the full glass. */
@media (min-width: 901px) and (max-width: 1180px) {
  .service.cut {
    grid-template-columns:
      minmax(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)))
      minmax(0, var(--cut-copy))
      clamp(40px, 1.6078rem + 3.6601vw, 96px)
      minmax(0, var(--cut-body))
      minmax(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)));
    gap: 0;
    padding-inline: 0;
  }
  .service.cut--left {
    grid-template-columns:
      minmax(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)))
      minmax(0, var(--cut-body))
      clamp(40px, 1.6078rem + 3.6601vw, 96px)
      minmax(0, var(--cut-copy))
      minmax(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)));
  }
  .service .cut__copy { grid-column: 2; }
  .service .cut__body { grid-column: 4 / 6; margin-inline: 0; }
  .service.cut--left .cut__body { grid-column: 1 / 3; }
  .service.cut--left .cut__copy { grid-column: 4; }
  .service .cut__body, .service .cut__copy { grid-row: 1; }
}
/* STACKED (900 and under): the photograph first, edge to edge, then the
   words on the gutter, 40px clear before the next row's photograph. The
   mirrored cut's spans come off here because the stacked cut is one column
   (a span of two in one column would open a second), and the two cells take
   rows one and two. The photograph goes 16:9 stacked so a row is under a
   screen on a phone. */
@media (max-width: 900px) {
  .cut--left .cut__body, .cut--left .cut__copy { grid-column: 1; }
  .service .cut__body { grid-row: 1; }
  .service .cut__copy { grid-row: 2; }
  .service { gap: 24px; padding-bottom: 40px; }
  .service__photo { aspect-ratio: 16 / 9; }
  .service__photo--drawn .drawing { width: 40%; max-width: 260px; }
  .service__mark { width: 64px; margin-bottom: 12px; }
}

/* ---- 3, foot: how a job runs, HG's ProcessRun ported ------------------
   Four numbered steps on ONE RULE (the page's rule device, run as a thread):
   below 1024 a left-hand timeline, the numeral in a 48px column and the rule
   dropping from it to the next step; from 1024 four across, each step centred
   under its numeral and the rule running across through the numerals'
   centres, HG's `.run` at the same breakpoint (apps/hg-electrical styles.css,
   `.run__step::before`, and Ethan's "centred above" on it, 2026-09-14). HG's
   62px disc is not ported: this page has no curve (DL 4). The numeral box is
   a square of the same width as the phone column so the rule's ends are
   computed, not guessed. Measured with
   node static-sites/paraone-residential/measure/structure-round3.mjs (steps).
   My call, from R56 and comparative-mass-3-1; reversible by deleting this
   block and the list in index.html. */
.steps {
  --step-num: 48px;
  --steps-gap: clamp(28px, 1.2rem + 1.2vw, 48px);
  margin-top: 56px;
  padding: 0;   /* an ol, which the file's list reset (ul only) does not reach */
  list-style: none;
  display: grid;
}
/* BELOW 1024 THE NUMERAL TAKES THE SERVICE DRAWING'S TRACK, so the step words
   start on the service words' x: a 48px column and a 20px gap put them at 88
   under service words at 124 at 390, and at 100 under 196 and 76 at 768, a
   third edge in one band (stranger-on-a-phone-4-5). Now the column is
   --svc-drawing and the gap --svc-gap (the band's, #services), the numeral
   centred in it and the thread dropping from its centre; at 641 to 1023 that
   is the odd rows' x, the one the band opens on. Measured as each
   .step__title's and .service__title's Range left at 390 and 768 in
   Playwright (measure/round4-layout.mjs stepTitleX, svcTitleX).
   My call, from R2 and R11; reversible by writing var(--step-num) and 20px
   back into these two lines and the thread's left. */
.step {
  position: relative;
  display: grid;
  grid-template-columns: var(--svc-drawing) minmax(0, 1fr);
  column-gap: var(--svc-gap);
  padding-bottom: 28px;
}
.step__num { justify-self: center; }
.step:last-child { padding-bottom: 0; }
.step::before {
  content: '';
  position: absolute;
  left: calc(var(--svc-drawing) / 2);
  top: calc(var(--step-num) + 8px);
  bottom: 8px;
  width: 1px;
  background: var(--rule-light);
}
.step:last-child::before { display: none; }
.step__num {
  width: var(--step-num);
  height: var(--step-num);
  display: grid;
  place-items: center;
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(30px, 1.5rem + 0.6vw, 40px);
  line-height: 1;
  color: var(--gold);
}
.step__title {
  font-size: clamp(19px, 1.0625rem + 0.3vw, 24px);
  font-weight: 600;
  line-height: 1.2;
  color: var(--ink);
  padding-top: 11px;
  text-wrap: balance;
}
.step__body { margin-top: 8px; color: var(--body); line-height: 1.5; text-wrap: pretty; }
@media (min-width: 1024px) {
  .steps { grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: var(--steps-gap); }
  .step {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
    align-content: start;
    text-align: center;
    padding-bottom: 0;
    row-gap: 14px;
  }
  .step__title { padding-top: 0; }
  /* THE BAND'S BUTTON CENTRES UNDER THE CENTRED RUN, as HG sets its
     ProcessRun's action ("desktop:text-center" on the paragraph holding it,
     apps/hg-electrical ProcessRun/Component.tsx). The steps are centred on
     the band's axis from here up and the page-wide band__foot left the
     button flush left under them, 48..317 at 1440 against the run's axis at
     720: three alignments in one band. The head stays flush left: the head
     over its rows is the band's own stack. Only the Services foot sits under
     a centred group (the Our Work foot sits under a flush grid and a flush
     head, and keeps its edge). My call, from R56 and R2 (fe-4-6); reversible
     by deleting this line. Button centre on the run's axis within 1px at
     1024, 1440 and 1920 after (round4-partition2.mjs steps, in measure/). */
  #services .band__foot { text-align: center; }
  /* the rule runs from this numeral's right edge, 12px clear, to the next
     numeral's left edge, 12px clear: this cell's centre plus half a numeral,
     through the gap, to the next cell's centre less half a numeral */
  .step::before {
    left: calc(50% + var(--step-num) / 2 + 12px);
    right: calc(var(--step-num) / 2 + 12px - 50% - var(--steps-gap));
    top: calc(var(--step-num) / 2);
    bottom: auto;
    width: auto;
    height: 1px;
  }
}

/* STRUCK 2026-09-28, polish-1 round 3 (ethan-lens-3-5), with the query it
   argued for: the ruled index below read as small end-marks on a ruled table
   (one line of words per 186px row, an empty drawing slot on every row), and
   its premise that "no text measure fills that" was the wrong question: the
   row does not have to be filled across, it has to be set by its words. The
   comment is kept as the record; the rules are deleted.
   ~~THE WIDE TIER: THE ROW IS A RULED INDEX, THE DRAWING STILL CROSSING SIDES.
   Above 1240 the unit above (a 160px drawing, then the title over a one
   sentence body) measures 850 to 920px and the row it sits in 1145 to 1584, so
   every row was about half empty: 434 to 629px of black per row at 1440, 667
   to 772 at 1920, the words jumping between x=380 and x=830 row to row. No
   text measure fills that, because the SOP's service bodies are one sentence.
   So the row takes four columns shared by all seven rows (a subgrid): a
   drawing slot at each end, the title, and the body beside it. The drawing
   sits in the left slot on odd rows and the right slot on even rows, which is
   the zigzag Ethan ruled (DL 14), and the titles and bodies now start on one
   x each, down the whole band, so the words read as one schedule and fill the
   row between the two slots. The title and the body share a first baseline.
   A head beside the rows (the critic's proposal) was refused: Ethan, on
   Eclipse's /process, "i [do not] like the heading in a seperate coloumn from
   its body content" (docs/v2/language/eclipse-air/spec.md, "headings stack").
   Measured in measure/services.mjs, the ink void per row (the row's
   width less the span from its first ink to its last): see DL section 16.
   My call, from R13 and R35, alternation kept per DL 14 (ethan-lens-2-3);
   reversible by deleting this query.~~ */

/* The round-4 wide tier (the ruled index from 1024 up) was here; superseded
   2026-09-28 by the cuts (section 3 above) and kept in git. */

/* ---- 4. trust: one strip of five equal cells -------------------------- */
/* ONE LINE OF FIVE, evenly pitched, ruled top and bottom and nothing between
   (Ethan, 2026-09-28: "logos thing could look a lot more polished"). The two
   drawn badges sit at the same height, their label under. Pitch is judged on
   the marks' centres, so the tracks are equal (R12).
   ~~The three marks sit in MONOCHROME at one visual weight and take their own
   colour under the pointer.~~ STRUCK 2026-09-28, polish-1 partition 1: the
   marks are credentials, and grey at 0.72 read as disabled "as seen in"
   logos; on a phone, the SOP's primary device, no pointer ever brought the
   colour back, and neither referent greys its marks (grep for grayscale in
   apps/hg-electrical and apps/eclipse-air: map only). They sit in their own
   colour at rest, and without a hover, because they are not links (R42). */
.trust {
  margin-top: var(--head-gap);
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  border-top: 1px solid var(--rule-light);
  border-bottom: 1px solid var(--rule-light);
}
/* WITHOUT THE LBP CELL THE STRIP IS FOUR EQUAL CELLS, 2026-10-02 (index.html,
   the trust comment: the cell is off until Buddy's licence is through). Five
   columns holding four cells left a bare fifth at the right. Keyed on the
   cell's presence, so putting it back restores the five with no edit here.
   Above 900 only: this selector outranks the 900 tier's six-column grid.
   My call, from R3 and R12; reversible by deleting the rule. */
@media (min-width: 901px) {
  .trust:not(:has(.trust__logo--lbp)) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* ONE MARK LINE (R11, R12). Every cell is the same two rows: a mark slot the
   height of the tallest mark (the NZCB chevron, --mark-slot), then the label. The
   ~~three logos have no label, so all five marks sit in one slot at one height
   and their centres share a line, with the two labels hanging under it.~~
   Since 2026-09-28 evening (Ethan: "need text under these") every cell
   carries its SOP name under the mark, and the two drawings bookend the
   three logos; the mark line and the label row are the same two grid rows
   for all five. It was
   a centred flex column per cell, where a badge's label pushed its drawing up:
   the badge centres stood 19px above the logos' at 1440. The cells take their
   height from that content (R53); the 176px and 132px floors are gone.
   My call, from R11 and R12; reversible by restoring the flex column. */
.trust__logo,
.trust__badge {
  /* 92px to 120px: the marks scale up to 1.3 times by 1920 (104 at 1024, 111
     at 1440, off the slope-intercept form at :root; it was flat to 1095), where
     five of them covered 10.7% of a 1584 x 178 strip at 1920. The logo heights
     below are fractions of this slot (the eye-set 58, 92 and 50 of 92), so the
     set keeps its balance as it scales. My call, from R53 and the band's one
     job; reversible by setting 92px back. */
  --mark-slot: clamp(92px, 5.3039rem + 1.8301vw, 120px);
  padding: 32px 20px;
  display: grid;
  grid-template-rows: var(--mark-slot) auto;
  align-content: start;
  align-items: center;
  justify-items: center;
  row-gap: 14px;
  text-align: center;
}
.trust__logo img {
  width: auto;
  height: auto;
  max-width: 100%;
}
/* Heights set against each other by eye so the three read as one weight: the
   LBP lockup wide and low, the NZCB badge tall, the ITM block squat (58, 92
   and 50 of the 92px slot, kept as fractions of it).
   ~~58, 92 and 50 read as one weight~~ MEASURED otherwise 2026-09-28, polish-1
   round 5 partition 1 (comparative-mass-4-7): weighted ink (the sum of each
   mark pixel's darkness against paper) at 1440 was LBP 2932, NZCB 3653, ITM
   4020, so the solid ITM block, the smallest box, was the heaviest mark and
   the LBP lockup, whose words are light grey, the lightest (1.37 to 1). The
   fractions are now set by weight, not by one height: LBP up to 0.70 of the
   slot, ITM down to 0.518, NZCB unchanged as the slot. Read with
   node static-sites/paraone-residential/measure/trust-ink.mjs (weighted per member, per width).
   My call, from R11 (peers read at one weight); reversible by restoring 0.63
   and 0.543. */
.trust__logo--lbp img { max-height: calc(var(--mark-slot) * 0.70); }
.trust__logo--nzcb img { max-height: var(--mark-slot); }
.trust__logo--itm img { max-height: calc(var(--mark-slot) * 0.518); }
/* LBP IS THE ONE MARK THE CELL'S WIDTH CAPS BEFORE ITS HEIGHT. At 1024 its
   cell had 146px inside 20px of side padding, so it drew at 146 x 49 and
   1407 of weighted ink against NZCB's 3172 (44 percent). The strip has no
   hairlines between cells, so a logo cell's side padding guards only the
   neighbour's distance, and LBP's neighbour NZCB is 50px wide in a cell of
   the same size: at 8px the ink gap between them is still 70px or more.
   Scoped to the one-line tier; the 900 and 640 tiers set their own. Read
   with measure/trust-ink.mjs at 901 to 1440. My call, from R11; reversible
   by deleting this query. */
@media (min-width: 901px) {
  .trust__logo--lbp { padding-inline: 8px; }
}
/* THE TWO DRAWN BADGES AT THE MARKS' MASS. At 72px their hairlines were about
   1px and their ink 58 x 20 (years) and 65 x 42 (local), beside logos whose
   ink is 50 to 92px tall and solid. Their symbols carry a 120 x 120 viewBox
   with the ink in a band (years y 36 to 70, local y 34 to 104), so each is
   sized for its ink and then moved so the INK's centre, not the box's, sits
   on the mark line: years at 1.39 slots (128px on the 92px slot, ink about
   102 x 36, the stroke about 1.9px), translated down 7/120 of its box; local
   at 1.04 slots (96px, ink about 86 x 56), translated up 9/120. ~~Measured ink
   centres of all five on one line to the pixel at 1024, 1181, 1440, 1920 and
   2560 (box centre less or plus that fraction of the box, off
   getBoundingClientRect).~~ STRUCK 2026-09-28, polish-1 round 5, partition 2
   (fe-4-7): that was arithmetic on boxes, not a reading of ink. Read off the
   pixels instead (each .trust li screenshotted with .trust__label hidden, the
   bounding box of every pixel whose R+G+B is under 705 on the white paper),
   the five ink centres span 2.0px at 901, 1024 and 1920 and 1.5 at 1440 and
   2560, so they share one line within 2px; the critic's 98 / 80 against
   89 were the BOX centres, which this translate moves on purpose. (The
   getBBox ink column of measure/trust-badges.mjs disagrees with the pixels,
   106 against 86 for the years badge at 1440, and is not the instrument for
   this.) Percent translate is of the element's own box, so the
   correction holds at every size.
   THE WIDE BADGE CENTRES EVEN WHEN IT OVERFLOWS ITS CELL (fe-4-7's adjacent
   gap, taken). At 1.39 slots the years drawing is wider than its cell's
   content box from 901 to about 1000 (141 against 121 at 901), and the cell's
   one auto column grew to the drawing and started at the padding edge, so its
   ink stood right
   of its cell's centre by 9.5px at 901, 6.5 at 940, 3.0 at 980 and 1.0 at
   1000 (the other four within 1px), an uneven pitch on the one row R12
   judges by pitch. The badge cell's column is now minmax(0, 1fr), the content
   box, and `unsafe center` centres the drawing in it and lets it spill evenly
   into the cell's own empty padding. Badges only: on the logo cells the same
   column changed the phone line's image sizes (ITM 50 to 40px tall at 390),
   because there the cells are content-sized flex items. Same pixel reading
   as above, and the phone tiers read identically before and after (320, 390,
   640, 768, 900). My call, from R12; reversible by deleting the
   justify-self and the .trust__badge column line. My call, from R11 (peer weight) and DL
   section 13 (the badges are drawn, which is kept rather than swapped for
   HG's icon-and-label); reversible by restoring the 72px badge. The cleaner
   fix is a viewBox cropped to the ink in drawings/marks.mjs.
   THEIR WEIGHT, DECIDED IN ROUND 3 (sibling-evenness-2-11, fe-3-7): the
   badges match the logos' box but not their mass (dark-pixel mass at 1440:
   the local badge about a tenth of a logo's), because hairlines beside solid
   fills cannot. The course is a heavier stroke on the two badge symbols
   only, about 2.5 to 3px at their rendered size, generated in
   drawings/marks.mjs. ~~It cannot be done from here: each path in
   drawings.svg carries its own stroke-width attribute, which beats anything
   a <use> inherits from this file. So it is reported to that file's owner.~~
   DONE there in round 4 (commit 42c3aef39, sibling-evenness-4-1): strokes
   2.5 to 3.25 units and one fill each, 78 and 60 percent of LBP's weighted
   ink at 1440 (drawings/badge-mass.mjs). My call, from R11.
   THE STRIP KEEPS ONE MARK LINE WITH TWO LABELS HANGING UNDER IT (round 4,
   comparative-mass-4-7, parity-inventory-4-7, which proposed one ink band
   for all five: the logos grown to the badges' drawing-plus-label height, or
   HG's icon beside a label). The five marks' ink centres share one line,
   which is the evenness R12 judges a centred row by; a label is not a mark,
   and growing three credentials until they match two drawings plus their
   captions would make the logos the loudest thing in a strip whose drawn
   facts are its argument. Icon beside label does not fit the cell (a 111px
   slot, a gap and a two-line label need about 225px, and at 1024 a cell is
   186 wide, 146 inside its padding: 928 / 5, less 20 a side). My call, from R12 and DL 14 (one line of five); reversible by
   raising the three logo fractions below. */
.trust__badge { grid-template-columns: minmax(0, 1fr); }
.trust__badge .drawing--badge { --badge: calc(var(--mark-slot) * 1.04); width: var(--badge); max-width: none; height: var(--badge); justify-self: unsafe center; }
/* The two translates that centred the hairline badges' ink on the mark line
   are gone (2026-09-28, the mark-language redraw): each badge's ink is now
   drawn centred in its own 120 box (drawings/marks.mjs, the two "ink spans"
   notes), so the box centre is the ink centre and no correction is owed. The
   years badge keeps its wider slot, its level being a wide thing. */
.trust__badge--years .drawing--badge { --badge: calc(var(--mark-slot) * 1.39); }
/* THE CAPTIONS STAY UNDER THEIR DRAWINGS (ethan-lens-4-7 weighed, polish-1
   round 4). The critic asked for HG's hero-strip anatomy, a mark beside a
   two-line label, so all five members share one band of ink instead of two
   captions hanging 34 to 59px below the logos' feet at 1440. It does not fit
   this strip: HG's marks are 20px icons, and these badges are drawn at the
   logos' mass (above, and sibling-evenness-4-1's heavier strokes). The years
   drawing's ink is 159 x 58 at 1440 and 185 x 68 at 1920 in cells with 229
   and 277px inside their padding, so a 95px caption beside it needs 268 and
   294px; at 1024 the cell has 146px for 138px of ink alone. Beside would mean
   shrinking the badges back to the icon weight the round-2 and round-4 mass
   passes removed. Read as each .trust li's drawing getBBox and caption Range
   in Playwright (node static-sites/paraone-residential/measure/trust-badges.mjs). The SOP's own "icon badge"
   is a mark over its words, and the two captions are the strip's only text.
   My call, from R11 and R56 weighed against DL 13 (the badges are drawn);
   nothing to reverse. */
/* (.icon--badge, a 40px gold-deep Lucide badge, left in round 5: no element
   has carried the class since DL 13 drew the badges; it was dead CSS naming
   the retired hairline gold.) */
.trust__label {
  font-family: var(--display);
  font-weight: 700;
  font-size: 16px;
  line-height: 1.25;
  color: var(--ink);
  text-wrap: balance;
}

/* ---- 5. our work: eight frames, black shoulders ---------------------- */
/* THE GRID RUNS THE CHROME TRACK; THE HEAD AND THE BUTTON DO NOT. Round 3 put
   the whole band on .container--wide (R36, the showcase image band runs
   wide), and its heading and button went with the grid, so above 1776 the
   reader's left edge stepped out and back: band heads at 168, 168, 48, 168,
   168 down the page at 1920 and the two band-foot buttons at 168 and 48
   (sibling-evenness-4-6, ethan-lens-4-6). R36 names the image band as what
   runs wide and says reading content holds its track, and a heading and a
   button are reading furniture. The inset is the container's own: half of
   whatever the wide track exceeds the 1680 container by (padding percentages
   resolve against the track), zero at 1776 and under. Measured as each
   .band__title's and .band__foot .btn's Range left at 1440 and 1920
   (measure/round4-layout.mjs headX, footBtnX). My call, from R36, R2 and
   R11, striking DL 7's and 18's derived "head and button with the grid"
   (reported; that file is another partition's); reversible by deleting this
   rule. */
#work .band__head,
#work .band__foot { padding-inline: max(0px, (100% - var(--container) + 2 * var(--gutter)) / 2); }
.work {
  margin-top: var(--head-gap);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}
/* THE LEAD FRAME ACROSS TWO COLUMNS, three across and more: eight frames as
   2+1, 3, 3, three full rows. The lead is taken out of flow inside its cell
   so the row's height is its neighbour's 4:3 and the lead crops to fill it,
   rather than the lead's own ratio setting a taller row. The same move the
   phone grid already makes (DL section 15), at the desktop's column count.
   My call, from R11 and R30 (no orphan tile); reversible with the phone
   rule's span and the ninth frame back.
   SEVEN FRAMES SINCE polish-1 round 2 (the duplicate deck is out, see
   index.html): the LAST frame spans two as well, so the grid is 2+1, 3, 1+2,
   three full rows again, the wide frame crossing from left to right down the
   band the way the service rows alternate. The same construction as the
   lead, deployed once more (R17), not a new one. My call, from R11 and R30;
   reversible by deleting the :last-child selectors. */
/* ELEVEN FRAMES SINCE 2026-09-29 (Karan's "add all those photos"): the lead alone spans, so the
   grid is 2+1, 3, 3, 3, four full rows; the last frame's span is off, since eleven plus two spans
   is thirteen cells, an orphan. My call, from R11 and R30; reversible with the :last-child
   selectors, when the count is odd again. */
/* FIFTEEN FRAMES SINCE 2026-09-29 evening (Buddy's three finished-build photos and the slab pour
   freed from About, under Karan's same "add all those photos"): THREE wide frames, the lead, the
   tenth and the fourteenth, so the grid is 2+1, 3, 3, 1+2, 3, 2+1, six full rows, the wide frame
   crossing left, right, left down the band the way the service rows alternate. The same lead
   construction deployed twice more (R17), no new device. At 390 only the lead spans: fifteen plus
   one is sixteen cells, eight rows of two. My call, from R11 and R30; reversible by deleting the
   nth-child(10) and nth-child(14) selectors, when the count changes again. */
@media (min-width: 701px) {
  .work li:is(:nth-child(1), :nth-child(10), :nth-child(14)) { grid-column: span 2; position: relative; }
  .work li:is(:nth-child(1), :nth-child(10), :nth-child(14)) .work__item { position: absolute; inset: 0; }
  .work li:is(:nth-child(1), :nth-child(10), :nth-child(14)) img { height: 100%; aspect-ratio: auto; }
  /* EACH WIDE FRAME IS CROPPED TO ITS SUBJECT, not to its middle (fe-4-11).
     A wide tile is about 2.68:1 at every width from 701 up (894 x 333 at
     1440, 1214 x 453 at 1920, 467 x 173 at 768), cut from a 1200 x 900 file,
     so it shows 448 of the 900 source rows, and centred it took rows 226 to
     674: the crane's jib and the rigging over the module went off the top,
     the empty foreground piles stayed. Keyed by FILE, not by position, so a
     reorder in index.html cannot hand one frame the other's crop.
     work-7, the crane lift: rows about 110 to 558, the hook and slings over
     the module down to the module's foot; the jib's last knuckle is the one
     part the 448 rows cannot also hold. work-1, the cabin: rows about 180 to
     628, the gable with a band of sky over it and the deck under it (centred,
     the roof's peak sat on the tile's top edge). Read off the files at full
     size and off each tile at 1440. My call, from R41 (the tile stays under
     one viewport) and the band's one job, the work shown whole; reversible by
     deleting these two lines. Measured with
     node static-sites/paraone-residential/measure/round4-partition2.mjs work. */
  .work img[src^="assets/img/work-7."] { object-position: 50% 24%; }
  .work img[src^="assets/img/work-1."] { object-position: 50% 40%; }
}
.work__item {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: var(--black);
}
.work__item img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition: filter 160ms ease;
}
@media (hover: hover) { .work__item:hover img { filter: brightness(1.07); } }
/* NO MARK ON A FRAME AT REST, on touch either (round 4,
   stranger-on-a-phone-4-8 proposed an expand icon on the lead frame): a
   photo grid that opens on a tap is the convention every phone gallery a
   reader has used, and a mark over Buddy's lead photograph is a new placement
   of the icon set on the one band whose argument is the frames themselves.
   The SOP's "click to open full size" works (the round-4 critic read the
   lightbox open at 390: a modal dialog, "3 of 7", 52px controls). My call, from R17 and R38; reversible by adding the mark. */
.work__item:focus-visible { outline-offset: -3px; }

/* ---- reviews: CUT (Ethan, 2026-09-28 evening: "cut since sop doesnt ask and he doesnt have afaik") ----------------------------------------------
   The band and its rules are gone; the map card's object it borrowed stays
   where it was born. Reversible from git. */

/* ---- the statement: Eclipse's No.8 dark panel, in gold (round 5) -------
   index.html argues the panel at the site (parity-inventory-5-1). The line is
   the display face at 600 like every H2, in the gold (11.30:1 on black), from
   30px at 390 to 54 at 1920: 1.15 to 1.17 times the section heading (26 and
   46 at those widths), which is Eclipse's own step (its panel line 43.2 over
   a 46.4 H2 is the other side of 1.0; HG's blockquote is 1.0 of its H1).
   Balanced, never capped: the line takes the container (R35) and wraps
   where the width makes it, two lines on a desktop (R34). The attribution
   is the About sign-off's construction exactly, the 28x3 gold bar over the
   words, in paper on black. Nothing is set in two colours: DL 5a's
   signature stays the H1's alone. My call, from R56, R17, R34 and R35;
   reversible by deleting this block and the div in index.html. */
.statement__line p {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(30px, 1.4926rem + 1.5686vw, 54px);
  line-height: 1.05;          /* the section heading's */
  letter-spacing: -0.01em;    /* the h1, h2, h3 rule's */
  color: var(--gold);
  text-wrap: balance;
}
.statement__by { color: var(--paper); }

/* ---- 6. service area: the map band, the card standing on the map ----- */
.map-band { background: var(--paper); }
.map-stage { position: relative; }
/* ~~.area__credit~~ removed 2026-09-29: the credit moved to the footer (.site-footer__data). */

/* THE CARD standing on the map, top right, on paper with a hairline edge.
   NO LIT EDGE: the device is light getting past a DARK mass (DL section 4),
   and this card is paper on a map, not a body on black. It was also the third
   lit edge in one 900px screen at 1440 (its edge ended 58px above the closing
   panel's, which sits 296px above the form card's), against the language's
   own "one per screen", and a sixth placement section 4 never named. My call,
   from DL section 4 and R50 (a device carries its ground); reversible by
   restoring the ::before rule this replaces. */
.map-card {
  position: absolute;
  top: clamp(32px, 1.4902rem + 2.0915vw, 64px);
  right: max(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)));
  width: min(440px, calc(100% - 2 * var(--gutter)));
  padding: 36px 36px 32px;
  background: var(--paper);
  color: var(--body);
  border: 1px solid var(--rule-light);
  z-index: 1;
}
/* The card's eyebrow keeps the shared 18px to its title (it was 12, the one
   eyebrow of seven set tighter, R11). The title sits a step under the section
   heading because the card is a smaller object: 26px to 36px. My call, from
   R11 and the H2 retune. */
.map-card__title { font-size: clamp(26px, 1.3125rem + 0.781vw, 36px); }
.map-card .link-cta { margin-top: 24px; }
/* The places as ruled rows in the card, one column, seven rows. */
.places {
  margin-top: 24px;
  border-top: 1px solid var(--rule-light);
}
.places li {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 46px;
  border-bottom: 1px solid var(--rule-light);
  cursor: default;
  transition: color 140ms ease;
  font-family: var(--display);
  font-weight: 600;
  font-size: 17px;
  color: var(--ink);
}
/* Round 5 (ethan-lens-5-1, the token comment): the pin is a line glyph on
   paper, so ink; and the row's hover was the NAME going gold-deep, a word in
   the non-text gold (DL 2: "never a word"). The hover is now the nav's own
   device, the gold rule drawing under the word (.nav__link::after), at the
   bar's 3px because it stands on paper. The map plane still lights with it.
   My call, from R17, R25 and R61; reversible by restoring the two gold-deep
   lines and deleting the ::after pair.
   THE PINS ARE GONE (Ethan, 2026-09-28 evening: "remove the location pins").
   Seven identical map-pin glyphs said nothing a ruled row of place names
   does not; the rows keep their rule, their hover and their map plane. */
.places li span { position: relative; }
.places li span::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 3px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 260ms cubic-bezier(0.2, 0.6, 0.2, 1);
}
@media (hover: hover) { .places li:hover span::after { transform: none; } }

/* THE OVERLAY: pointer-transparent, one plane per place, the group moved to
   the frame's centre where the projected points are measured from. A plane is
   a wash of the gold inside ~~a gold-deep outline~~ its own full-strength
   outline (round 5: one gold on the band, the outline 2px so it holds by
   mass; the token comment has the ruling), hidden until the row of the
   same position is under the pointer; on a touch screen (no hover) every
   plane shows faintly, because a highlight nobody can trigger is no
   highlight. */
.map-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  color: var(--gold);
  pointer-events: none;
}
.map-overlay-centre { transform: translate(50%, 50%); }
/* THE SERVICE AREA IS ON THE MAP AT REST, on every device: all seven planes
   faint (the touch state), and a row under the pointer ISOLATES its plane,
   the others going out. It was every plane at opacity 0 until a row was
   hovered, so on a desktop the band at rest was a plain map of Northland with
   nothing saying where he works (read as computed opacity of .map-area, 7 of
   7 at 0, at 1440 after the embed mounted). My call, from R30 (a hover-only
   device needs a resting equivalent) and R29; reversible by setting the rest
   opacity back to 0 behind (hover: hover).
   AND AT REST THE AREA IS THE LOUDEST THING ON THE MAP (polish-1 round 2):
   it rested at 0.55 x a 0.22 fill, a 12 percent gold wash, so at 1920 the
   band's answer to "do you cover me" was a faint outline in a field that
   also showed Auckland. Now 0.85 x 0.3, a 26 percent wash with the outline
   near full strength; a hovered row still isolates its plane (the others go
   to 0, it goes to 1). The embed's zoom cannot grow the region (index.html
   has why), so its weight does the work. My call, from R29 and R30
   (ethan-lens-2-8, comparative-mass-2-3); reversible by restoring 0.55 and
   0.22. */
.map-area { opacity: 0.85; transition: opacity 200ms ease; }
.map-area path { fill: var(--gold); fill-opacity: 0.3; stroke: currentColor; stroke-width: 2; stroke-linejoin: round; }
@media (hover: hover) {
  .map-stage:has(.places li:hover) .map-area { opacity: 0; }
  .map-stage:has(.places li:nth-child(1):hover) .map-area:nth-child(1),
  .map-stage:has(.places li:nth-child(2):hover) .map-area:nth-child(2),
  .map-stage:has(.places li:nth-child(3):hover) .map-area:nth-child(3),
  .map-stage:has(.places li:nth-child(4):hover) .map-area:nth-child(4),
  .map-stage:has(.places li:nth-child(5):hover) .map-area:nth-child(5),
  .map-stage:has(.places li:nth-child(6):hover) .map-area:nth-child(6),
  .map-stage:has(.places li:nth-child(7):hover) .map-area:nth-child(7) { opacity: 1; }
}

/* THE MAP, bled to both edges (the first day's "bleeds on three sides", map
   right and list left, was replaced by section 14's map band), in colour taken down a step so the gold planes
   are the brightest thing on it. */
/* Tall enough that the card never runs off its foot: the card measures about
   630px at desktop, plus its offset from the top. */
.area__map {
  position: relative;
  height: clamp(740px, 44.3382rem + 7.8431vw, 860px);
  /* Paper before the embed mounts: it was #e6e4df, a grey outside the two
     grounds (DL section 2, "no third value"), visible in any frame captured
     before the observer fired. My call, from DL section 2. */
  background: var(--paper);
}
.area__map iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  filter: saturate(0.55) contrast(1.02);
}
.area__map-facade {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
.area__map-mark { width: 56px; height: 56px; color: var(--ink); opacity: 0.6; }   /* a line glyph on paper: ink (round 5, token comment) */

/* WHERE THE CARD STANDS ON THE MAP (901 and up), TWO THINGS ARE DERIVED.
   1. THE MAP IS AS TALL AS THE CARD PLUS AN EQUAL INSET ABOVE AND BELOW (R53,
      the height a consequence). The map was a set clamp, 740 to 860px, and the
      card, content-height (about 630) and pinned to the top, so the two heights
      left their difference at the foot: card to map top 54 and 64px, card to
      map foot 139 and 158px at 1440 and 1920. The stage is now one grid cell
      that the map and the card share; the card sits at the top right with its
      inset as margin, so the cell, and the map stretched across it, is the
      card's height plus the same inset twice. Measured as the card's top and
      bottom against .area__map's in Playwright at 1024, 1440 and 1920: equal.
   2. THE MAP BAND ENDS ON PAPER. With the map to the band's foot, the closing
      panel's lit edge (3px of gold, 6px of the light ground, then the black,
      DL section 4) sat inside the last 9px of the embed, drawn over Google's
      attribution strip, and its "light ground" was map tiles. A paper foot
      under the map puts the device back on its own ground (R50) and clears the
      third party's UI (R30: never fight it). Below 901 the band already ends on
      the card's paper. Measured as #contact's top less .area__map's bottom:
      0 at 1024, 1440 and 1920 before, ~~32 now~~ 15 since polish-1 round 3:
      32 left 23px of bare paper between the map's foot and the gold, so the
      bled field read as a picture set on paper 38px above the close
      (stranger-on-a-phone-3-6). The paper is now exactly the device's: 6px,
      the 3px gold, 6px, the black, the gold centred in its own light. The
      critic's zero would have put the gold back on Google's attribution
      strip, the defect this paper foot exists to remove. My call, from R50
      and R11; reversible by writing 32px back.
   My call, from R53, R50, R30 and R11 (ethan-lens-2-1, fe-2-1,
   sibling-evenness-2-9); reversible by deleting this query.
   3. THE INSET CLEARS GOOGLE'S CORNER CONTROL (fe-4-10, polish-1 round 4).
      The embed sets its fullscreen control at the map's bottom right, a 40px
      disc 10px in from the right edge and 24px up from the foot (standing on
      the attribution strip), so it owns the last 64px of the map's foot and
      50px of its right edge. The card's right edge is the container's, 48px
      in from the glass from 901 to about 1776, and its fluid inset (43 at
      901, 45 at 1024, 54 at 1440, 56 at 1512) left its bottom right corner ON
      the control: card 952..1392 x 135..765 against the control at about
      1390..1430 x 755..795 in a 1440 x 900 viewport. The inset now has a
      floor of the control's 64px plus 16 of air, 80, top and bottom alike so
      the card still stands evenly inset (R11): 16px between the card's foot
      and the control's top from 901 up, and the map 52px taller at 1440
      (790). At 1920 the card clears the control sideways as well (right
      edge 1752 against 1870). Read off shots at 1024 x 768, 1440 x 900,
      1512 x 860 and 1920 x 1080 with the map mounted
      (node static-sites/paraone-residential/measure/round4-partition2.mjs map
      from the worktree root, which saves the shots to the OS temp dir). My call,
      from R30 (never fight a third party's own UI) and R11; reversible by
      deleting the max(). */
@media (min-width: 901px) {
  .map-band { padding-bottom: 15px; }
  .map-stage { display: grid; }
  .area__map,
  .map-card { grid-area: 1 / 1; }
  .area__map { height: auto; }
  .map-card {
    position: relative;
    top: auto;
    right: auto;
    justify-self: end;
    align-self: start;
    margin-block: max(80px, clamp(32px, 1.4902rem + 2.0915vw, 64px));
    margin-inline-end: max(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)));
  }
}

/* ---- 7 + 8. the closing panel: contact form and footer, one field ----- */
.close {
  background: var(--paper);   /* was black; 2026-09-28 evening, Ethan: "make all sections light except for the statement, nav and footer" */
  color: var(--body);
  /* Top only: the footer band is this section's last child, and a bottom pad
     here painted a band of paper UNDER the black footer at the page's end
     (Ethan, 2026-09-28 evening: "footer has random white bar"). The gap the
     footer's lit edge needs above it comes from .contact's margin below. */
  padding-block: var(--band-y) 0;
}
.close .contact { margin-bottom: var(--band-y); }
/* The head and the ways to reach him at the left, the form at the right
   (Ethan, 2026-09-28: "form on the right"). THE HEAD IS IN THE LEFT COLUMN,
   HG Electrical's close (the band head and its rows in one panel beside the
   form), so the form's top is the eyebrow's top and the column beside it
   carries the band's words rather than three short lines. My call, from R56;
   reversible with the markup (index.html, section 7).
   WHAT WAS TAKEN FROM HG'S CLOSE, AND WHAT WAS NOT (parity-inventory-2-6):
   taken, the head in the panel beside the form, and the rows as one ledger in
   one face (.reach__area joined the other two, below). Not taken, a tinted
   card ground (DL 2 measured a tint on #111111 at 1.18:1, invisible; the page
   has two grounds) and a label over each row (PHONE, EMAIL: new words, which
   are Karan's to write, and markup). The lead stays its content's height
   rather than stretched to the form's: stretching it moves the difference
   into a void between the head and the rows (R53). My call.
   AND THE TWO COLUMNS STAY TOP-ALIGNED (sibling-evenness-3-8, for the eye):
   the eyebrow's top is the card's top, which is the join HG sets; centring
   the lead on the card would float the head down off the band's first line.
   The card runs 182px past the lead at 1440 now that the fields breathe
   (.enquiry, fe-2-7), which is the form's length, not slack. My call, from
   R53 and R56; reversible by align-items: center here.
   RE-DECIDED THE SAME WAY in round 4 (parity-inventory-4-2 proposed HG's
   dark info card stretched to the form's height, labelled rows, an hours
   row): the card's ground is a third value (DL 2), its labels and the hours
   are words Karan has not written, and stretching the lead only moves the
   difference into a void (R53). My call, from DL 2, R16 and R53. */
.contact {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(40px, 1.6078rem + 3.6601vw, 96px);
  align-items: start;
}
.contact__lead { display: grid; gap: var(--head-gap); align-content: start; }

/* The form: a paper card ~~on the black~~ on paper since the panel went light, so it
   takes the map card's frame (one rule-light border). RULED fields (below), square corners. */
.enquiry {
  position: relative;
  background: var(--paper);
  color: var(--body);
  border: 1px solid var(--rule-light);
  padding: 36px;
  display: grid;
  gap: var(--field-gap);
  container-type: inline-size;
  container-name: enquiry;
  /* THE SPACE BETWEEN ONE FIELD'S RESERVED ERROR LINE AND THE NEXT LABEL.
     It was 4px, so at rest (fields empty, as every visitor first sees them)
     each label's ink stood 26px under the rule ABOVE it and 51 over its OWN
     (label foot to its rule, at 390 and 1440, five fields of five), twice as
     close to the field it does not belong to. The own-rule side cannot drop
     much: it is the control's box, held at the 44px tap floor (46 before,
     44 now). So the separating side grows: 42px of ink over the label
     against 49 under it. Not reversed, which proximity would need and which
     would cost about 30px a field on a card already 124px taller than the
     column beside it at 1440 (sibling-evenness-3-8); for Ethan's eye whether
     near-parity reads as bound. Read as each label's Range top less the
     previous control's bottom, and the control's bottom less the label's
     Range bottom, in Playwright. My call, from R11 (fe-2-7); reversible by
     setting this to 4px and the control back to 46px, 12px 0 6px, and the
     status margins to 0 and -4px. */
  --field-gap: 20px;
}
/* No lit edge on the form card since polish-1 round 2 (index.html has the
   reason: one per screen, and the card is paper). */
/* THE CARD'S HEAD: the title at the H3 step in the display face, the line under
   it in the reading face at the body's size, quiet. Rides the card's own grid
   gap above the first field like any row. My call, from the card's existing
   voices; reversible by deleting the block in index.html and these rules. */
.enquiry__head { display: grid; gap: 6px; }
.enquiry__title { font-family: var(--display); font-weight: 600; font-size: clamp(20px, 1.125rem + 0.3125vw, 24px); line-height: 1.2; color: var(--ink); text-wrap: balance; }
.enquiry__sub { font-size: 16px; line-height: 1.5; color: var(--quiet); text-wrap: pretty; }
.field { display: flex; flex-direction: column; gap: 2px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--field-gap) 28px; }
/* Two fields side by side only where each is wide enough to hold the longest
   error message on ONE line (257px, "An email address, so we can send the
   quote." at 14px, measured in the page): two columns need 2 x 276 + 28 of
   card, so below 580 the fields stack. That is what lets the error slot below
   reserve one line instead of two. It was 470, which set 221 to 265px fields
   on cards 470 to 560 wide (viewports about 1000 to 1240), where the message
   takes two lines. My call, from R5 and R11; reversible by restoring 470 and
   the 36px slot. */
@container enquiry (max-width: 580px) {
  .field-row { grid-template-columns: minmax(0, 1fr); }
}
.field label {
  font-family: var(--display);
  font-weight: 600;
  font-size: 14px;
  color: var(--ink);
}
.req { color: var(--gold); font-weight: 700; }   /* R25: a label mark in the plain gold, by mass (round 5, token comment) */
.field__optional { font-family: var(--text); font-weight: 400; color: var(--quiet); }

/* RULED LINES, NOT BOXES (Ethan, 2026-09-28: "form underline style please",
   the gavin-voss precedent). A 2px rule to write on; no horizontal padding, so
   the label, the value and the left end of the rule share one edge. Padding is
   bottom-weighted so the value sits ON its line rather than floating above it.
   ~~46px~~ 44px (round 3, fe-2-7, see .enquiry) is the tap floor itself,
   held without a box to give it height. */
.field input,
.field select,
.field textarea {
  width: 100%;
  min-height: 44px;
  padding: 10px 0 6px;
  border: 0;
  border-bottom: 2px solid var(--edge);
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  font-size: 17px;
  transition: border-color 140ms ease;
}
.field textarea { min-height: 96px; padding: 8px 0 8px; resize: vertical; }
/* A failed submit focuses the first bad field, and focus scrolls it to the
   html's scroll-padding (the bar), so the input's top landed flush under the
   bar with its label hidden behind it (input top 72 = the bar's foot at
   768 x 1024). The margin is the label's line plus air, so the reader sees
   which field is empty. My call, from R29 (the boundary frame of the error
   state; stranger-on-a-phone-2-10); reversible by deleting this rule. */
.field input,
.field select,
.field textarea { scroll-margin-top: 40px; }
@media (hover: hover) { .field input:hover, .field select:hover, .field textarea:hover { border-bottom-color: var(--ink); } }
/* Focus on a ruled field: no box (a box around a rule is the boxed field
   back), the rule and its label go gold-deep instead. Measured by check.mjs
   section 7 as a colour change on focus. */
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  outline: none;
  border-bottom-color: var(--gold-deep);
}
.field:has(:focus-visible) label { color: var(--gold-deep); }

/* The native select arrow is the one piece of chrome the palette does not
   reach, so it is drawn: an ink chevron, hard edges.
   THE CHEVRON IS THE ICON SET'S OWN: Lucide 0.469's chevron-down ("m6 9 6 6
   6-6"), stroke 1.75 like every sprite icon, ink (#141414, written out
   because a data URI cannot read a custom property). It was a filled triangle
   drawn with two gradients, the one solid glyph beside 24 stroke icons. The
   CSP allows data: images. My call, from R17 (stranger-on-a-phone-2-13);
   reversible by restoring the two gradients. */
.field select {
  appearance: none;
  padding-right: 32px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23141414' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-position: right 0 center;
  background-size: 18px 18px;
  background-repeat: no-repeat;
}
.field select:invalid { color: var(--quiet); }
.field.is-invalid input, .field.is-invalid select { border-bottom-color: var(--error); }

/* Reserved so a failed submit never moves the card (measured at four widths by
   check.mjs, not asserted): ONE line wherever every field is at least 276px
   wide (the field-row query above), TWO lines only on a card too narrow for
   that (content under 290px, a 320 phone).
   THE LABEL BELONGS TO ITS OWN RULE. With two lines reserved everywhere, a
   label stood 42px under the rule above it and 49px over its own (label foot
   to its rule, 1440), so at rest it read as the caption of the field above.
   One line puts it 24px under the rule above, at every width from 390 up. My call,
   from R11 (proximity binds a label to its field, DL 5g); reversible by setting
   the slot back to 36px. */
.field__error { min-height: 18px; font-size: 14px; line-height: 18px; color: var(--error); }
.field--details .field__error { display: none; }

.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* THE BUTTON SITS ONE FIELD-GAP UNDER THE LAST RULE: the field gap (2) plus
   the error slot the details field does not carry (18, or 36 above). It sat
   14px under the textarea's rule against 42px between every other rule and
   the next label. My call, from R11; reversible by restoring margin-top 10. */
.enquiry .btn { margin-top: 20px; }
/* The status line's reserved slot IS the card's bottom padding, so at rest the
   button sits the card's own padding (36, 24 on a phone) above its foot, as
   the first label sits under its head. It was 4 + 12 + 24 + 36 = 76px under
   the button against 36 over the first label. My call, from R11 and R2;
   reversible by restoring padding 36 and margin-top 12. */
.enquiry { padding-bottom: 8px; }
/* Less the field gap, which the grid also puts between the button and this
   slot: the button stays the card's padding over its foot. */
.enquiry__status { min-height: 24px; margin-top: calc(4px - var(--field-gap)); font-size: 15px; color: var(--body); }
@container enquiry (max-width: 290px) {
  .field__error { min-height: 36px; }
  .enquiry .btn { margin-top: 38px; }
}
.enquiry__status.is-error { color: var(--error); }
/* The number in the failure line is a tel: link (app.js). Vertical padding on
   an INLINE box grows its hit area to the 44px floor without growing the line
   box, so a failed send does not move the card. My call. */
.enquiry__status a { font-weight: 600; text-decoration: underline; text-underline-offset: 3px; padding-block: 13px; }
.enquiry.is-sent .enquiry__status { color: var(--ink); font-weight: 600; }

/* The three ways to reach him, at display size, with a mark each, AS RULED
   ROWS: the rule device (DESIGN-LANGUAGE.md 4) on the black, the same
   construction the map card's places use on paper, so the three read as one
   ledger under the head (HG's close sets its contact rows the same way). My
   call, from R56 and R48; reversible by restoring the 18px grid gap and
   deleting the border lines. */
.reach { display: grid; align-content: start; border-top: 1px solid var(--rule-light); }
.reach__line {
  display: flex;
  align-items: center;
  gap: 16px;
  min-height: 44px;
  padding-block: 18px;
  border-bottom: 1px solid var(--rule-light);
}
.reach .icon { width: 26px; height: 26px; color: var(--gold); }
.reach__phone {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(28px, 1.4314rem + 1.3072vw, 48px);
  line-height: 1.1;
  color: var(--ink);
  transition: color 140ms ease;
}
.reach__email {
  font-family: var(--display);
  font-weight: 600;
  /* Slope-intercept from 1024, not 390: the address is one unbreakable word
     in a 5fr column that is about 324px wide at 901, and the 390 slope set it
     at 17.9px there and broke it over two lines (it held one at 16px). Now
     16px to 1024, 18.8 at 1440, 22 at 1920; one line at every width from 390
     up (measure/tokens.mjs, emailLines). My call. */
  font-size: clamp(16px, 0.5714rem + 0.6696vw, 22px);
  color: var(--ink);
  overflow-wrap: anywhere;
}
/* THE LEDGER'S TWO LINKS CARRY THE LINK MARK AT REST; its third row, the area,
   does not. The three rows were one construction (a gold mark, Montserrat 600
   or 700, a ruled row) and at rest the only difference between the email, a
   mailto: link, and "Bay of Islands and surrounds", a plain line, was white
   against bone, 1.27:1 by the WCAG formula; on a phone, with no hover, the
   area read as tappable and the email no more a link than it
   (stranger-on-a-phone-4-7). The mark is the page's own quiet link rule, the
   footer credit's (.site-footer__credit: 1px, offset, half strength), in
   white at a third here because the words are white; it goes gold with the
   words under the pointer. Read as computed text-decoration-line on the
   three rows' spans in Playwright at 390. My call, from R42 and R61;
   reversible by deleting these two rules. */
.reach__phone span,
.reach__email span {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  text-decoration-color: rgba(20, 20, 20, 0.34);
  transition: text-decoration-color 140ms ease;
}
@media (hover: hover) { .reach__phone:hover span, .reach__email:hover span { text-decoration-color: var(--gold); } }
/* The area line is the ledger's third row, so it takes the ledger's voice: the
   display face at 600 and the email's size (it was the reading face at 400,
   the one row of three in another face, with no comment arguing it). Bone, not
   white, is what says it is not a link. My call, from R11 and R9
   (sibling-evenness-2-14); reversible by restoring the reading face. */
.reach__area {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(16px, 0.5714rem + 0.6696vw, 22px);
  color: var(--body);
}
/* THE HOURS ROW'S TWO LINES (Karan, 2026-09-29 01:07: "Monday to Friday, 7am
   to 5pm" with "Open 24/7" underneath, as his Google profile has it). One row
   of the ledger, one mark, the two lines stacked in the words' track; the
   second is the same voice a step down (0.82 of the row's size, 500), because
   it qualifies the first line rather than being a second fact. A column flex,
   not a grid: the ledger's phone tier puts every span in the row in track 3
   (.reach__line span), which a nested grid would read as a column of its own.
   Shared by the ledger row and the drawer's. My call, from R11 (one ledger,
   one voice); reversible by writing the two strings back as one span. */
.hours { display: flex; flex-direction: column; gap: 2px; }
.hours__note { font-size: 0.82em; font-weight: 500; line-height: 1.3; }

/* ---- footer ---------------------------------------------------------- */
/* NO FULL-WIDTH RULE OVER THE FOOTER: every column already carries a rule
   across its own top, so a rule here was a fifth one 57px above four (measured
   at every width from 320 to 2560), a boundary drawn twice. Eclipse Air took
   its own the same way on 2026-09-19 (apps/eclipse-air styles.css, .close-lead:
   "a fifth one an inch above four"). The margin and the column rules are what
   divide the footer from the form. My call, from R4, R40 and R56; reversible
   by restoring the border-top and the padding. */
/* ONE ROW GAP FOR THE FOOTER, used twice: between the footer's rows and above
   the legal bar's rule, so every rule in the footer clears the ink above it by
   the same amount. The legal bar sat 48px under the last column against a
   40px row gap (desktop), 36 (641 to 900) and 28 (phone), so the stacked
   footer's last rule stood 20px further from its column than the column
   rules stood from theirs (box gaps 28, 28, 28 then 48 at 390; 36 then 48 at
   768). Now 28 four times at 390, 36 three times at 768 and 40 twice at 1440
   and 1920. Read as each column's box bottom against the next rule's top in
   Playwright. My call, from R11 (sibling-evenness-3-7, and the same 48
   against 40 on the desktop tier, taken with it); reversible by restoring
   margin-top: 48px on .site-footer__legal. */
/* THE FOOTER BAND: black, the closing panel's old ground, since 2026-09-28 evening, Ethan: "make all sections light except for the statement, nav and footer". */
.foot { background: var(--black); color: var(--bone); padding-block: var(--band-y) 40px; }
.site-footer {
  --foot-row-gap: 40px;
  margin-top: 0;   /* was 96px inside the black panel; the band's own padding carries it now */
}
.site-footer__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr));
  gap: var(--foot-row-gap) 40px;
}
/* ~~The brand column's rule~~ STRUCK 2026-09-28, polish-1 round 3: since the
   brand became a full ROW (below) its column-era border-top ran the whole
   footer, 48 to 1392 at 1440 and 48 to 1872 at 1920, 151px above the four
   column rules: the fifth-rule-over-four this block's own first comment
   removed, back by another route (fe-3-1). The row has no rule, as both
   referents set their logo row; the column heads carry the footer's rules.
   That also retires the one rule-to-ink offset that differed (the lockup's
   ink 14px under its rule against 19 under every head, sibling-evenness-3-9).
   My call, from R4, R40 and R56; reversible by restoring padding-top: 14px
   and border-top here. */
.site-footer__brand { padding-top: 0; }
/* The footer lockup is the name crop at 240px wide (it was the full stacked
   lockup at 34px tall, 109px wide, SMALLER than the header's and carrying the
   tagline in miniature 40px above the SOP's own tagline line). Now the SOP's
   line under it is the only instance of those words, and the two read as the
   lockup, rebuilt at a size where every part is legible. HG's footer lockup
   is 199 x 88 at 1920. My call, from R34 (comparative mass). */
.site-footer__brand .brand__mark { width: 240px; height: auto; }
.site-footer__line {
  margin-top: 16px;
  font-family: var(--display);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mist);
}
/* The column head is the brightest thing in its column (R49). */
.site-footer__head {
  padding-top: 14px;
  border-top: 1px solid var(--rule-dark);
  margin-bottom: 14px;
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper);
}
.site-footer__col li a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  min-width: 44px;   /* "Decks" measured 40 x 44, the one link of 13 under the floor */
  font-size: 16px;
  color: var(--bone);
  transition: color 140ms ease;
}
@media (hover: hover) { .site-footer__col li a:hover { color: var(--gold); } }
/* FIVE COLUMNS since the quick links joined (index.html, R62): the brand,
   Services, Service Area, Quick Links, and Contact at its own width, because
   the email is one unbreakable 230px word that a 1fr track at 1440 (about
   219px) would push out of its column. Under 1240 the brand takes a row of
   its own and the four lists share the next. My call, from R62; reversible
   with the quick links nav.
   THE BRAND IS A ROW AT EVERY WIDTH, both referents' footer construction (HG
   and Eclipse set the logo as a top row over the link columns, never as a
   column of its own; R56). As a 1.4fr column it held a 240px lockup and one
   13px line, about 105px of ink, beside four lists 358px tall: a 452 x 358
   cell at 1920 with about 250px of black under the tagline. The row is the
   construction this footer already used under 1240, now used everywhere.
   ~~The referents' convert slot at the row's right end (the quote control)
   needs a button in the markup and is not built here.~~ BUILT 2026-09-28,
   polish-1 round 4: see THE BRAND ROW CLOSES ON THE ASK below. My call, from R56 and R11
   (parity-inventory-2-3); reversible by restoring the 1.4fr first track
   above 1240.
   ~~and Contact at its own width~~ AMENDED 2026-09-28, polish-1 round 3: a
   max-content track sized the Contact head's rule to the email (242px) while
   the other three shared the rest, 327 against 242 at 1440 and 487 against
   242 at 1920, one head-rule of four at a third less length. Contact is now
   an equal share with the email as its FLOOR, minmax(max-content, 1fr), so
   the four rules are one length wherever the email fits a quarter (about
   1184 and up: 256 four times at 1240, 306 at 1440, 426 at 1920) and Contact
   alone keeps the email's width below that (242 against 189 at 1024, 214 at
   1100), where the one
   unbreakable word decides. Read as each .site-footer__head box in
   Playwright. My call, from R11 (sibling-evenness-3-4); reversible by writing
   max-content back as the fourth track. */
.site-footer__grid { grid-template-columns: repeat(3, minmax(0, 1fr)) minmax(max-content, 1fr); }
.site-footer__brand { grid-column: 1 / -1; }
/* THE BRAND ROW CLOSES ON THE ASK, both referents' footer (Eclipse: "Book a
   Site Assessment" at the row's right end, x 1664 at 1920; HG: "GET A FREE
   QUOTE" at x 1471, with a contact form directly above it, this page's own
   adjacency). The row was the lockup at the left and 1344 to 1824px of bare
   black to its right at 1440 and 1920 (R3). The control is the SOP's own
   GET YOUR FREE QUOTE at the header's GHOST weight, so it reads as chrome,
   not as a sixth solid ask. R57 (a call to action directly above a form is a
   stutter) is weighed and set aside here: this is the page's closing chrome
   under the form, not a band above it, and both referents set it there.
   Stacked (900 and under) the row centres, the lockup and its line then the
   control, so on a phone it sits a clear 24px under the tagline rather than
   against the Send Enquiry button. Measured as the control's box against
   .site-footer__brand in Playwright (node static-sites/paraone-residential/measure/brand-drawer.mjs, brand.ctl):
   none at 390, 768, 1024, 1440 and 1920 before; after, its right edge is the
   row's (976, 1392 and 1872 at 1024, 1440 and 1920) on the row's centre line,
   and it centres under the tagline at 390 and 768 (77 to 313, 270 to 498).
   My call, from R56, R3 and R30 (parity-inventory-3-1, parity-inventory-4-5);
   reversible by deleting .site-footer__cta in index.html and these rules. */
/* STRUCK 2026-09-28, polish-1 round 5 (index.html, THE BRAND ROW): the
   control went, so the row is the lockup over its line and nothing else; the
   cta area and its rules are gone with it. Stacked (900 and under) the pair
   centres, as before. */
.site-footer__brand {
  display: grid;
  grid-template-columns: max-content;
  grid-template-areas: "mark" "line";
  align-items: center;
}
.site-footer__brand .brand__mark { grid-area: mark; }
.site-footer__brand .site-footer__line { grid-area: line; }
@media (max-width: 900px) {
  .site-footer__brand { grid-template-columns: minmax(0, 1fr); justify-items: center; }
}
/* The contact rows' marks: gold on black, the reach rows' construction at
   the footer's size (R48). */
.site-footer__col li a { gap: 10px; }
.site-footer__col li a .icon { width: 18px; height: 18px; color: var(--gold); }

/* THE LEGAL BAR'S MIDDLE LINE IS CENTRED ON INK: three tracks, the outer two
   equal, so the copyright line sits on the container's centre whatever the
   outer items measure. Space-between on items 63 and 230px wide put it 84px
   left of centre at 1440 and 1920 (ink centre 636 against 720, 876 against
   960), a row claiming symmetry and missing it. At 900 and under the three
   cannot hold one row with the middle centred (they need 818px), so they
   stack on equal 44px rows (the credit is a link with a 44px box; the two
   spans were bare 24px lines, and the ink gaps read 7 then 17). My call, from
   R2, R12 and R11; reversible by restoring flex with space-between. */
.site-footer__legal {
  margin-top: var(--foot-row-gap);
  padding-top: 24px;
  border-top: 1px solid var(--rule-dark);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 8px 32px;
  font-size: 15px;
  color: var(--mist);
}
.site-footer__legal > :first-child { justify-self: start; }
.site-footer__legal > :last-child { justify-self: end; }
@media (max-width: 900px) {
  .site-footer__legal { display: flex; flex-direction: column; align-items: flex-start; gap: 0; }
  .site-footer__legal > span { padding-block: 10px; }
}
/* THE CREDIT IS A LINK AT REST, not only under a pointer: it computed exactly
   as the two spans beside it (mist, 15px, 400, no decoration, at 390, 1440
   and 1920), the one control of the 49 in the round-3 critic's census at 1920
   whose rest state equals the prose around it, and its gold hover never
   reaches a phone. Computed after: underline, the rule rgba(184,180,170,0.5). A quiet rule
   under the words, the mist at half strength, is the page's link-as-control
   mark (CONSTRUCTIONS, the note role); the hover takes it to gold with the
   words. My call, from R42 and R61 (ethan-lens-3-7); reversible by deleting
   the four text-decoration lines. */
.site-footer__credit {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--mist);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  text-decoration-color: rgba(184, 180, 170, 0.5);
  transition: color 140ms ease, text-decoration-color 140ms ease;
}
@media (hover: hover) { .site-footer__credit:hover { color: var(--gold); text-decoration-color: var(--gold); } }
/* The map's data credit under the legal row (moved off the map card 2026-09-29): the legal row's
   own mist, a step smaller, since it is a licence notice and not a line anyone reads for itself. */
.site-footer__data { margin-top: 8px; font-size: 13px; color: var(--mist); }
/* THE FOOTER'S HOURS ROW (2026-09-29): the Contact column's links' shape (the 18px gold mark, bone
   at 16px, the 44px row) on a row that is not a link, so the mark sits beside the words rather
   than over them. The mark aligns with the first line, since the row carries two. */
.site-footer__hours {
  display: inline-flex;
  align-items: flex-start;
  gap: 10px;
  min-height: 44px;
  padding-top: 11px;
  font-size: 16px;
  line-height: 1.4;
  color: var(--bone);
}
.site-footer__hours .icon { flex: none; width: 18px; height: 18px; margin-top: 2px; color: var(--gold); }

/* ---- the lightbox ---------------------------------------------------- */
/* svh, not vh (the responsive skill's mobile floor): on a phone 100vh is the
   glass with the URL bar retracted, so the dialog and its image cap ran under
   the bar. On a desktop the two are equal. */
.lightbox {
  width: 100vw;
  height: 100svh;
  max-width: 100vw;
  max-height: 100svh;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--paper);
}
.lightbox::backdrop { background: rgba(17, 17, 17, 0.95); }
.lightbox__img {
  position: absolute;
  inset: 0;
  margin: auto;
  max-width: calc(100vw - 120px);
  max-height: calc(100svh - 96px);
  width: auto;
  height: auto;
  object-fit: contain;
}
.lightbox__close,
.lightbox__nav {
  position: absolute;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  background: rgba(255, 255, 255, 0.08);
  color: var(--paper);
  transition: background-color 140ms ease, color 140ms ease;
}
@media (hover: hover) { .lightbox__close:hover, .lightbox__nav:hover { background: rgba(255, 255, 255, 0.16); color: var(--gold); } }
.lightbox__close { top: 16px; right: 16px; }
.lightbox__nav { top: 50%; transform: translateY(-50%); }
.lightbox__nav--prev { left: 16px; }
.lightbox__nav--next { right: 16px; }
.lightbox .icon { width: 26px; height: 26px; }
.lightbox__count {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 18px;
  text-align: center;
  font-family: var(--display);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.12em;
  color: var(--mist);
}
/* ON A PHONE THE PHOTOGRAPH TAKES THE GLASS'S WIDTH and the controls move to a
   foot row, previous and next either side of the count. The desktop's side
   buttons reserved 60px a side, so a portrait frame opened at 270 of 390px
   and the buttons still covered 8px of it at each edge (prev x16 to 68, image
   from x60). The image now clears both rows: 68px of close button above, 84px
   of foot row below. My call, from R29 (boundary frames) and the responsive
   skill's mobile floor; reversible by deleting this query. */
@media (max-width: 700px) {
  .lightbox__img { max-width: 100vw; max-height: calc(100svh - 168px); }
  .lightbox__nav { top: auto; bottom: 16px; transform: none; }
  .lightbox__count { bottom: 33px; }
}

/* ---- motion: one quiet moment ---------------------------------------- */
.is-armed [data-reveal] { opacity: 0; transform: translateY(14px); }
.is-armed [data-reveal].is-in {
  opacity: 1;
  transform: none;
  transition: opacity 560ms ease-out, transform 560ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .is-armed [data-reveal], .is-armed [data-reveal].is-in { opacity: 1; transform: none; transition: none; }
  .is-armed [data-lit]::before { transform: none; transition: none; }
  /* Scoped by the header so it outranks the drawer's own rules, which come
     later in the file. */
  .site-header .nav, .site-header .nav.is-open,
  html .site-header .nav > *, html .site-header .nav.is-open > * { transition: none; }
}

/* ---- responsive ------------------------------------------------------ */
/* (--band-y: 96px left this query in round 5: the token is fluid, :root.) */

/* THE DRAWER: the list folds under the bar, full width, on the same black,
   rows on hairlines, the primary control last. The bar keeps the number.
   It folds at 1240, not 1000: the bar's one-line width measures 1214px with
   the 44px lockup (1158 with the old 38px one), and between 1001 and 1180
   "Where We Work" and the quote button were already breaking onto two lines
   inside a 72px bar. My call, from R34 (nothing in the chrome wraps by
   accident); reversible by moving this block's query back to 1000px. */
@media (max-width: 1240px) {
  /* The two bar controls wear one costume: the number's ghost box (DL 7) on
     the menu toggle too, so the pair reads as one control set rather than a
     boxed control beside a bare glyph (both centre y 32 at 390, the X bare
     beside the boxed number once the drawer is open). The box is the page's
     existing ghost weight, no new device. My call, from R11 and R17
     (sibling-evenness-2-13); reversible by deleting the border. */
  .nav-toggle { display: inline-flex; border: 1.5px solid rgba(255, 255, 255, 0.72); }
  .site-header__actions { margin-left: auto; }
  .site-header__cta { display: none; }
  .site-header__phone { border: 1.5px solid rgba(255, 255, 255, 0.72); padding: 8px 14px; }
  .site-header__phone .icon { color: var(--paper); }
  /* THE DRAWER IS A SHEET, both referents' construction (Eclipse and HG open
     a full-viewport panel): it runs from the bar to the foot of the glass on
     the same black, so the page under it, and the hero's own GET YOUR FREE
     QUOTE 52px below the drawer's, are covered rather than showing through
     under a 210px strip. It ~~FADES in and out over 180ms, the one element and
     one property~~ (round 3: the ground then the contents, 120ms each, below),
     which is R44's cheap CSS tier; a display toggle has no
     frames to animate, so it is visibility plus opacity, and the visibility
     flip waits for the fade on the way out. Measured before: identical frames
     at 0, 30, 60 and 200ms after the tap, in both directions. The page under
     it does not scroll while it is open (the html rule below). dvh, not svh:
     this is a fixed overlay with nothing flowing under it (the responsive
     skill, section 6). My call, from R29 (boundary frames), R37 and R44;
     reversible by restoring display: none / flex and the auto height.
     KEPT FULL HEIGHT in round 3 (stranger-on-a-phone-3-8 weighed a sheet
     sized to its content, ending at 486 of 844 at 390): a content-height
     sheet is the 210px strip this replaced, with the page and the hero's
     own GET YOUR FREE QUOTE showing under it, and both referents open a
     full-viewport panel. My call, from R56; nothing to reverse. */
  .nav {
    display: flex;
    visibility: hidden;
    opacity: 0;
    position: absolute;
    left: 0;
    right: 0;
    top: var(--nav-h);
    height: calc(100dvh - var(--nav-h));
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 8px var(--gutter) 24px;
    background: var(--black);
    border-top: 1px solid var(--rule-dark);
    border-bottom: 3px solid var(--gold);
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
    transition: opacity 120ms ease 120ms, visibility 0s linear 240ms;
  }
  .nav.is-open { visibility: visible; opacity: 1; transition: opacity 120ms ease, visibility 0s; }
  /* THE GROUND FIRST, THE CONTENTS AFTER (and the reverse on the way out).
     One fade of the whole sheet crossfaded the page under it by construction:
     90ms into the open, at about half opacity, the hero's H1 and its solid
     GET YOUR FREE QUOTE (y 480 to 530 at 390) showed through under the
     drawer's own (y 354 to 405), two quote controls stacked on one frame. The
     sheet now reaches black in 120ms with its contents held at 0, and the
     contents fade in over that black in the next 120; closing runs it
     backwards. The page is only ever dimmed or covered, never overprinted.
     Read as frames at 0, 60, 120, 180 and 260ms after the tap in Playwright
     at 390: the sheet's contents measure opacity 0 on every frame where the
     sheet is below 1. My call, from R29 (the boundary frame) and R44 (the
     CSS tier); reversible by deleting the two child rules and restoring the
     single 180ms fade on .nav. */
  /* Scoped by the header so the fade outranks the children's own colour
     transitions (.btn, .nav__phone), which would otherwise drop it on the
     way out; the colours are carried along. */
  .site-header .nav > * { opacity: 0; transition: opacity 120ms ease, color 140ms ease, background-color 140ms ease, border-color 140ms ease; }
  .site-header .nav.is-open > * { opacity: 1; transition: opacity 120ms ease 120ms, color 140ms ease, background-color 140ms ease, border-color 140ms ease; }
  html:has(.nav.is-open) { overflow: hidden; }
  .nav__list { flex-direction: column; align-items: stretch; gap: 0; }
  .nav__list li { border-bottom: 1px solid var(--rule-dark); }
  .nav__link { min-height: 52px; padding: 0; font-size: 17px; }
  .nav__link::after { display: none; }
  .nav__cta { width: 100%; min-height: 52px; font-size: 15px; }
  /* The number as a line, in the reach rows' voice (display face 700, white,
     the mark in gold, gold under the pointer), at the reach phone's own
     phone size (30px, the 640 tier below), so the number reads the same in
     the drawer as in the close. */
  .nav__phone {
    display: flex;
    align-items: center;
    gap: 14px;
    min-height: 52px;
    margin-top: 12px;
    font-family: var(--display);
    font-weight: 700;
    font-size: 30px;
    line-height: 1.1;
    color: var(--paper);
    transition: color 140ms ease;
  }
  .nav__phone .icon { width: 26px; height: 26px; color: var(--gold); }
  /* THE DRAWER CARRIES THE CLOSE'S OTHER TWO REACH LINES, the email and the
     area, under the number and over the control: HG's drawer at 390x844 ends
     its ink at 618 (a 226px foot, 27 percent of the sheet) on the same rows,
     number and quote, and this sheet ended at 486 (358px bare, 46 percent;
     530px, 56 percent, at 768x1024). Same voice as the reach ledger in the
     close (the display face at 600, the email white because it is a link, the
     area bone because it is not), at the drawer's size; no new words, both
     are the SOP's "beside the form" lines. Measured as the last visible
     child's bottom against the sheet's in Playwright with the drawer open
     (node static-sites/paraone-residential/measure/brand-drawer.mjs, drawer). My call, from R29 and R56
     (comparative-mass-4-6); reversible by deleting the two .nav__reach lines
     in index.html and these rules. */
  .nav__reach {
    display: flex;
    align-items: center;
    gap: 14px;
    min-height: 44px;
    font-family: var(--display);
    font-weight: 600;
    font-size: 17px;
    line-height: 1.25;
    color: var(--paper);
    overflow-wrap: anywhere;
  }
  .nav__phone + .nav__reach { margin-top: -8px; }
  .nav__area { color: var(--bone); }
  .nav__reach .icon { flex: none; width: 22px; height: 22px; margin-inline: 2px; color: var(--gold); }
}
@media (hover: hover) and (max-width: 1240px) { .nav__email:hover { color: var(--gold); } }
@media (hover: hover) and (max-width: 1240px) { .nav__phone:hover { color: var(--gold); } }

@media (max-width: 1000px) {
  .hero__copy { max-width: 84%; }
  .hero__facts { gap: 8px 28px; }
  /* Too narrow for two columns: the side goes under the words and the row turns left, the
     years beside the buttons. */
  .hero__split { flex-direction: column; align-items: stretch; }
  .hero__side { flex-direction: row; align-items: flex-end; justify-content: space-between; text-align: left; }
  .hero__years { align-items: flex-start; }
  .hero__actions { justify-content: flex-start; }
}

/* THE CUT STACKS AT 1180, not 900. Side by side, the body is the copy's height
   plus both band paddings in a 5fr track, so as the track narrows it becomes a
   sliver: h/w 1.63 at 1180, 1.86 at 1100, 1.98 at 1024 (413 x 819, taller than
   the 768 glass) and 2.38 at 901. Above 1180 it is 1.62 at 1181, 1.60 at
   1200, 1.37 at 1440 and 1.09 at 1920, never taller than 856px. Read as
   document.querySelector('.about__photo').getBoundingClientRect() in a
   Playwright page at each width, 768 to 1080 tall. My call,
   from R41 and R53; reversible by moving these rules back into the 900 tier. */
@media (max-width: 1180px) {
  /* The cut stacks: the copy on the gutter, the body edge to edge under it. */
  .cut { grid-template-columns: minmax(0, 1fr); gap: 40px; padding-inline: var(--gutter); }
  .cut__copy, .cut__body { grid-column: 1; }
  .cut__body { margin-inline: calc(-1 * var(--gutter)); }
  /* Stacked, the body is 4:3 until that would outgrow the glass under the bar
     (R41: a content image never exceeds one viewport). At 1024 x 768 it was
     going to be 1024 x 768 at 4:3; it is 1024 x 696. On a portrait phone the
     4:3 is the smaller term and nothing changes. The lit edge moves to its top. */
  .about__photo {
    height: min(75vw, calc(100svh - var(--nav-h)));
    margin-top: 0;
  }
  .about__photo::before { left: 0; right: 0; top: -9px; bottom: auto; width: auto; height: 3px; transform-origin: left center; }
  .is-armed .about__photo[data-lit]::before { transform: scaleX(0); }
}

@media (max-width: 900px) {
  :root { --gutter: 32px; }   /* --band-y is fluid since round 5 (:root) */
  /* Five cells as three logos over two badges, the across-feel kept (R30).
     The DOM order is badge, logo, logo, logo, badge since the bookend
     (2026-09-28 evening), so the two rows are made by `order`, not by source:
     auto-placement honours it. */
  .trust { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .trust__logo { grid-column: span 2; order: 0; }
  /* Two logos, not three, while the LBP cell is off (2026-10-02): each takes a
     half, so the tier is two logos over two badges with nothing bare. */
  .trust:not(:has(.trust__logo--lbp)) .trust__logo { grid-column: span 3; }
  .trust__badge { grid-column: span 3; order: 1; border-top: 1px solid var(--rule-light); }
  .trust__logo, .trust__badge { --mark-slot: 76px; padding: 20px 12px; }
  .trust__logo--nzcb img { max-height: 76px; }
  /* The first day's tablet and phone heights, kept (the desktop fractions
     would take LBP to 48 and ITM to 41 in the 76px slot). */
  .trust__logo--lbp img { max-height: 58px; }
  /* ~~50px~~ 40px, 2026-09-28 polish-1 round 5 partition 1: at 50 the solid
     ITM block weighed 2710 of ink against NZCB 1754 and LBP 1992 at 768
     (measure/trust-ink.mjs), the heaviest mark in the row by half again.
     At 40 (the square root of the weight ratio, 0.8) it reads with them.
     My call, from R11; reversible by restoring 50px. */
  .trust__logo--itm img { max-height: 40px; }
}
/* THE PHONE TIER IS THE 900 TIER'S GRID, NOT A CONTENT-SIZED LOGO LINE
   (2026-09-28 evening, Ethan: "need text under these"). The flex line that
   stood here from polish-1 round 3 (git, this comment's history) sized each
   logo cell to its mark, which cannot hold a caption: NZCB's cell was 45px
   wide. With every cell captioned the three logos take equal thirds again,
   the marks capped by height and, for the wide LBP lockup, by the cell's
   width (the base max-width: 100%), and the badges share the next row in
   halves under a hairline as before. Slot 60px, side padding 4px, captions
   14px so "Licensed Building Practitioners" holds to three lines in a
   ~100px cell. My call, from R30 (credentials across); reversible by
   restoring the flex tier from git. */
@media (max-width: 640px) {
  .trust__logo, .trust__badge { --mark-slot: 60px; padding: 16px 4px; row-gap: 10px; }
  .trust__logo--lbp img { max-height: 48px; }
  .trust__logo--nzcb img { max-height: 60px; }
  .trust__logo--itm img { max-height: 34px; }
  .trust__label { font-size: 14px; }
}

@media (max-width: 900px) {
  /* The card leaves the map and sits under it, full width, on a phone. Once
     it is a single-column stack it CENTRES (R30 names the map band): the
     eyebrow, the heading, each place on its rule, the link and the credit.
     My call, from R30; reversible by deleting the text-align and the three
     justify lines. */
  .map-card { position: static; width: auto; margin: 0 var(--gutter); border-left: 0; border-right: 0; padding: 32px 0 8px; text-align: center; }
  .map-card .eyebrow { justify-content: center; }
  /* A CENTRED LIST IS CENTRED AS ONE BLOCK, its rows left-aligned inside it.
     Centring each row on its own label put the marks on seven different x
     (pins at 119, 152, 139, 130, 137, 130, 137 at 390; reach marks at 85, 43,
     72; the footer's two at 142 and 74), a column of icons that zigzags. Now
     each list is four tracks, the outer two equal 1fr, with every row a
     subgrid: the mark in the second track and the words in the third, which
     is as wide as the longest label. So the group is centred (R30), the marks
     stand on one x (R2, R12), and each row's rule still runs the card's
     width. Measured as each .icon's left in Playwright at 390 and 768: one
     value per list. My call, from R30, R2 and R12 (sibling-evenness-2-2,
     stranger-on-a-phone-2-6); reversible by restoring justify-content: center
     on .places li and .reach__line.
     KEPT FOR THE REACH ROWS in round 3 (stranger-on-a-phone-3-4 proposed
     centring each row on its own ink): the block's ink envelope IS centred
     (the email, the widest row, runs gutter-true), and centring each row
     would put the three marks back on three x, the zigzag round 2 removed.
     The rows are unlike (a number, an address, a place) but they are one
     ledger with one mark column. My call, from R2 and R12; reversible by
     restoring justify-content: center on .reach__line at this tier. */
  /* The words' track may shrink below its longest label (minmax 0), and the
     space between mark and words is the mark's own margin rather than a
     column gap, which would also open between the outer tracks: at 320 three
     16px gaps plus the one-word email pushed the page 10px wider than the
     glass. */
  .places,
  .reach { display: grid; grid-template-columns: 1fr auto minmax(0, max-content) 1fr; column-gap: 0; }
  .places li,
  .reach__line { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; column-gap: 0; }
  .places li .icon,
  .reach__line .icon { grid-column: 2; }
  .places li .icon { margin-inline-end: 12px; }
  .reach__line .icon { margin-inline-end: 16px; }
  .places li span,
  .reach__line span { grid-column: 3; }
  /* THE WORDS START AT THEIR MARK. The card centres its text (above), and the
     span in track 3 inherited that, so every place shorter than the longest
     centred inside the track, away from its pin: pin to word 12, 45, 32, 23,
     30, 23, 30px at 320, 390, 768 and 900 (the marks were on one x, the words
     were not). The reach rows and the footer's contact rows never inherited a
     centre, which is why only this list drifted. Now 12 on all seven rows.
     Measured as the span's Range left less the icon's right, per row, with
     .fixer-scratch/p1r3/m.mjs places. My call, from R2, R11 and R12
     (sibling-evenness-3-1, stranger-on-a-phone-3-1, ethan-lens-3-2);
     reversible by deleting this line. */
  .places li span { text-align: start; }
  .map-band { padding-bottom: var(--band-y); }
  /* STACKED, THE MAP STAYS THE EVENT (DL 5f names it that). At a flat 420px
     the card under it outweighed it: map 390 x 420 against the card's
     350 x 560 at 390 (the card 120 percent of the map's area), 768 x 420
     against 704 x 562 at 768 (123 percent), the seven places each one word
     in a 704px row (comparative-mass-4-5). Now 480px at 390 rising to 560
     by 900: at 768 the map is 768 x 540, larger in area than the card's
     704 x 562, and at 390 the two are about level in area (390 x 480 against
     350 x 560) (the card holds the list the map lights, and a phone
     map past half the glass would push the list a screen down). app.js fits
     the zoom to the stage's own size, so the region grows with it. The
     critic's two-column places at 641 to 900 were not taken: the list's
     centred subgrid (below) holds its marks on one x, and two columns would
     be a second list construction for one tier. Measured as .area__map's and
     .map-card's boxes at 390 and 768 (measure/round4-layout.mjs map).
     My call, from R30 and DL 5f; reversible by writing 420px back. */
  .area__map { height: clamp(480px, 26.131rem + 15.873vw, 560px); }

  /* Stacked, the three ways to reach him centre over the form (R30), and so
     does the head above them in the same column: it was left-set over centred
     rows, one column on two axes, while the map card just above centres its
     whole head. My call, from R30 ("two-up column interiors centre") and R2
     (sibling-evenness-2-3, ethan-lens-2-7, fe-2-6); reversible by deleting
     the two .contact__lead lines. */
  .contact { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  .contact__lead .band__head { text-align: center; }
  .contact__lead .eyebrow { justify-content: center; }
  .site-footer__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 32px; }
  .site-footer { --foot-row-gap: 36px; margin-top: 72px; }
  /* THE FOOTER CENTRES FROM 900, not from 640. Between 641 and 900 it sat
     left-set under the centred reach rows directly above it, so the close
     flipped centre, left, centre down one stack. The centring is the phone's
     own (R30), started where the close stacks. The Contact list is centred as
     one block with its rows left-aligned inside it, so its two marks share an
     x. My call, from R30 and R2 (ethan-lens-2-7); reversible by moving these
     rules back into the 640 query. */
  .site-footer__grid { text-align: center; }
  .site-footer__brand .brand__mark { margin-inline: auto; }
  .site-footer__col ul { display: flex; flex-direction: column; align-items: center; }
  .site-footer__col:not(.site-footer__col--run) ul { width: fit-content; margin-inline: auto; align-items: flex-start; }
  .site-footer__legal { align-items: center; text-align: center; }
}

@media (max-width: 700px) {
  :root { --nav-h: 64px; }
  /* THE PHONE BAR SETS THE LOCKUP AT THE DESKTOP'S 44px, 178 wide, and lets
     it give way only where the row runs out of room. The name's caps are 79
     of the file's 296 rows (26.7 percent, read off the PNG through a canvas),
     so the fixed 36px it had set them at 9.6px, where Eclipse's phone
     wordmark is 19.2px type and HG's name runs 163 wide. The brand is the one
     shrinkable item in the row (the number's box and the toggle are 44px tap
     targets and do not shrink), and the image takes the brand's width, so at
     320 it settles at 152 x 37.5 (caps 10.0) and from about 348 up it is the
     full 178 x 44 (caps 11.7). Nothing is measured into a number here: the
     room is whatever the row leaves. Measured with
     .fixer-scratch/p2r2-bar.mjs at 320, 360, 390 and 700. My call, from R17
     and R34 (parity-inventory-1-4, fe-1-5); reversible by restoring
     `.site-header .brand__mark { height: 36px; }` and deleting the two rules
     below. */
  .site-header .brand { flex: 0 1 auto; min-width: 0; }
  .site-header .brand__mark { width: 178px; max-width: 100%; height: auto; }
  /* NO PHOTOGRAPH-ONLY ZONE ABOVE THE COPY ON A PHONE (parity-inventory-3-2
     proposed one of at least 35 percent). Measured at 390 x 667, the small
     phone the SOP's reader holds: the second hero button ends at y=597, 70px
     above the fold, so any zone over about 70px pushes a call to action
     under it, and 35 percent is about 280. The zone would need the facts out
     of the hero (HG's under-hero strip), which moves markup (index.html's).
     My call, from R53 and the SOP's phone-first reader, over R38's "the
     photo shines"; reversible by raising the top padding here. */
  .hero { min-height: 0; padding-block: 72px 64px; }
  .hero__copy { max-width: 100%; }
  .hero__actions { flex-direction: column; align-items: stretch; }
  /* On a phone the side is a column again, full width: the years as one line (figure and label
     on a shared baseline) over the two full-width buttons. */
  .hero__side { flex-direction: column; align-items: stretch; gap: 22px; }
  .hero__years { flex-direction: row; align-items: baseline; gap: 12px; }
  .hero__years-figure { font-size: 48px; }
  .hero__years-label { margin-top: 0; font-size: 14px; }
  .hero__scrim { --scrim-foot: 0.9; --scrim-mid: 0.8; --scrim-mid-at: 64%; --scrim-top-at: 100%; --scrim-top: 0.6; }
  /* Every solid call to action takes the column on a phone, as the hero's do
     (the two band-foot buttons were content-width, 239px at x=20 under
     350px-wide hero buttons). My call, from R30. */
  .hero__actions .btn,
  .band__foot .btn { width: 100%; }
  /* Two across on a phone. SEVEN frames (polish-1 round 2): the lead runs
     across both columns at its own 4:3 and the other six are three even rows
     of two, so no row is orphaned (with eight there was no span, with nine
     the lead spanned, and the rule follows the count). My call, from R30 and
     R11; reversible by deleting the span line. */
  .work { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px; }
  .work li:first-child { grid-column: span 2; }
  /* KEPT TWO ACROSS in round 3 (comparative-mass-2-6 weighed one across):
     the band is 946px at 390 against Services' 2039, but the lead already
     runs 350 x 263 and one across would set seven frames at that size, a
     band of about 1870px, to fix a ratio rather than a reading. DL 5e's two
     across keeps a frame at least 170px wide, a photograph, not a
     thumbnail. My call, from R30 (N-across keeps its across-feel); nothing
     to reverse. */
  .hero__photo img { object-position: 64% 40%; }
}

@media (max-width: 640px) {
  :root { --gutter: 20px; }   /* --band-y is fluid since round 5 (:root) */
  /* On a phone the column IS the screen, so the headline sizes off the
     viewport: 36.8px at 320 (where "Bay of Islands" still clears the 280px
     column) to 40px at 400 and up. */
  .hero__title { font-size: clamp(36px, 1.5rem + 4vw, 40px); }
  /* Four facts TWO BY TWO, the across-feel kept by shrinking rather than
     stacking; each fact centred in its cell with its mark above the label,
     and a label that needs two lines takes them (R30: "stats go 2x2, not
     1x4", creds "stay across with wrapped labels"). It was one column of four
     left-piled rows. My call, from R30, striking DL 5a's derived "one column
     on a phone"; reversible by restoring the one-column grid. */
  .hero__facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 16px; margin-top: 40px; }
  .hero__facts li { flex-direction: column; justify-content: flex-start; gap: 8px; font-size: 12px; letter-spacing: 0.1em; line-height: 1.35; min-height: 30px; text-align: center; }
  /* The number's ghost box in the bar. These rules name the number, not
     .btn--header, which the drawer's GET YOUR FREE QUOTE also carries: the
     44px here, later in source than the drawer's 52, took that button to 44
     tall against 52 on every other solid button on a phone. It measures
     350 x 52 at 390 now (opened drawer, Playwright). My call, from R11;
     reversible by restoring the three .btn--header selectors. */
  .site-header__phone span { display: none; }
  .site-header__phone .icon { width: 20px; height: 20px; }
  .eyebrow { font-size: 12px; letter-spacing: 0.14em; gap: 12px; }
  .eyebrow::before { margin-top: 6.9px; }
  .btn { min-height: 52px; padding: 10px 22px; font-size: 14px; }
  /* With its digits hidden the number is an icon in a box, and the box is
     the toggle's module: 44 x 44, the icon centred. It kept the text
     button's 14px side padding around a 20px icon and measured 50 x 44
     beside the 44 x 44 menu control, 10px away, two ghost boxes of one
     costume at two widths. Read as both boxes in Playwright at 390. My call,
     from R11 (sibling-evenness-3-6); reversible by restoring padding 8px 14px
     and deleting the width.
     THE DIGITS STAY OUT OF THE PHONE BAR (parity-inventory-3-7): the row
     at 390 is the 178px lockup, 24 of gap, this 44px box, 10, the 44px
     toggle, 300 of 350, and the number with its digits measures 158 wide
     (read at 700), so showing them would take the lockup back to about
     114px, undoing round 2's 178. HG's fixed call bar at the foot of the
     glass would carry them, but it is a device this page does not have and
     DL 4 is a closed registry, so it is Ethan's to add. My call, from R17
     and R34; nothing to reverse. */
  .site-header__phone { min-height: 44px; width: 44px; padding: 0; justify-content: center; font-size: 13px; }
  .band__intro { margin-top: 16px; }
  .band__foot { margin-top: 40px; }
  /* On a phone each row is the drawing BESIDE its words, small, at the left
     of every row: a list you can scan, seven services in about one and a half
     screens. It was the drawing at 220px stacked above one sentence, seven
     posters in 2867px (3.4 screens) with 130px of dead black beside every
     drawing. The side swap is dropped here because a column of text whose
     left edge jumps row to row does not scan. My call, from R30 and R53 (the
     words set the height); reversible by restoring the stacked grid.
     THE DRAWING GIVES WAY UNDER 390, NOT THE TITLE (R28: a container-caused
     wrap is fixed at the container, never by shrinking the type). At a fixed
     88px the title column is what is left, so "Consent-Ready Plans" (237px
     at its 22px floor) fell to "Consent-Ready / Plans" at 375 (231 of
     column), 360 (216) and 340, and "Interior Fit-Outs" (178) as well at 320
     (176), a one-word second line (stranger-on-a-phone-4-4, R34). The track
     is now 88px from 390 and eases to 62px at 360, where the column is 240:
     every title holds one line from 360 up. At 320 no drawing leaves room
     (237 against 280 of row), so Consent-Ready Plans sets two lines there,
     broken at the space as a compound, the hyphenated word kept whole; the
     other six hold one. Measured as each .service__title's Range line count
     at 320, 340, 360, 375 and 390 in Playwright
     (measure/round4-layout.mjs svcTitleLines). The steps' numeral column
     rides the same track (.step). My call, from R28 and R34; reversible by
     writing 88px back here. */
  #services {
    --svc-drawing: clamp(62px, -15.625rem + 86.6667vw, 88px);
    --svc-gap: 16px;
  }
  /* The rows' phone rules were here; the cuts stack by their own tier
     (section 3). The #services tokens above stay for the steps' column. */
  .enquiry { padding: 24px 20px 0; }
  .enquiry__status { margin-top: calc(-1 * var(--field-gap)); }   /* gap + (-gap) + 24: the button sits 24 over the foot, the phone pad */
  .field-row { grid-template-columns: minmax(0, 1fr); }
  .reach__phone { font-size: 30px; }
  /* THE FOOTER'S PHONE SHAPE (R30: brand centred, long lists as centred
     runs, short columns kept short). It was one left-piled column, 1141px
     tall at 390. Now every block centres on the column and the service and
     place lists run as centred lines of words, each word still its own 44px
     link; the words are the SOP's and all of them stay. The runs are divided
     by space, not by a dot: a dot separator measured leading the second and
     third wrapped lines ("· Bathrooms"), which is a stray mark on every wrap.
     Contact keeps its two rows (the email is too wide to sit beside a second
     column in 350). My call, from R30; reversible by restoring the one-column
     grid and deleting the run rules.
     (The centring itself now starts at 900, above.)
     BALANCED RUNS: a wrapping flex row packs each line greedily, so all three
     runs ended on one word alone ("Consent-Ready Plans" under 3 + 3, "Far
     North" under 3, "Contact" under 4 at 390). The words are set inline in
     one block with text-wrap: balance, so the lines come out even (2 + 3 + 2
     and 2 + 2 at 360 and 390, 4 + 3 and 4 at 430 to 640; at 320 the seven
     services cannot pair their last two in 280px, so 2 + 2 + 2 + 1 is the
     floor there) with each word still its own 44px link.
     QUICK LINKS IS NOT IN THE PHONE FOOTER: its five links are the header's
     five, which the drawer carries one tap away on every screen this narrow,
     and R30's phone footer drops the redundant line. It stays from 641 up,
     where the columns pair two by two. The footer measures 852px at 390 (it
     was 1018), .site-footer's box height. My call, from R30 and R11 (sibling-evenness-2-8,
     sibling-evenness-1-12, stranger-on-a-phone-2-4); reversible by deleting
     the display: none line and the three balance lines. */
  .site-footer__grid { grid-template-columns: minmax(0, 1fr); }
  .site-footer { --foot-row-gap: 28px; }
  .site-footer__brand .brand__mark { width: 220px; }
  .site-footer__col[aria-label="Quick links"] { display: none; }
  .site-footer__col--run ul { display: block; text-wrap: balance; }
  .site-footer__col--run li { display: inline; }
  .site-footer__col--run li a { margin-inline: 9px; }
}

/* THE LEDGER'S TWO WORD ROWS HOLD ONE LINE AT THE FLOOR (320, the responsive
   skill's floor). The email is one unbreakable word, and with the token flat
   at 16px below 1024 it ran 262px against the 238 the stacked row leaves it
   at 320 (280 of column, less the 26px mark and its 16px margin), so
   overflow-wrap: anywhere broke it mid-word; "Bay of Islands and surrounds"
   wrapped beside it, the subgrid track being the email's width. Both rows
   take the same size (they are one ledger, sibling-evenness-2-14): 14px at
   320 rising to the token's 16 at 390, slope-intercept. One line each at
   320, 340, 360 and 375 after (Range line count of each row's words in
   Playwright); nothing changes from 390 up. My call, from R15 and R34
   (fe-3-5, and the area row beside it, taken with it); reversible by
   deleting this query. */
@media (max-width: 389px) {
  .reach__email,
  .reach__area { font-size: clamp(14px, 0.3036rem + 2.8571vw, 16px); }
}
