/* Built by scripts/build.mjs from css/styles.css's own @import order.
   Do not hand-edit: edit the source files it lists below and rebuild. */

/* ── tokens/fonts.css ── */
/* Webfonts. NO LOCAL BINARIES EXIST for this brand — the source handoff loaded
   all three faces from Google Fonts, so this system does the same. If licensed
   files are ever bought, replace this @import with @font-face rules pointing at
   assets/fonts/ and keep the family names identical.

   Fraunces is variable: opsz 9..144, wght 300..900, SOFT 0..100, WONK 0..1. The
   system runs it SOFTENED (--disp-vf) with the wonk off, optical sizing auto.
   Inter Tight keeps 400/500/600; JetBrains Mono keeps 400/500. */


/* ── tokens/colors.css ── */
/* Colour. Warm throughout — THERE IS NO GREY IN THIS SYSTEM. The warmth comes
   from the neutrals, never from a warm accent. Contrast ratios are the measured
   values carried from the source handoff. */
:root{
  /* surfaces */
  --paper:   #f7f3ea;   /* page ground */
  --raised:  #fdfbf6;   /* panels lifted off the page */
  --sage:    #e9eee0;   /* large quiet surfaces */
  --edge:    #cfd9c1;   /* hairlines, panel edges on paper */
  --forest:  #2b4232;   /* the signature dark surface */
  --deepest: #1d2a1e;   /* deepest ink, frames, closing rules. Never pure black. */

  /* brand */
  --lime:      #b6cd65;  /* MARKER ONLY — 1.76:1 on white. Never text on light. */
  --brandtext: #5e6b28;  /* the brand speaking in text on light — 5.82:1 */
  --head:      #3a5642;  /* headings on paper — 7.32:1 */

  /* on dark */
  --on-dark:       #f7f3ea;  /* 9.84:1 on forest */
  --on-dark-quiet: #c6d2c0;  /* 6.94:1 on forest */

  /* body prose on light — slightly softer than --deepest */
  --prose: #33403a;

  /* ── semantic aliases ────────────────────────────────────────────────── */
  --surface-page:    var(--paper);
  --surface-panel:   var(--raised);
  --surface-quiet:   var(--sage);
  --surface-deep:    var(--forest);
  --surface-deepest: var(--deepest);
  --hairline:        var(--edge);

  --text-heading:     var(--head);
  --text-body:        var(--prose);
  --text-strong:      var(--deepest);
  --text-brand:       var(--brandtext);   /* also the link colour */
  --text-on-deep:     var(--on-dark);
  --text-on-deep-quiet: var(--on-dark-quiet);

  --marker:      var(--lime);             /* fill, rule, spine, swipe, focus halo */
  --marker-halo: rgba(182,205,101,.55);   /* the focus halo */

  --link:       var(--brandtext);
  --link-hover: var(--head);
  --link-on-deep: var(--paper);
}

/* ── tokens/typography.css ── */
/* Type. Display serif + neutral sans + a mono RECORD LAYER.

   The record layer is not decoration. This business's real differentiator is
   documentation — it issues a hazardous-waste consignment note and customers
   praise that unprompted — so prices, postcodes, phone numbers, counts, dates
   and references are set in tabular mono. The typography says: this business
   keeps paperwork.

   Body 16px minimum. Below 16px iOS Safari zooms the viewport on input focus
   and the visitor has to pinch back out. */
:root{
  --disp: "Fraunces", Georgia, "Times New Roman", serif; /* @kind font */
  --body: "Inter Tight", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; /* @kind font */
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; /* @kind font */

  --font-display: var(--disp); /* @kind font */
  --font-body:    var(--body); /* @kind font */
  --font-record:  var(--mono); /* @kind font */

  /* Fraunces variable-axis setting, inherited from the root so every use of
     --disp gets it. SOFT rounds the terminals; WONK stays 0 — the wonky
     alternates are too much personality for a hazard record. Unknown axes are
     ignored by Inter Tight and JetBrains Mono, so this is safe globally. */
  --disp-vf: "SOFT" 60, "WONK" 0; /* @kind font */

  /* weights — Fraunces is variable 300..900, so 500 is a real weight here */
  --w-display: 400; /* @kind font */
  --w-body:    400; /* @kind font */
  --w-medium:  500;   /* @kind font */ /* controls, emphasis */
  --w-semi:    600;   /* @kind font */ /* doc headings only */

  /* fluid display sizes, as used on the reference page */
  --t-h1:    clamp(2.15rem, 1.15rem + 4.4vw, 4.4rem); /* @kind spacing */
  --t-h2:    clamp(1.6rem, 3.4vw, 2.4rem); /* @kind font */
  --t-h3:    1.0625rem; /* @kind font */
  --t-stand: clamp(1rem, .98rem + .2vw, 1.125rem); /* @kind spacing */
  --t-body:  1rem;      /* @kind font */ /* 16px floor */
  --t-small: .9375rem; /* @kind font */
  --t-rec:   .8125rem;  /* @kind font */ /* record layer default */
  --t-eyebrow: .6875rem; /* @kind font */

  --lh-display: 1.14; /* @kind font */
  --lh-body:    1.55; /* @kind font */
  --ls-display: -.018em; /* @kind font */
  --ls-eyebrow: .14em;   /* @kind font */ /* uppercase mono STATE labels only */

  /* The state-label recipe. For state and field names only (Step 2 of 4, Call,
     Yard, Caution) — never as a kicker stacked above a heading. A section label
     goes in the seam instead: sentence-case mono at --t-rec, untracked. */
  --eyebrow-font: var(--w-body) var(--t-eyebrow)/1.2 var(--mono); /* @kind font */
}

/* ── tokens/shape.css ── */
/* Shape. Two radii and nothing else: 16px for anything page-sized, 12px for
   anything you tap. Sections are separated by whitespace and hairlines — never
   by boxes and drop shadows.

   The spacing scale below is DERIVED from the values used on the reference page
   (there was no named scale in the source handoff); --frame and --gut are the
   source's own tokens and are authoritative. */
:root{
  --r:      16px;   /* page, bands, panels */
  --r-ctrl: 12px;   /* chips, buttons, small marks */
  --r-hair: 2px;    /* the swipe, focus outline rounding */

  --radius-panel:   var(--r);
  --radius-control: var(--r-ctrl);

  --frame: clamp(8px, 1.1vw, 16px); /* @kind spacing */         /* the deep frame around the page */
  --gut:   clamp(1.15rem, 3.6vw, 3.25rem); /* @kind spacing */  /* horizontal page gutter */

  --sp-1: .35rem;
  --sp-2: .5rem;
  --sp-3: .75rem;
  --sp-4: 1rem;
  --sp-5: 1.25rem;
  --sp-6: 1.75rem;
  --sp-7: 2.5rem;
  --sp-8: 3.5rem;

  /* hairline is a border, not a shadow */
  --border-hair: 1px solid var(--edge); /* @kind other */
  --border-ctrl: 1.5px solid var(--forest); /* @kind other */

  /* tap targets: 44px floor, 48px+ for primary actions */
  --tap-min: 44px; /* @kind spacing */
  --tap-primary: 52px; /* @kind spacing */

  /* the ONE sanctioned shadow: an open dropdown lifting off a dark field.
     Never as page structure. */
  --shadow-overlay: 0 0 0 1px var(--deepest), 0 16px 40px -20px rgba(29,42,30,.5);
}

/* ── tokens/motion.css ── */
/* Motion budget is near zero. This visitor is stressed: motion explains a state
   change and never announces itself. Opacity and small translates only, always
   behind @media (prefers-reduced-motion: no-preference). No parallax, no
   scroll-jacking, no bounce. */
:root{
  --dur:  260ms; /* @kind other */
  --ease: cubic-bezier(.2,.6,.2,1); /* @kind other */   /* never linear */
  --rise: 8px; /* @kind spacing */                        /* the only translate distance in the system */
}

/* ── tokens/fold.css ── */
/* THE FOLD — geometry tokens for the signature component. See patterns/fold.css
   for the component itself and readme.md for the rules.

   drop = how far the fold descends, and the height of anything riding it.
   run  = how far it travels horizontally. ALWAYS 2 x the drop; any other ratio
          is a different shape and breaks the family.

   The four orientations are one curve mirrored, supplied as MASK shapes — fill
   is irrelevant inside a mask, which is the whole point: the surface colour is
   a separate free variable, so any pair of colour fields can fold into each
   other. The edge is two mirrored cubic Beziers on a 96x48 viewBox:
       M0 0  C24 0 24 0 48 24  C72 48 72 48 96 48  L0 48 Z
   Tangent horizontal at both ends, ~45 degrees at the midpoint. Circular arcs
   do NOT do this — an arc turns at a constant rate and reads tighter and
   steeper. Do not freehand it. */
:root{
  --fold-drop: clamp(44px, 4.4vw, 60px); /* @kind spacing */   /* 44px floor = the tap-target floor */
  --fold-run:  calc(var(--fold-drop) * 2); /* @kind spacing */

  --fold-shape-bl: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 96 48' preserveAspectRatio='none'%3E%3Cpath d='M0 0 C24 0 24 0 48 24 C72 48 72 48 96 48 L0 48 Z' fill='%23000'/%3E%3C/svg%3E"); /* @kind other */
  --fold-shape-br: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 96 48' preserveAspectRatio='none'%3E%3Cpath d='M96 0 C72 0 72 0 48 24 C24 48 24 48 0 48 L96 48 Z' fill='%23000'/%3E%3C/svg%3E"); /* @kind other */
  --fold-shape-tl: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 96 48' preserveAspectRatio='none'%3E%3Cpath d='M0 48 C24 48 24 48 48 24 C72 0 72 0 96 0 L0 0 Z' fill='%23000'/%3E%3C/svg%3E"); /* @kind other */
  --fold-shape-tr: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 96 48' preserveAspectRatio='none'%3E%3Cpath d='M96 48 C72 48 72 48 48 24 C24 0 24 0 0 0 L96 0 Z' fill='%23000'/%3E%3C/svg%3E"); /* @kind other */
}

/* ── tokens/hazard.css ── */
/* Smart Asbestos Solutions — HAZARD RAMP (RESERVED)
   ---------------------------------------------------------------------------
   These belong to warning components. They are NOT part of the marketing
   palette and must never be used decoratively. Choosing a green brand bought
   red and amber outright: they are the only warm-hostile hues in the system,
   and that is what makes them mean something. A warm decorative accent
   anywhere on a page neutralises the warning.

   Every hazard component carries FOUR redundant signals — colour, glyph,
   label and spine — so it survives greyscale and every form of colour
   blindness. Colour alone never carries the meaning.

   Each hue is stated twice, once for light context and once for dark, because
   a light island inside a dark band needs something to reset to. Every ratio
   below was measured, not estimated.
   --------------------------------------------------------------------------- */
:root{
/* Each hazard hue is stated TWICE — an -on-light and an -on-dark value —
     and --haz-*-fg / --haz-*-bg are aliases pointing at one of them. .on-ink
     repoints the aliases to -on-dark; anything that reverses back to a light
     surface inside a dark band (the hero form card) repoints them to
     -on-light. Keep both literals addressable, or a light-on-dark island has
     nothing to reset TO and silently keeps the dark value.
     ---------------------------------------------------------------------- */

  /* Information — steel blue. Deliberately not the link colour. */
  --haz-info-on-light: #175a78;  /* 7.58:1 white · 6.85 page bg · 6.68 own tint */
  --haz-info-bg-light: #e9f2f7;
  --haz-info-fg:      var(--haz-info-on-light);
  --haz-info-bg:      var(--haz-info-bg-light);
  --haz-info-edge:    #a9cde0;
  --haz-info-spine:   #1d7099;
  --haz-info-on-dark: #7cc5e8;   /* 7.85:1 on the companion (was 7.42 on ink) */

  /* Caution — amber. Text weight only; the bright amber is a fill. */
  --haz-caution-on-light: #7a4b06; /* 7.40:1 white · 6.69 page bg · 6.68 own tint */
  --haz-caution-bg-light: #fdf2df;
  --haz-caution-fg:      var(--haz-caution-on-light);
  --haz-caution-bg:      var(--haz-caution-bg-light);
  --haz-caution-edge:    #e6c283;
  --haz-caution-spine:   #b87413;
  --haz-caution-fill:    #f2bb63;  /* the companion on this: 8.60:1 */
  --haz-caution-on-dark: #f2bb63;  /* 8.60:1 on the companion (was 8.13 on ink) */

  /* Stop / danger — brick red, not fire-engine red. Authority, not alarm. */
  --haz-danger-on-light: #a11d17;  /* 7.77:1 white · 7.01 page bg · 6.79 own tint */
  --haz-danger-bg-light: #fdecea;
  --haz-danger-fg:      var(--haz-danger-on-light);
  --haz-danger-bg:      var(--haz-danger-bg-light);
  --haz-danger-edge:    #e08a82;
  --haz-danger-spine:   #a11d17;
  --haz-danger-solid:   #8c1913;   /* white on this: 9.28:1 */
  --haz-danger-on-dark: #ff9d95;   /* 7.49:1 on the companion (was 7.09 on ink) */

  /* Clear / success — pine. Blue-green, 80° off the lime so it never reads as
     "brand" and the brand never reads as "safe". */
  --haz-clear-on-light: #0d6547;   /* 7.07:1 white · 6.39 page bg · 6.24 own tint */
  --haz-clear-bg-light: #e6f4ee;
  --haz-clear-fg:      var(--haz-clear-on-light);
  --haz-clear-bg:      var(--haz-clear-bg-light);
  --haz-clear-edge:    #9bcfba;
  --haz-clear-spine:   #10805a;
  --haz-clear-on-dark: #63d3a5;    /* 8.12:1 on the companion (was 7.68 on ink) */

  }

/* ── base/reset.css ── */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;font-optical-sizing:auto;font-variation-settings:var(--disp-vf)}
body{margin:0;background:var(--sage);color:var(--deepest);font-family:var(--body);font-size:16px;line-height:var(--lh-body);-webkit-font-smoothing:antialiased}
a{color:var(--link)}
a:hover{color:var(--link-hover)}
.on-dark a{color:var(--link-on-deep)}
.on-dark a:hover{color:var(--on-dark-quiet)}
/* Visible focus in EVERY context, including inside dark bands. The lime halo is
   the marker doing its job — never lime text. */
:where(a,button,summary,input,select,textarea,[tabindex]):focus-visible{outline:3px solid var(--deepest);outline-offset:2px;border-radius:var(--r-hair);box-shadow:0 0 0 6px var(--marker-halo)}
.on-dark :where(a,button,summary,input,select,textarea,[tabindex]):focus-visible{outline-color:var(--paper)}
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;clip-path:inset(50%);overflow:hidden;white-space:nowrap}
@media (prefers-reduced-motion:no-preference){
.rise{animation:sas-rise var(--dur) var(--ease) both}
.rise-2{animation-delay:.05s}
.rise-3{animation-delay:.1s}
@keyframes sas-rise{from{opacity:0;transform:translateY(var(--rise))}to{opacity:1;transform:none}}
}

/* ── patterns/fold.css ── */
/* THE FOLD — the signature component, class-based because the mask needs
   multi-value longhands. Set --fold-surface to whichever field is doing the
   reaching; the fold always takes the colour of the surface it belongs to,
   never a third colour.

   TWO OVERLAP RULES, both mandatory and both invisible when right. Abutting
   boxes render a hairline at fractional pixel positions and a hairline through
   the fold destroys the illusion of one continuous surface:
     1. HORIZONTAL — the body mask is calc(100% - var(--fold-run) + 1px), so the
        body and the curve OVERLAP by 1px rather than meeting.
     2. VERTICAL — the fold sits at bottom:-1px and is 1px taller, lapping into
        the surface below. The CURVE MASK MUST BE THAT SAME 1px TALLER
        (calc(--fold-drop + 1px)): masked to the bare drop it is anchored to the
        bottom edge, so its flat top lands 1px below the body's top edge and a
        visible 1px step appears where the flat run begins.

   The label MUST NOT WRAP. The component's height IS its drop (plus the 1px lap)
   and the curve is masked to that height; a taller element pushes the top of the
   run-out outside the mask and punches a transparent notch. Two to four words.

   DISCIPLINE: one fold per screen. Never on a hazard surface. */
.fold{position:relative;display:inline-flex;align-items:center;gap:.55rem;margin:0;height:calc(var(--fold-drop) + 1px);padding-block:0 1px;background-color:var(--fold-surface,var(--paper));-webkit-mask-image:var(--fold-shape,var(--fold-shape-bl)),linear-gradient(#000,#000);mask-image:var(--fold-shape,var(--fold-shape-bl)),linear-gradient(#000,#000);-webkit-mask-repeat:no-repeat,no-repeat;mask-repeat:no-repeat,no-repeat;-webkit-mask-size:var(--fold-run) calc(var(--fold-drop) + 1px),calc(100% - var(--fold-run) + 1px) 100%;mask-size:var(--fold-run) calc(var(--fold-drop) + 1px),calc(100% - var(--fold-run) + 1px) 100%;font-family:var(--mono);font-size:clamp(.75rem,2.5vw,.8125rem);letter-spacing:.06em;color:var(--deepest);white-space:nowrap}
.fold--bl{-webkit-mask-image:var(--fold-shape-bl),linear-gradient(#000,#000);mask-image:var(--fold-shape-bl),linear-gradient(#000,#000);position:absolute;left:0;bottom:-1px;z-index:2;padding-inline:var(--gut) calc(var(--fold-run) + .35rem);-webkit-mask-position:right bottom,left bottom;mask-position:right bottom,left bottom}
.fold--br{-webkit-mask-image:var(--fold-shape-br),linear-gradient(#000,#000);mask-image:var(--fold-shape-br),linear-gradient(#000,#000);position:absolute;right:0;bottom:-1px;z-index:2;padding-inline:calc(var(--fold-run) + .35rem) var(--gut);-webkit-mask-position:left bottom,right bottom;mask-position:left bottom,right bottom}
.fold--tl{-webkit-mask-image:var(--fold-shape-tl),linear-gradient(#000,#000);mask-image:var(--fold-shape-tl),linear-gradient(#000,#000);position:absolute;left:0;top:-1px;z-index:2;padding-block:1px 0;padding-inline:var(--gut) calc(var(--fold-run) + .35rem);-webkit-mask-position:right top,left top;mask-position:right top,left top}
.fold--tr{-webkit-mask-image:var(--fold-shape-tr),linear-gradient(#000,#000);mask-image:var(--fold-shape-tr),linear-gradient(#000,#000);position:absolute;right:0;top:-1px;z-index:2;padding-block:1px 0;padding-inline:calc(var(--fold-run) + .35rem) var(--gut);-webkit-mask-position:left top,right top;mask-position:left top,right top}
.fold .dot{width:9px;height:9px;flex:none;border-radius:50%;background:var(--lime);box-shadow:0 0 0 1px rgba(29,42,30,.25)}
.fold b{font-weight:var(--w-medium)}

/* ── patterns/surfaces.css ── */
/* Surfaces. The page is inset inside a deep frame; header and hero are ONE
   continuous forest field with no boundary between them. */
.page{border-radius:var(--r);overflow:hidden;background:var(--paper);display:flex;flex-direction:column}
.frame{background:var(--deepest);padding:var(--frame);border-radius:calc(var(--r) + var(--frame))}
.topfield{position:relative;background:var(--forest);color:var(--on-dark)}
/* The dot lattice: the only texture in the system. Lime at 20% on forest, faded
   out toward the top so it thins as it rises through the header. px stops, not
   %, so the fade always resolves over the header whatever the field's height. */
.topfield::before,.lattice::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;background-image:radial-gradient(circle,rgba(182,205,101,.20) 1.15px,transparent 1.15px);background-size:20px 20px;-webkit-mask-image:linear-gradient(to bottom,rgba(0,0,0,0) 0,rgba(0,0,0,.10) 14px,rgba(0,0,0,.24) 30px,rgba(0,0,0,.42) 48px,rgba(0,0,0,.60) 68px,rgba(0,0,0,.76) 90px,rgba(0,0,0,.89) 115px,rgba(0,0,0,.97) 140px,rgba(0,0,0,1) 165px);mask-image:linear-gradient(to bottom,rgba(0,0,0,0) 0,rgba(0,0,0,.10) 14px,rgba(0,0,0,.24) 30px,rgba(0,0,0,.42) 48px,rgba(0,0,0,.60) 68px,rgba(0,0,0,.76) 90px,rgba(0,0,0,.89) 115px,rgba(0,0,0,.97) 140px,rgba(0,0,0,1) 165px)}
.lattice{position:relative}
/* The highlighter swipe — lime as a fill with dark text on top, never lime text.
   Marks ONE idea per page: the differentiator, never the problem. */
.swipe{background:var(--lime);color:var(--deepest);padding:.02em .1em .06em;margin:0 -.06em;border-radius:var(--r-hair);-webkit-box-decoration-break:clone;box-decoration-break:clone}

/* ── site.css ── */
/* ═══════════════════════════════════════════════════════════════════════════
   Smart Asbestos Solutions — homepage v2, page styles.

   Everything above this file is the design system, unedited. This file is the
   page: it composes the system's surfaces, fold, lattice, record layer, chips,
   buttons and swipe into one homepage, and adds the two things the system does
   not carry — PHOTOGRAPHY and SCROLL MOTION.

   Both are DEPARTURES from the system's original never-list, reopened by the
   22 Aug 2026 client pivot. They are declared in README.md, not applied
   silently.

   Layout families, never the same one twice running:
     1  full-bleed colour field ....... topfield (header + record + fold)
     2  single measure of prose ....... hero
     3  record row .................... hours strip
     4  asymmetric card grid .......... services
     5  two-column with plate ......... collection
     6  ruled row sequence ............ process
     7  full-bleed colour field ....... emergency
     8  tabular record ................ coverage
     9  quiet card row ................ reviews
    10  form panel .................... quote
    11  closing band .................. footer
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── page shell ─────────────────────────────────────────────────────────── */

body { background: var(--deepest); }

.frame  { min-height: 100vh; }
.page   { position: relative; }

.wrap   { max-width: 76rem; margin-inline: auto; }

/* Section rhythm. The page has a beginning, a middle and an end: the first two
   blocks sit tight under the hero, the middle breathes, the closing band is
   flush. Uniform padding across 11 blocks is a texture, not a shape. */
.sec        { padding: clamp(3rem, 7vh, 5.5rem) var(--gut); }
.sec--tight { padding-block: clamp(2rem, 4vh, 3rem); }
.sec--flush { padding-block: clamp(3.5rem, 9vh, 7rem); }
.sec--quiet { background: var(--sage); }
.sec--deep  { background: var(--forest); color: var(--on-dark); }

.seam { border-top: 1px solid var(--edge); }

/* ── shared type ────────────────────────────────────────────────────────── */

.h2 {
  margin: 0;
  font-family: var(--disp); font-weight: 400;
  font-size: clamp(1.6rem, 3.4vw, 2.5rem); line-height: 1.16;
  letter-spacing: -.018em; color: var(--head);
  max-width: 20ch; text-wrap: balance;
}
.on-dark .h2 { color: var(--on-dark); }

.h3 {
  margin: 0;
  font-family: var(--disp); font-weight: 400;
  font-size: 1.25rem; line-height: 1.25; letter-spacing: -.01em;
  color: var(--head);
}
.on-dark .h3 { color: var(--on-dark); }

.lede {
  margin: clamp(.9rem, 2vh, 1.2rem) 0 0;
  font-size: var(--t-stand); line-height: 1.6; color: var(--prose);
  max-width: 56ch;
}
.on-dark .lede { color: var(--on-dark-quiet); }

.note {
  margin: 0; font: 400 var(--t-rec)/1.5 var(--mono);
  color: var(--brandtext); font-variant-numeric: tabular-nums;
}
.on-dark .note { color: var(--on-dark-quiet); }

/* The seam label. Sentence-case mono in the hairline that already separates
   sections — never a tracked kicker stacked above a heading. */
.seamlabel {
  display: flex; align-items: center; gap: .7rem;
  margin: 0 0 clamp(1.1rem, 2.4vh, 1.6rem);
}
.seamlabel > span {
  flex: none; font: 400 var(--t-rec)/1 var(--mono); color: var(--brandtext);
}
.seamlabel::after {
  content: ""; flex: 1 1 auto; block-size: 1px; background: var(--edge);
  transform-origin: left center;
}
.on-dark .seamlabel > span { color: var(--on-dark-quiet); }
.on-dark .seamlabel::after { background: rgba(198,210,192,.32); }

/* ── buttons (the system's Button.jsx, as CSS) ──────────────────────────── */

.btn {
  flex: 1 1 15rem;
  display: inline-flex; align-items: center; justify-content: space-between;
  gap: .9rem; min-height: var(--tap-primary); padding: .8rem 1.15rem;
  border-radius: var(--r-ctrl); border: var(--border-ctrl);
  font-family: var(--body); font-size: 1rem; font-weight: var(--w-medium);
  line-height: 1.2; text-decoration: none; cursor: pointer;
  background: var(--forest); color: var(--paper);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn:hover  { background: var(--deepest); color: var(--paper); }
.btn__arrow { font-size: 1.05em; line-height: 1; }

/* Colours are doubled with an .on-dark prefix ON PURPOSE. reset.css carries
   `.on-dark a { color: var(--link-on-deep) }` at 0-1-1, which outranks a bare
   `.btn--onDark` at 0-1-0 — so inside the emergency band the light button
   rendered paper text on a paper fill, 1:1, completely invisible. The prefixed
   copies are 0-2-0 and win. Do not "tidy" these away as duplicates. */
.btn,               .on-dark .btn               { color: var(--paper); }
.btn:hover,         .on-dark .btn:hover         { color: var(--paper); }

.btn--quiet,        .on-dark .btn--quiet        { background: var(--sage); color: #25332a; }
.btn--quiet:hover,  .on-dark .btn--quiet:hover  { background: var(--edge); color: #25332a; }

.btn--onDark,       .on-dark .btn--onDark       { background: var(--paper); color: var(--deepest); border-color: var(--paper); }
.btn--onDark:hover, .on-dark .btn--onDark:hover { background: var(--on-dark-quiet); color: var(--deepest); }

.btn--ghostOnDark,       .on-dark .btn--ghostOnDark       { background: transparent; color: var(--paper); border-color: var(--on-dark-quiet); }
.btn--ghostOnDark:hover, .on-dark .btn--ghostOnDark:hover { background: rgba(247,243,234,.10); color: var(--paper); }

.actions {
  margin: clamp(1.7rem, 4vh, 2.4rem) 0 0;
  display: flex; flex-wrap: wrap; gap: .75rem; max-width: 34rem;
}

/* ═══ 1 ── the top field ════════════════════════════════════════════════════
   Header and hero-record are ONE continuous field. Four stacked layers, in
   paint order:

     0  __media   the halftone aerial (img/x1_chunky_2x.png)
     1  __scrim   three gradients that buy back legibility
     2  ::before  the system's lime dot lattice (surfaces.css)
     3  content   header, record layer, fold

   THE BLEND. The aerial is already forest-green with pale-green dots, so it
   sits in the palette without treatment — the work is purely tonal. The media
   layer is MASKED to fade out toward the top, so the plate behind the wordmark
   and the nav chips is close to flat --deepest and the halftone only arrives
   once it is past them. The scrim then adds a vertical wash (heavy at the
   header, near-clear across the middle where the image should read) and a
   horizontal wash from the left, so the wordmark side stays the darkest part
   of the field. Measured targets: >=13:1 behind the wordmark, >=7:1 behind the
   record layer. Never tune the image opacity alone — the mask and the scrim
   are what protect the header. */

/* NO `isolation: isolate` here. It creates a stacking context, and that trapped
   the mobile menu overlay inside the top field — it painted under .band, under
   .fold and under the sticky bar. The four layers below are ordered by explicit
   z-index on each child and do not need an isolate to hold. */
.topfield {
  position: relative; overflow: hidden;
  background: var(--forest); color: var(--on-dark);
  min-height: clamp(19rem, 44vh, 27rem);
  display: flex; flex-direction: column;
}

.topfield__media {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: url("../img/x1_chunky_2x.png") 50% 42% / cover no-repeat;
  opacity: .92;
  -webkit-mask-image: linear-gradient(to bottom,
    rgba(0,0,0,0) 0, rgba(0,0,0,.06) 34px, rgba(0,0,0,.28) 72px,
    rgba(0,0,0,.62) 118px, rgba(0,0,0,.88) 170px, #000 220px, #000 100%);
          mask-image: linear-gradient(to bottom,
    rgba(0,0,0,0) 0, rgba(0,0,0,.06) 34px, rgba(0,0,0,.28) 72px,
    rgba(0,0,0,.62) 118px, rgba(0,0,0,.88) 170px, #000 220px, #000 100%);
}

.topfield__scrim {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    /* the header plate — near-opaque, resolving by the time the record starts */
    linear-gradient(180deg,
      rgba(29,42,30,.97) 0, rgba(29,42,30,.93) 44px, rgba(29,42,30,.74) 96px,
      rgba(29,42,30,.40) 156px, rgba(29,42,30,.20) 230px, rgba(29,42,30,.30) 100%),
    /* the wordmark side stays the darkest column */
    linear-gradient(90deg,
      rgba(29,42,30,.66) 0, rgba(29,42,30,.30) 38%, rgba(29,42,30,0) 72%),
    /* a soft plate under the record layer, top-right */
    radial-gradient(120% 90% at 88% 22%, rgba(29,42,30,.52) 0, rgba(29,42,30,0) 62%);
}

/* THE LATTICE IS OFF ON THIS FIELD. The aerial is itself a halftone dot
   screen, so the system's lime lattice laid over it read as two competing dot
   grids. `.lattice` is untouched — the emergency band still carries it, and
   there is no photograph under it there. */
.topfield::before { content: none; }

/* Raise the content above the media, the scrim and the lattice.
   THE FOLD IS DELIBERATELY NOT IN THIS RULE. `.topfield > .fold` is 0-2-0 and
   would beat `.fold--bl`'s own 0-1-0 `position: absolute`, turning the fold
   into a full-width flex item — the curve then lands at the far right of the
   field instead of ~380px in, and the signature shape is gone. It only needs
   the z-index, which is set on its own line below. */
.topfield > .hdr,
.topfield > .band { position: relative; z-index: 3; }
.topfield > .fold { z-index: 3; }

/* While the menu is open the header outranks everything on the page, including
   the sticky bar at 40 — otherwise the bar covers the panel's own actions. */
html.menu-open .topfield > .hdr { z-index: 70; }
html.menu-open .stickybar       { transform: translateY(110%); }

/* ── header ─────────────────────────────────────────────────────────────── */

.hdr {
  display: flex; align-items: center; justify-content: space-between;
  gap: .75rem; padding: .7rem var(--gut);
}

.wordmark {
  display: inline-flex; align-items: center; gap: .6rem;
  min-height: var(--tap-min); text-decoration: none; min-width: 0; flex: 0 1 auto;
}
.wordmark > span { min-width: 0; }
.wordmark svg { inline-size: 30px; block-size: 30px; flex: none; display: block; }
.wordmark__name {
  display: block; font-family: var(--disp); font-size: 1.0625rem;
  letter-spacing: -.01em; color: var(--on-dark);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.wordmark__place {
  display: block; margin-top: 1px;
  font: 400 var(--t-eyebrow)/1.2 var(--mono);
  letter-spacing: var(--ls-eyebrow); text-transform: uppercase;
  color: var(--on-dark-quiet); white-space: nowrap;
}

/* flex: none — the nav never shrinks. Whatever room is short comes off the
   wordmark, which ellipses; a clipped Menu button is unusable, a clipped
   company name is merely shorter. */
.nav      { display: flex; align-items: center; gap: .4rem; flex: none; }
.navpanel ul { display: flex; gap: .4rem; list-style: none; margin: 0; padding: 0; }
/* Above the collapse breakpoint the panel is just a wrapper round the chip row. */
.navpanel__top, .navpanel__foot { display: none; }

.chip {
  display: inline-flex; align-items: center; min-height: var(--tap-min);
  padding: .45rem .8rem; border-radius: var(--r-ctrl);
  border: 1px solid var(--on-dark-quiet); background: var(--deepest);
  color: var(--on-dark); font-size: .9375rem; text-decoration: none;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.chip:hover              { background: var(--head); border-color: var(--on-dark); color: var(--on-dark); }
.chip[aria-current=page] { background: var(--lime); border-color: var(--lime); color: var(--deepest); }
.chip[aria-current=page]:hover { background: var(--lime); color: var(--deepest); }

.navtoggle {
  display: none; align-items: center; gap: .5rem;
  min-height: var(--tap-min); padding: .45rem .85rem;
  border: 1px solid var(--on-dark-quiet); border-radius: var(--r-ctrl);
  background: var(--deepest); color: var(--on-dark);
  font: var(--w-body) .9375rem var(--body); cursor: pointer;
}
.navtoggle span {
  inline-size: 15px; block-size: 1px; background: currentColor;
  box-shadow: 0 5px 0 currentColor, 0 -5px 0 currentColor;
}

/* ── the record band ────────────────────────────────────────────────────── */

.band {
  flex: 1 1 auto;
  display: flex; align-items: flex-start; justify-content: flex-end;
  gap: 1.5rem 2rem; flex-wrap: wrap;
  padding: clamp(1rem, 3.4vh, 2.2rem) var(--gut) clamp(3.6rem, 8vh, 4.8rem);
}

.rec        { text-align: right; margin-left: auto; }
.rec__label {
  margin: 0 0 .15rem; font: 400 var(--t-eyebrow)/1.2 var(--mono);
  letter-spacing: var(--ls-eyebrow); text-transform: uppercase;
  color: var(--on-dark-quiet);
}
.rec__value {
  display: inline-flex; align-items: center; min-height: var(--tap-min);
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: clamp(1.0625rem, 2.8vw, 1.35rem);
  color: var(--paper); text-decoration: none;
  border-bottom: 2px solid var(--lime);
}
.rec__value:hover { color: var(--paper); border-bottom-color: var(--on-dark); }
.rec__note { margin: .2rem 0 0; font-size: .875rem; color: var(--on-dark-quiet); }

/* ═══ 2 ── the hero, on paper ═══════════════════════════════════════════ */

/* Tight to the fold on purpose. The h1 is the first thing on paper and the
   fold's label is the last thing on the field above it, so a large gap here
   reads as two unrelated blocks rather than one continuous top. */
.hero { padding: clamp(1.4rem, 3.4vh, 2.6rem) var(--gut) clamp(2.4rem, 5vh, 3.4rem); }

.hero h1 {
  margin: 0;
  font-family: var(--disp); font-weight: 400;
  font-size: var(--t-h1); line-height: var(--lh-display);
  letter-spacing: var(--ls-display); color: var(--head);
  max-width: 19ch; text-wrap: balance;
}

/* ServicesList — every item carries its own leading dot, INCLUDING the first,
   so a wrapped row starts flush on a dot and rows align down the left. */
/* Set in the BODY face, not the display serif. It sits directly above the
   standfirst, which is the page's only Fraunces paragraph — two serif blocks
   stacked cancelled that out and the list read as a second headline. */
.services {
  list-style: none; margin: clamp(1.3rem, 3vh, 1.9rem) 0 1.1rem; padding: 0;
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem 1.05rem;
  font-family: var(--body); font-weight: var(--w-medium);
  font-size: .9375rem; letter-spacing: .01em; color: var(--brandtext);
}
.services li { display: inline-flex; align-items: center; white-space: nowrap; }
.services li::before {
  content: ""; inline-size: 5px; block-size: 5px; border-radius: 50%;
  background: var(--lime); margin-inline: 0 .5rem; flex: none;
}

.standfirst {
  margin: 0; max-width: 50ch;
  font-family: var(--disp); font-weight: 400;
  font-size: clamp(1.15rem, 1.05rem + .35vw, 1.3rem);
  line-height: 1.38; letter-spacing: -.008em; color: var(--head);
}

/* the small mono trust row under the hero actions */
.trustrow {
  list-style: none; display: flex; flex-wrap: wrap; gap: .4rem 1.4rem;
  margin: clamp(1.4rem, 3vh, 2rem) 0 0; padding: 0;
  font: 400 var(--t-rec)/1.5 var(--mono); color: var(--brandtext);
}
.trustrow li { display: inline-flex; align-items: center; gap: .45rem; }
.trustrow li::before {
  content: ""; inline-size: 5px; block-size: 5px; border-radius: 50%;
  background: var(--lime); flex: none;
}

/* ═══ 3 ── the hours strip ══════════════════════════════════════════════ */

.strip {
  display: grid; gap: 1.4rem 2rem;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}
.strip__k {
  margin: 0 0 .3rem;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: clamp(1.15rem, 1rem + .9vw, 1.6rem);
  letter-spacing: -.01em; color: var(--head);
}
.strip__v { margin: 0; font-size: .9375rem; line-height: 1.45; color: var(--prose); }
.strip > li { list-style: none; border-top: 2px solid var(--lime); padding-top: .7rem; }

/* ═══ 4 ── services grid ════════════════════════════════════════════════ */

.grid {
  display: grid; gap: clamp(.9rem, 2vw, 1.4rem);
  grid-template-columns: repeat(auto-fit, minmax(15.5rem, 1fr));
  margin-top: clamp(1.4rem, 3vh, 2rem);
}

.card {
  display: flex; flex-direction: column;
  background: var(--raised); border: var(--border-hair);
  border-radius: var(--r); overflow: hidden;
}
.card__media {
  position: relative; aspect-ratio: 4 / 3; overflow: hidden;
  background: var(--sage);
}
.card__media img {
  inline-size: 100%; block-size: 100%; object-fit: cover; display: block;
}
.card__body { padding: 1rem 1.1rem 1.2rem; }
.card__body p { margin: .4rem 0 0; font-size: .9375rem; line-height: 1.5; color: var(--prose); }

/* the type-only card that closes the row */
.card--list { background: var(--forest); color: var(--on-dark); border-color: var(--forest); }
.card--list .h3 { color: var(--on-dark); }
.card--list p   { color: var(--on-dark-quiet); }
.card--list .card__body { padding: 1.2rem 1.1rem 1.4rem; }
.card--list ul {
  list-style: none; margin: .8rem 0 0; padding: 0;
  display: grid; gap: .5rem; font-size: .9375rem;
}
.card--list li { display: flex; gap: .55rem; align-items: baseline; }
.card--list li::before {
  content: ""; inline-size: 5px; block-size: 5px; border-radius: 50%;
  background: var(--lime); flex: none; transform: translateY(-.2em);
}

/* PHOTOGRAPHY TREATMENT. Photographs are the one element in this page that did
   not come from the brand, so they are pulled toward it rather than left raw:
   a small desaturation and a warm lift, then a --forest multiply veil at 14%.
   Enough to stop a stock photograph shouting; not enough to look filtered. */
.card__media img,
.plate img { filter: saturate(.82) contrast(1.04) brightness(1.02); }
.card__media::after,
.plate::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: var(--forest); mix-blend-mode: multiply; opacity: .14;
}

/* ═══ 5 ── collection, two-column with a plate ══════════════════════════ */

.split {
  display: grid; gap: clamp(1.6rem, 4vw, 3.2rem);
  grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
  align-items: center;
}
.split--rev > .plate { order: -1; }

.plate {
  position: relative; overflow: hidden; border-radius: var(--r);
  aspect-ratio: 5 / 4; background: var(--sage);
}
.plate img { inline-size: 100%; block-size: 100%; object-fit: cover; display: block; }

.plate__cap {
  position: absolute; left: 0; bottom: 0; z-index: 2; margin: 0;
  padding: .5rem .8rem; max-width: 88%;
  background: var(--deepest); color: var(--on-dark-quiet);
  font: 400 var(--t-eyebrow)/1.4 var(--mono);
  letter-spacing: .04em; border-top-right-radius: var(--r-ctrl);
}

.ticks { list-style: none; margin: clamp(1.2rem, 3vh, 1.7rem) 0 0; padding: 0; display: grid; gap: .7rem; }
.ticks li {
  display: flex; gap: .7rem; align-items: baseline;
  font-size: 1rem; line-height: 1.5; color: var(--prose);
}
.ticks li::before {
  content: ""; inline-size: 7px; block-size: 7px; flex: none;
  border-radius: 50%; background: var(--lime); transform: translateY(-.1em);
  box-shadow: 0 0 0 1px rgba(29,42,30,.22);
}

/* ═══ 6 ── process, ruled row sequence ══════════════════════════════════ */

/* MOBILE FIRST: ruled rows, ordinal in a fixed first column. This is the
   arrangement the client signed off on a phone and it is unchanged. */
.steps {
  list-style: none; margin: clamp(1.4rem, 3vh, 2rem) 0 0; padding: 0;
  border-top: 1px solid var(--edge);
}
.steps > li {
  display: grid; gap: .2rem 1.6rem;
  grid-template-columns: 2.6rem minmax(0, 13ch) minmax(0, 1fr);
  align-items: baseline; padding: 1.1rem 0;
  border-bottom: 1px solid var(--edge);
}

.plate--wide { aspect-ratio: 5 / 4; margin-top: clamp(1.2rem, 3vh, 1.8rem); }
.steps__n {
  font: 500 var(--t-rec)/1.3 var(--mono); font-variant-numeric: tabular-nums;
  color: var(--brandtext);
}
.steps p { margin: 0; font-size: .9375rem; line-height: 1.55; color: var(--prose); }

/* STACKED, not wrapped. Three chips on one line wrapped to three ragged rows
   at 375 and read as debris; stacked full-width they read as a choice. */
.steps__acts {
  grid-column: 3; list-style: none; margin: .8rem 0 0; padding: 0;
  display: grid; gap: .4rem; max-width: 20rem;
}
/* Link buttons: the affordance is the underline and the arrow, not a box. Three
   boxed chips read as three peer CTAs competing with the hero's own pair; as
   links they read as three ways to do one thing. The 44px min-height and the
   padding keep the tap target legal without drawing a control. */
.steps__acts a {
  display: inline-flex; align-items: center; gap: .45rem;
  min-height: var(--tap-min); padding: .3rem 0;
  color: var(--brandtext); font: var(--w-medium) .9375rem/1.3 var(--body);
  text-decoration: underline; text-decoration-color: var(--edge);
  text-underline-offset: .28em; text-decoration-thickness: 1.5px;
  transition: color var(--dur) var(--ease), text-decoration-color var(--dur) var(--ease);
}
.steps__acts a:hover { color: var(--head); text-decoration-color: var(--head); }
.steps__acts a span  { transition: transform var(--dur) var(--ease); }
@media (prefers-reduced-motion: no-preference) {
  .steps__acts a:hover span { transform: translateX(3px); }
}
.steps__acts a:hover { background: var(--sage); border-color: var(--forest); color: var(--head); }

@media (max-width: 40rem) {
  .steps > li { grid-template-columns: 2.6rem minmax(0, 1fr); }
  .steps > li > p { grid-column: 2; margin-top: .25rem; }
  .steps__acts { grid-column: 2; }
}

/* TABLET AND UP: the rail. The steps stop being rows beside a photograph and
   become columns beneath one, so each gets the full measure instead of the
   ~13ch it had when it shared the width. Two-by-two from 44rem, four across
   from 68rem where there is room for a readable column. */
@media (min-width: 44rem) {
  .plate--wide { aspect-ratio: 2.4 / 1; }

  .steps {
    display: grid; gap: 0 clamp(1.2rem, 2.6vw, 2.4rem);
    grid-template-columns: repeat(2, minmax(0, 1fr));
    border-top: 0; margin-top: clamp(1.8rem, 4vh, 2.6rem);
  }
  .steps > li {
    display: block; padding: 1.1rem 0 1.6rem;
    border-top: 2px solid var(--lime); border-bottom: 0;
  }
  .steps__n  { display: block; margin-bottom: .55rem; }
  .steps .h3 { margin-bottom: .35rem; }

  /* Stacked full-width is right on a phone, where three chips on one line
     wrapped to three ragged rows. In a rail column it makes step 01 half again
     as tall as its neighbours and the grid equalises the row, so they go back
     to a compact wrapping row here. */
  .steps__acts   { display: flex; flex-wrap: wrap; gap: .2rem 1.3rem; max-width: none; }
}

@media (min-width: 68rem) {
  .plate--wide { aspect-ratio: 3.1 / 1; }
  .steps { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ═══ 6 ── strengths ═══════════════════════════════════════════════════
   The record-row family, which nothing else on the page now uses. A lime rule
   over each entry rather than a card: six boxes here would be the third card
   grid on one page. */
/* Explicit column counts, not auto-fit. Six items on auto-fit resolved to four
   across at 1440 and left a ragged 4 + 2; 3 + 3 and 2 + 2 + 2 both close. */
.strengths {
  list-style: none; margin: clamp(1.6rem, 3.5vh, 2.4rem) 0 0; padding: 0;
  display: grid; gap: clamp(1.2rem, 2.6vw, 2.2rem);
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 40rem) { .strengths { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 64rem) { .strengths { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.strengths > li { border-top: 2px solid var(--lime); padding-top: .8rem; }
.strengths__k {
  margin: 0 0 .35rem;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: clamp(1.0625rem, 1rem + .5vw, 1.3rem);
  letter-spacing: -.01em; color: var(--head);
}
.strengths__v { margin: 0; font-size: .9375rem; line-height: 1.5; color: var(--prose); }


/* ═══ 8 ── emergency band ═══════════════════════════════════════════════ */

.emergency { position: relative; overflow: hidden; }
.emergency .wrap { position: relative; z-index: 1; }

.bignum {
  display: inline-flex; align-items: center; min-height: var(--tap-primary);
  margin: clamp(1.2rem, 3vh, 1.8rem) 0 0;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: clamp(1.6rem, 1rem + 3.4vw, 2.8rem); letter-spacing: -.02em;
  color: var(--paper); text-decoration: none;
  border-bottom: 3px solid var(--lime);
}
.bignum:hover { color: var(--paper); border-bottom-color: var(--on-dark); }

/* ═══ 8 ── coverage, tabular record ═════════════════════════════════════ */

/* The four blocks in this section (lede, counties, towns, note) were each
   separated by a single margin and read as one dense slab. Counties and towns
   are different KINDS of list, so a hairline divides them and each gets its
   own air. */
.counties {
  list-style: none; margin: clamp(1.6rem, 3.5vh, 2.4rem) 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .55rem;
}
.counties li {
  padding: .4rem .75rem; border-radius: var(--r-ctrl);
  border: 1px solid var(--edge); background: var(--raised);
  font: 500 .9375rem/1.3 var(--body); color: var(--head);
}

.towns {
  list-style: none; padding: clamp(1.5rem, 3.2vh, 2.2rem) 0 0;
  margin: clamp(1.6rem, 3.5vh, 2.4rem) 0 0;
  border-top: 1px solid var(--edge);
  columns: 4 9rem; column-gap: clamp(1.5rem, 3vw, 2.75rem);
  font-family: var(--mono); font-size: var(--t-rec); line-height: 2.3;
  color: var(--prose);
}
.towns li { break-inside: avoid; }

#areas .note { margin-top: clamp(1.5rem, 3.2vh, 2.2rem); }

/* ═══ 9 ── reviews ══════════════════════════════════════════════════════ */

.quotes {
  display: grid; gap: clamp(.9rem, 2vw, 1.3rem);
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  margin-top: clamp(1.4rem, 3vh, 2rem);
}
.quote {
  margin: 0; padding: 1.2rem 1.3rem 1.1rem;
  background: var(--raised); border: var(--border-hair); border-radius: var(--r);
}
.quote blockquote {
  margin: 0; font-family: var(--disp); font-weight: 400;
  font-size: 1.125rem; line-height: 1.45; color: var(--head);
}
.quote figcaption {
  margin-top: .8rem; padding-top: .7rem; border-top: 1px solid var(--edge);
  font: 400 var(--t-eyebrow)/1.4 var(--mono);
  letter-spacing: var(--ls-eyebrow); text-transform: uppercase;
  color: var(--brandtext);
}

/* SPECIMEN. Deliberately ugly, deliberately not tidied away. This block shows
   the layout the live Google reviews will land in; the words in it are not
   anybody's. See README.md. */
.specimen {
  display: inline-block; margin: 0 0 1rem;
  padding: .45rem .7rem; border: 2px dashed var(--haz-caution-spine, #b4671b);
  background: var(--raised);
  font: 500 var(--t-eyebrow)/1.4 var(--mono);
  letter-spacing: var(--ls-eyebrow); text-transform: uppercase;
  color: var(--haz-caution-fg, #8a4d12);
}

/* ═══ 10 ── the quote form ══════════════════════════════════════════════ */

/* At narrow widths the intro stacks above the form. From 66rem the two sit
   side by side — otherwise the whole right half of the tallest block on the
   page is empty, which reads as an unfinished layout rather than a quiet one. */
.quoteblock { display: grid; gap: clamp(1.6rem, 3.5vw, 3.2rem); align-items: start; }
.quoteblock__intro .lede { max-width: 40ch; }

@media (min-width: 66rem) {
  .quoteblock { grid-template-columns: minmax(0, 21rem) minmax(0, 34rem); }
  .quoteblock__intro { position: sticky; top: 2rem; }
}

.formwrap {
  background: var(--raised); border: var(--border-hair); border-radius: var(--r);
  padding: clamp(1.3rem, 3.4vw, 2.2rem);
  max-width: 42rem;
}

.fields { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
.field  { display: flex; flex-direction: column; gap: .35rem; }
.field--wide { grid-column: 1 / -1; }
.field > label { font-size: .9375rem; font-weight: var(--w-medium); color: var(--deepest); }
.field__hint  { font-size: .875rem; color: var(--prose); }

.field input,
.field select,
.field textarea {
  inline-size: 100%; min-height: 48px; padding: .7rem .85rem;
  border-radius: var(--r-ctrl); border: 1.5px solid var(--edge);
  background: var(--raised); color: var(--deepest);
  font: 400 1rem/1.4 var(--body);
}
.field textarea { resize: vertical; min-height: 6rem; }
.field--record input {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  text-transform: uppercase;
}
.field input[type=file] { min-height: 48px; padding: .75rem .6rem; font-size: .9375rem; line-height: 1.5; }

/* The checkbox itself is 24px, so the LABEL is the tap target — it is
   for-associated, so a tap anywhere on it toggles the box. Measured at 42px on
   a wide viewport where the text fits on two lines, which is 2px under the AA
   floor; the padding buys that back without moving anything visually. */
.consent { grid-column: 1 / -1; display: flex; gap: .7rem; align-items: flex-start; }
.consent input { inline-size: 24px; block-size: 24px; min-height: 0; flex: none; margin-top: .55rem; }
.consent label {
  font-size: .875rem; line-height: 1.5; color: var(--prose); font-weight: 400;
  padding-block: .3rem; min-height: var(--tap-min); display: flex; align-items: center;
}

/* ═══ 11 ── footer ══════════════════════════════════════════════════════ */

.foot { background: var(--deepest); color: var(--on-dark); padding: clamp(2.4rem, 5vh, 3.4rem) var(--gut) 1.4rem; }
.foot a { color: var(--paper); }

.foot__cols {
  display: grid; gap: 1.8rem 2.4rem;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
/* The mark at a size the header can never give it. On a phone the header drops
   the mark so the company NAME survives, which is the right trade — this is
   where the mark is actually legible, and it is on every page view. */
.foot__mark { inline-size: 48px; block-size: 48px; display: block; margin-bottom: .9rem; }
.foot h2 {
  margin: 0 0 .7rem; font: 400 var(--t-eyebrow)/1.2 var(--mono);
  letter-spacing: var(--ls-eyebrow); text-transform: uppercase;
  color: var(--on-dark-quiet);
}
.foot ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .45rem; font-size: .9375rem; }
.foot ul:has(a) { gap: 0; }
.foot .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: var(--t-rec); }
/* 44px floor: these were bare inline links at 17-19px tall. */
.foot ul a { display: inline-flex; align-items: center; min-height: var(--tap-min); }

.foot__legal {
  margin: clamp(1.8rem, 4vh, 2.6rem) 0 0; padding-top: 1.1rem;
  border-top: 1px solid rgba(198,210,192,.28);
  font-size: .8125rem; line-height: 1.6; color: var(--on-dark-quiet);
  max-width: 62ch;
}

/* ── the sticky quote bar ───────────────────────────────────────────────── */

/* TWO PEER ACTIONS, always present — including at the very top of the page.
   The enquiry route leads; WhatsApp is the route to a person and is deliberately
   metrically identical, never the smaller or quieter option. */
.stickybar {
  position: fixed; inset: auto 0 0 0; z-index: 40;
  display: flex; align-items: center; gap: .6rem;
  padding: .6rem var(--gut) calc(.6rem + env(safe-area-inset-bottom, 0px));
  background: var(--deepest); border-top: 1px solid rgba(198,210,192,.28);
  color: var(--on-dark);
  transform: translateY(110%);
  transition: transform var(--dur) var(--ease);
}
.stickybar[data-shown=true] { transform: none; }
.stickybar .btn {
  flex: 1 1 auto; justify-content: center; gap: .5rem;
  min-height: var(--tap-min); padding: .6rem .9rem; font-size: .9375rem;
}
/* On a phone the two actions fill the bar. On a wide screen a full-width
   "Enquire now" reads as a page-wide banner rather than a control, so they
   drop to their natural width and sit at the right. */
@media (min-width: 44rem) {
  .stickybar     { justify-content: flex-end; }
  .stickybar .btn { flex: 0 0 auto; min-width: 11rem; }
}
.stickybar .btn--wa,
.on-dark .stickybar .btn--wa {
  flex: 0 1 auto; background: var(--sage); color: #25332a; border-color: var(--sage);
}
.stickybar .btn--wa:hover,
.on-dark .stickybar .btn--wa:hover { background: var(--edge); color: #25332a; }
.stickybar .wa { inline-size: 18px; block-size: 18px; flex: none; }

/* Clearance for the bar. --bar-h is measured and written by app.js; the 68px
   fallback covers the no-JS case, where the bar never renders anyway. */
.frame { padding-bottom: calc(var(--frame) + var(--bar-h, 68px)); }
html:not(.js) .frame { padding-bottom: var(--frame); }

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

/* ═══════════════════════════════════════════════════════════════════════════
   MOTION
   ───────────────────────────────────────────────────────────────────────────
   The client brief for this page asked for content motion on scroll, which
   reverses plan/05-motion.md. It is implemented the safe way round:

   THE FINISHED STATE IS THE BASE CSS. Every start state below lives inside
   @media (prefers-reduced-motion: no-preference). Written the other way, a
   reduced-motion visitor — or anyone whose JS fails — would get a page of
   invisible content. Nothing here can hide anything by failing.

   Everything animates `transform` and `opacity` only. Two IntersectionObservers
   total, no scroll listeners, no parallax. Text is in the DOM at first paint,
   so Ctrl+F and screen readers are unaffected.
   ═══════════════════════════════════════════════════════════════════════════ */

@media screen and (prefers-reduced-motion: no-preference) {

  /* rise — the default. Content lifts --rise and fades. */
  .js [data-anim="rise"] {
    opacity: 0; transform: translateY(14px);
    transition: opacity 520ms var(--ease), transform 520ms var(--ease);
    transition-delay: var(--d, 0ms);
  }

  /* fade — for things that must not move (the record layer, the big number) */
  .js [data-anim="fade"] {
    opacity: 0;
    transition: opacity 620ms var(--ease);
    transition-delay: var(--d, 0ms);
  }

  /* rule — a hairline draws from the left as its seam clears the fold */
  .js [data-anim="rule"] {
    transform: scaleX(0); transform-origin: left center;
    transition: transform 760ms var(--ease);
    transition-delay: var(--d, 0ms);
  }
  .js .seamlabel[data-anim="rule"]     { transform: none; }
  .js .seamlabel[data-anim="rule"]::after {
    transform: scaleX(0); transform-origin: left center;
    transition: transform 760ms var(--ease); transition-delay: var(--d, 0ms);
  }
  .js .seamlabel[data-anim="rule"].in::after { transform: scaleX(1); }

  /* plate — a photograph settles from a 4% overscale. The image moves, its
     frame does not, so nothing around it reflows. */
  .js [data-anim="plate"] { overflow: hidden; }
  .js [data-anim="plate"] img {
    opacity: 0; transform: scale(1.04);
    transition: opacity 760ms var(--ease), transform 900ms var(--ease);
    transition-delay: var(--d, 0ms);
  }
  .js [data-anim="plate"].in img { opacity: 1; transform: none; }

  .js [data-anim].in { opacity: 1; transform: none; }

  /* The hero runs on load, not on scroll — it is already in view, and an
     observer would fire it a frame late. */
  .js .topfield [data-hero],
  .js .hero [data-hero] {
    opacity: 0; transform: translateY(14px);
    transition: opacity 620ms var(--ease), transform 620ms var(--ease);
    transition-delay: var(--d, 0ms);
  }
  .js.ready .topfield [data-hero],
  .js.ready .hero [data-hero] { opacity: 1; transform: none; }

  /* The aerial itself resolves once, on load — a slow tonal settle, no
     movement, so it cannot read as parallax. */
  .js .topfield__media {
    opacity: 0;
    transition: opacity 1100ms var(--ease) 120ms;
  }
  .js.ready .topfield__media { opacity: .92; }
}

/* ── responsive ─────────────────────────────────────────────────────────── */

/* ── the mobile menu: a full-screen panel ───────────────────────────────────
   It used to be an absolutely positioned dropdown, which never rendered: the
   top field sets overflow:hidden so the hero image mask has something to clip
   against, and that clipped the menu away. position:fixed takes its containing
   block from the viewport and escapes the clip — so long as no ancestor sets
   transform, filter or contain, which none does. Do not add one. */
@media (max-width: 60rem) {
  .navtoggle { display: inline-flex; }

  .navpanel {
    position: fixed; inset: 0; z-index: 1;
    display: flex; flex-direction: column; gap: 1.1rem;
    padding: calc(var(--frame) + .8rem) var(--gut)
             calc(1rem + env(safe-area-inset-bottom, 0px));
    background: var(--deepest); color: var(--on-dark);
    overflow-y: auto; overscroll-behavior: contain;
  }
  /* [hidden] is a UA rule at specificity 0; the class above would beat it. */
  .navpanel[hidden] { display: none; }

  .navpanel__top {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
    padding-bottom: .8rem; border-bottom: 1px solid rgba(198,210,192,.28);
  }
  .navpanel__brand { display: inline-flex; align-items: center; gap: .6rem; min-width: 0; }
  .navpanel__brand .mark { inline-size: 30px; block-size: 30px; flex: none; }
  .navpanel__title {
    font-family: var(--disp); font-size: 1.0625rem; letter-spacing: -.01em;
    color: var(--on-dark); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .navclose {
    display: inline-flex; align-items: center; gap: .5rem;
    min-height: var(--tap-min); padding: .45rem .9rem;
    border: 1px solid var(--on-dark-quiet); border-radius: var(--r-ctrl);
    background: transparent; color: var(--on-dark);
    font: var(--w-medium) .9375rem var(--body); cursor: pointer;
  }
  .navclose:hover { background: rgba(247,243,234,.10); }

  /* the links take the middle of the screen, thumb-height */
  .navpanel ul { flex-direction: column; align-items: stretch; gap: .5rem; margin-block: auto; }
  .navpanel .chip {
    width: 100%; justify-content: space-between; min-height: 52px;
    padding: .75rem 1.1rem; font-size: 1.0625rem;
    background: transparent; border-color: rgba(198,210,192,.34);
  }
  .navpanel .chip[aria-current=page] { background: var(--lime); border-color: var(--lime); }

  .navpanel__foot { display: block; padding-top: 1rem; border-top: 1px solid rgba(198,210,192,.28); }
  .navpanel__foot .rec__value { font-size: 1.3rem; }
  .navpanel__foot .rec__note  { margin-top: .1rem; }
  .navpanel__foot .actions    { margin-top: .8rem; max-width: none; gap: .5rem; }
  .navpanel__foot .btn        { min-height: 48px; }
}

@media (max-width: 34rem) {
  .topfield { min-height: clamp(17rem, 40vh, 22rem); }
  .btn      { flex: 1 1 100%; }
  .towns    { columns: 2 8rem; }
  .stickybar .btn { padding-inline: .7rem; font-size: .875rem; }
}

/* THE MARK SHRINKS BEFORE IT GOES. 68% of this site's traffic is mobile, so
   the header is where the mark earns most of its keep — but "Smart Asbestos
   Solutions" ellipsing to "Smart Asbestos Solut…" is the one thing the design
   system's Wordmark note rules out outright: the NAME is what a customer has
   to be able to read. The compromise is a 24px mark and a tighter locator
   line, which measures small enough to keep both down to ~360px. Below 22rem
   there is genuinely no room and the mark goes; the footer and the menu panel
   carry it there. */
@media (max-width: 26rem) {
  .wordmark svg    { inline-size: 24px; block-size: 24px; }
  .wordmark        { gap: .5rem; }
  .wordmark__name  { font-size: 1rem; }
  .wordmark__place { font-size: .625rem; letter-spacing: .08em; }
  /* the room for the mark comes off the Menu button, not off the name */
  .navtoggle       { padding: .45rem .6rem; font-size: .875rem; gap: .4rem; }
}

/* 360px is the commonest Android width and the name was ellipsing there. */
@media (max-width: 23.3rem) {
  .wordmark__name { font-size: .9375rem; }
}

/* 320px is the floor, and below here the mark genuinely will not fit. It is
   carried by the footer and the menu panel instead. The name WRAPS here rather
   than ellipsing: at this width it would otherwise run right up to the Menu
   button, and a two-line company name costs 25px of header where a truncated
   one costs the customer the name. */
@media (max-width: 21.5rem) {
  .wordmark svg   { display: none; }
  /* Capping the wordmark is what actually forces the break — left to itself the
     name still fitted on one line at 320 and sat 15px off the Menu button. */
  .wordmark       { max-inline-size: 58%; }
  .wordmark__name {
    white-space: normal; overflow: visible; text-overflow: clip;
    line-height: 1.15; text-wrap: balance;
  }
  .hdr            { align-items: flex-start; padding-top: .85rem; }
  .navtoggle      { flex: none; }
  .trustrow li:nth-child(n+3) { display: none; }
}

/* ── no-JS ──────────────────────────────────────────────────────────────── */

.nojs-hide { display: none; }
html:not(.js) .nojs-hide { display: revert; }

/* NO JAVASCRIPT. The panel ships `hidden` so it cannot flash open on a phone
   before CSS lands — which means without JS to unset it there would be no
   navigation at all. It degrades to the plain inline chip row at every width:
   the overlay is useless with no script, because nothing could close it.
   Higher specificity than the overlay rules, so the media query cannot win. */
html:not(.js) .navtoggle { display: none; }
html:not(.js) .navpanel,
html:not(.js) .navpanel[hidden] {
  display: block; position: static; inset: auto; z-index: auto;
  padding: 0; background: none; overflow: visible;
}
html:not(.js) .navpanel__top,
html:not(.js) .navpanel__foot { display: none; }
html:not(.js) .navpanel ul {
  flex-direction: row; flex-wrap: wrap; gap: .4rem; margin-block: 0;
}
html:not(.js) .navpanel .chip {
  width: auto; min-height: var(--tap-min); padding: .45rem .8rem;
  font-size: .9375rem; background: var(--deepest);
  border-color: var(--on-dark-quiet); justify-content: center;
}
html:not(.js) .navpanel .chip[aria-current=page] { background: var(--lime); border-color: var(--lime); }

/* the chips need their own row once the wordmark has taken the first one */
@media (max-width: 60rem) {
  html:not(.js) .hdr { flex-wrap: wrap; }
  html:not(.js) .nav { flex: 1 1 100%; }
}

/* ═══ 14 ── the fold's credential strip and reviews line ════════════════
   Added 26 Aug 2026 in the content review (cards P1, P2, P4, P6, P7).
   The strip is deliberately QUIETER than .services above it: sage surface,
   hairline border, no lime fill. The fold reads as one primary row
   (recognition — what we take away) and one secondary row (reassurance —
   who we are), never as two competing chip rows. */

.creds {
  list-style: none; margin: 1.5rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .5rem .55rem; align-items: center;
}
.creds li {
  font-size: .8rem; letter-spacing: .01em; color: var(--text-body);
  background: var(--surface-quiet); border: 1px solid var(--hairline);
  border-radius: 2px; padding: .34rem .7rem; white-space: nowrap;
}
.creds li::before {
  content: ""; display: inline-block; width: .42rem; height: .42rem;
  background: var(--marker); margin-right: .5rem; vertical-align: .05em;
  transform: rotate(45deg);
}

/* PENDING. Same job as .specimen and the same rule: deliberately ugly, never
   tidied away in a polish pass. Marks a claim on the page that has no source
   yet. It comes off when the fact behind it is confirmed, not before.
   See README.md "Pending claims on the page". */
.pend {
  white-space: normal;
  border: 2px dashed var(--haz-caution-spine, #b4671b) !important;
  background: var(--raised) !important;
  color: var(--haz-caution-fg, #8a4d12) !important;
  font-family: var(--mono); text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow); font-size: .68rem !important;
}
.pend::before { background: var(--haz-caution-spine, #b4671b) !important; }

.grev {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .15rem .55rem;
  margin: 1.05rem 0 0; font-size: .85rem; color: var(--text-body);
}
.grev__stars { color: #e8a33d; letter-spacing: .06em; font-size: .92rem; }
.grev__num b { color: var(--text-strong); font-size: 1rem; }
.grev__txt b { color: var(--text-strong); }
.grev__link {
  color: var(--link); text-decoration: none;
  border-bottom: 1px solid var(--hairline); padding-bottom: 1px;
}
.grev__link:hover { color: var(--link-hover); border-bottom-color: currentColor; }

/* The site-visit line under the quote form (card P8). */
.sitevisit {
  margin: 1.1rem 0 0; font-size: .9rem; color: var(--text-body);
}
.sitevisit a { color: var(--link); }

/* Narrow widths: the chips must be allowed to wrap internally. "Over 12 years
   in asbestos removal" and the pending marker are both longer than 390px, so
   nowrap here pushes the page into horizontal overflow. Caught at 390px,
   26 Aug 2026. */
@media (max-width: 46rem) {
  .creds li { white-space: normal; }
}
@media (max-width: 30rem) {
  .creds { gap: .4rem; }
  .creds li { font-size: .74rem; padding: .3rem .55rem; }
  .grev { font-size: .8rem; }
}

/* ═══ 15 ── FAQ ════════════════════════════════════════════════════════
   Added 26 Aug 2026. The competitor audit found 35 distinct customer
   questions across their pages; this page answered 6. Questions scan
   differently from prose — a reader will read a long block of questions
   where they will not read a long block of claims — so this block earns
   its length in a way the deleted strengths grid did not.

   <details>/<summary>: no JS, works with the script blocked, keyboard
   operable, and the answers stay in the DOM for indexing. */

.faq { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--hairline); }

.faq details { border-bottom: 1px solid var(--hairline); }

.faq summary {
  display: flex; align-items: baseline; gap: .9rem;
  padding: 1.05rem .25rem; cursor: pointer; list-style: none;
  font-size: clamp(1rem, 2.2vw, 1.1rem); line-height: 1.35;
  color: var(--text-heading); font-weight: 550;
}
.faq summary::-webkit-details-marker { display: none; }

/* The marker is a rotating chevron drawn in CSS: no icon font, no SVG,
   and it still reads in high-contrast mode because it is a real border. */
.faq summary::after {
  content: ""; margin-left: auto; flex: none;
  width: .5rem; height: .5rem; margin-top: .3rem;
  border-right: 2px solid var(--text-brand);
  border-bottom: 2px solid var(--text-brand);
  transform: rotate(45deg); transition: transform .18s ease;
}
.faq details[open] summary::after { transform: rotate(-135deg); }

.faq summary:hover { color: var(--text-strong); }
.faq summary:focus-visible {
  outline: 2px solid var(--marker); outline-offset: 3px; border-radius: 2px;
}

.faq__a { margin: 0 0 1.15rem; padding-right: 2.5rem; max-width: 62ch; color: var(--text-body); }
.faq__a + .faq__a { margin-top: -.4rem; }

.faq__foot { margin: 1.6rem 0 0; color: var(--text-body); }
.faq__foot a { color: var(--link); }

@media (prefers-reduced-motion: reduce) {
  .faq summary::after { transition: none; }
}

/* The emergency section's safety instruction was reading as a stray line.
   A label gives it a reason to be there. */
.notelabel {
  margin: 0 0 .35rem;
  font: 500 var(--t-eyebrow)/1.4 var(--mono);
  letter-spacing: var(--ls-eyebrow); text-transform: uppercase;
  color: var(--text-on-deep-quiet);
}

/* ── pages.css ── */
/* Components added for the multi-page build, 11 Sep 2026.
   Kept OUT of site.css so that file stays comparable with ../homepage-v2/css/site.css.
   tokens/, base/ and patterns/ remain verbatim copies of the design system and
   are not touched by anything here. */

/* ── the docket (arrange/hazard device) ────────────────────────────────────
   Square corners, top-left cut on the fold's own 45 degrees, a 3px keel
   beside the text (a colour signal, not a box edge), and four redundant
   signals (colour, glyph, word, ruled header) so meaning survives greyscale
   and colour blindness. See _log/DECISIONS.md.

   Corner-cut technique matches the design system's own HazardNote.jsx
   (component/hazard/HazardNote.jsx): a single element cannot carry a border
   round a clipped corner — clip-path removes the border stroke together
   with the corner it cuts away, per that file's own comment. HazardNote
   nests two elements instead: an outer box filled with the edge colour and
   clipped, and an inner panel (.docket__panel) clipped identically 1px
   inside it, so the 1px gap reads as a hairline all the way round,
   including on the cut. Radius is the design system's own --r token (16px),
   not a hardcoded value: the previous 1.1rem (17.6px at the 16px root) had
   drifted from it. */
.docket {
  /* Fallbacks are the measured values from tokens/hazard.css, not guesses. */
  --dk-fg:    var(--haz-danger-fg, #a11d17);
  --dk-edge:  var(--haz-danger-edge, #e08a82);
  --dk-bg:    var(--haz-danger-bg-light, #fdecea);
  --dk-spine: var(--haz-danger-spine, #a11d17);

  position: relative;
  margin: clamp(1.3rem, 3vh, 2rem) 0 0;
  background: var(--dk-edge);            /* the outer, hairline-coloured box */
  clip-path: polygon(var(--r, 16px) 0, 100% 0, 100% 100%, 0 100%, 0 var(--r, 16px));
  padding: 1px;                          /* the width of the resulting hairline */
}
.docket__panel {
  padding: clamp(1rem, 2.6vw, 1.5rem);
  background: var(--dk-bg);
  clip-path: polygon(var(--r, 16px) 0, 100% 0, 100% 100%, 0 100%, 0 var(--r, 16px));
}
.docket--refer  { --dk-fg: var(--haz-caution-fg, #7a4b06); --dk-edge: var(--haz-caution-edge, #e6c283);
                  --dk-bg: var(--haz-caution-bg-light, #fdf2df); --dk-spine: var(--haz-caution-spine, #b87413); }
.docket--info   { --dk-fg: var(--haz-info-fg, #175a78); --dk-edge: var(--haz-info-edge, #a9cde0);
                  --dk-bg: var(--haz-info-bg-light, #e9f2f7); --dk-spine: var(--haz-info-spine, #1d7099); }

/* The ruled header: mono code, glyph and state word. Signal 2, 3 and 4. */
.docket__code {
  display: flex; align-items: center; gap: .5rem;
  margin: 0 0 .7rem; padding: 0 0 .6rem;
  border-bottom: 1px solid var(--dk-edge);
  font: 500 var(--t-rec)/1 var(--mono);
  text-transform: uppercase; letter-spacing: var(--ls-eyebrow);
  color: var(--dk-fg);
}
.docket__glyph {
  display: inline-grid; place-items: center;
  inline-size: 1.35rem; block-size: 1.35rem;
  border: 1.5px solid currentColor; border-radius: 50%;
  font-size: .8em; line-height: 1;
}

/* The keel. 3px, inside the docket, beside the text column. Not a box edge. */
.docket__body { position: relative; padding-inline-start: 1rem; }
.docket__body::before {
  content: ""; position: absolute; inset-block: .2rem; inset-inline-start: 0;
  inline-size: 3px; background: var(--dk-spine);
}
.docket__body h3 { margin: 0 0 .45rem; }
.docket__body p  { margin: 0 0 .7rem; color: var(--prose); }
.docket__body p:last-child { margin-bottom: 0; }
.docket__body a  { color: var(--link); }

/* ── page lede / h1 on an interior page ───────────────────────────────────
   Interior pages open on paper with a single measure of prose, not a hero
   photograph. One layout family, used once, at the top. */
.pagehead { padding-block: clamp(1.6rem, 5vh, 3rem) clamp(1rem, 3vh, 1.8rem); }
.pagehead h1 {
  margin: 0 0 clamp(.7rem, 2vh, 1.1rem);
  font: var(--w-display) var(--t-h1)/var(--lh-display) var(--disp);
  font-variation-settings: var(--disp-vf);
  letter-spacing: var(--ls-display);
  color: var(--head); max-inline-size: 19ch; text-wrap: balance;
}
.pagehead .standfirst {
  margin: 0; max-inline-size: 58ch;
  font-size: clamp(1.02rem, .96rem + .3vw, 1.18rem); line-height: 1.6;
  color: var(--prose);
}

/* ── breadcrumb ────────────────────────────────────────────────────────────
   Mono, hairline-separated, no chevron glyphs. Helps orientation on a deep
   service page arrived at cold from a search result. */
.crumb { margin: 0 0 1rem; font: 400 var(--t-rec)/1.4 var(--mono); color: var(--brandtext); }
.crumb ol { list-style: none; display: flex; flex-wrap: wrap; gap: .3rem .55rem; margin: 0; padding: 0; }
.crumb li + li::before { content: "/"; margin-inline-end: .55rem; color: var(--edge); }
.crumb a { color: inherit; text-decoration: none; border-bottom: 1px solid var(--edge); }
.crumb a:hover { color: var(--head); }

/* ── a two-column fact split used on geo pages ─────────────────────────────
   DESIGN-AUDIT 3.1 flags this as a duplicate of .strengths (css/site.css,
   used on zero pages). Checked: they are not the same component under two
   names. .strengths is an unordered list (`<li>` items), styled with a lime
   top-rule marker, a mono tabular-figure heading and breakpoints at 40rem
   (2-up) and 64rem (3-up). .pair is a plain heading+paragraph split with no
   marker and a single 44rem breakpoint that never goes past 2-up. Switching
   the 30 pages that use .pair over to .strengths as it stands in site.css
   would visibly change the page (a third column appears at >=64rem width,
   the 2-up breakpoint shifts by 4rem, the heading gains mono tabular
   figures) — not permitted here. site.css cannot be edited to reconcile the
   breakpoints either. Neutralising every difference in an override would
   mean writing as much CSS here as .pair already has, under .strengths's
   name instead — no reduction in duplication, just a rename. Kept .pair as
   the one implementation of this job; .strengths remains genuinely unused
   dead code in the frozen site.css. */
.pair { display: grid; gap: clamp(1.2rem, 3vw, 2.2rem); margin-top: clamp(1.3rem, 3vh, 2rem); }
@media (min-width: 44rem) { .pair { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.pair > div { min-inline-size: 0; }
.pair h3 { margin: 0 0 .5rem; }
.pair p  { margin: 0; color: var(--prose); }

/* Card footer link. The affordance is the underline and the arrow, not a box:
   a boxed button inside every card would read as four CTAs competing with the
   page's actual one. Matches the .steps__acts treatment already in site.css. */
.card__more {
  margin: .85rem 0 0;
  font: var(--w-medium) .9375rem/1.3 var(--body);
}
.card__more a {
  display: inline-flex; align-items: center; gap: .45rem;
  min-height: var(--tap-min); padding: .3rem 0;
  color: var(--brandtext); text-decoration: underline;
  text-decoration-color: var(--edge); text-underline-offset: .28em;
  text-decoration-thickness: 1.5px;
  transition: color var(--dur) var(--ease), text-decoration-color var(--dur) var(--ease);
}
.card__more a:hover { color: var(--head); text-decoration-color: var(--head); }

/* ── accessibility fixes, measured 11 Sep 2026 by qa/measure.mjs ───────────
   1. Breadcrumb links measured below the 44px tap-target floor. */
.crumb a { display: inline-flex; align-items: center; min-height: var(--tap-min, 44px); }
.crumb ol { align-items: center; }

/* 2. Google rating stars measured 1.95:1 (floor 4.5:1) and used an amber this
      system reserves for hazard states. DESIGN-AUDIT 3.2/1.4: the bug is
      css/site.css:1006's own hardcoded `color: #e8a33d` — not a design-system
      token (no such value exists in tokens/colors.css), so there is nothing
      to fix upstream. site.css is a frozen copy of homepage-v2 and is not
      edited here, so this override is the correct and permanent home for the
      fix, not a temporary patch. aria-hidden with an accessible name, so
      nothing is lost setting them in heading ink. */
.grev__stars { color: var(--head); }

/* The services-list card link measured 1.87:1 on a forest surface (floor
   4.5:1). .on-dark at 0-2-0 beats the bare rule, same doubling site.css
   already applies to buttons. See _log/DECISIONS.md. */
.on-dark .card__more a { color: var(--link-on-deep, var(--on-dark)); }
.on-dark .card__more a:hover { color: var(--on-dark); text-decoration-color: var(--on-dark); }

/* ── .bignum outside the dark band ─────────────────────────────────────────
   Measured 1:1 (invisible) on /emergency-asbestos-removal/: site.css sets
   .bignum to --paper for the homepage's dark band, so it's paper-on-paper on
   a light surface. Same doubling pattern as buttons — see _log/DECISIONS.md. */
.bignum { color: var(--head); }
.bignum:hover { color: var(--deepest); border-bottom-color: var(--head); }
.on-dark .bignum { color: var(--paper); }
.on-dark .bignum:hover { color: var(--paper); border-bottom-color: var(--on-dark); }

/* Footer legal row, above the company legal paragraph. Present on every page
   so the privacy/cookie policy is reachable from the footer everywhere. */
.foot__links {
  margin: clamp(1.4rem, 3vh, 2rem) 0 .8rem;
  font: 400 var(--t-rec)/1.6 var(--mono);
  color: var(--on-dark-quiet);
}
.foot__links a {
  color: inherit; text-decoration: none;
  border-bottom: 1px solid currentColor; padding-bottom: 1px;
  display: inline-flex; align-items: center; min-height: var(--tap-min, 44px);
}
.foot__links a:hover { color: var(--on-dark); }

/* ── WCAG 2.2 AA failures found by measuring, 12 Sep 2026 ──────────────────
   A1. Form borders. site.css:650-652 sets `border: 1.5px solid var(--edge)`
   (1.42:1) where SC 1.4.11 needs 3:1 on the only visual cue a text field has.
   DESIGN-AUDIT 3.2/1.4 checked this against the design system's own files:
   tokens/shape.css already defines --border-ctrl as `1.5px solid var(--forest)`
   (10.53:1), exactly what the system's readme specifies for controls — the
   token itself is correct and was simply not the one site.css reached for.
   So the bug is site.css's own, not the design system's; nothing to report
   upstream. site.css is a frozen copy of homepage-v2 and is not edited here,
   so this override is the correct and permanent home for the fix. */
.field input,
.field select,
.field textarea { border-color: var(--border-ctrl, #2b4232); }

/* A2. FAQ focus ring was lime (1.59:1) — site.css:1068 sets
   `outline: 2px solid var(--marker)`, and the system's own rule is "lime is a
   marker, never a text colour" (readme.md:177-181); its focus spec is a
   --deepest ring plus a lime halo (readme.md:337-339), which is what
   --marker-halo exists for. The design system's --marker token itself is
   correct (readme.md:432-435 confirms this exact rule); site.css simply used
   it as a solid outline instead of the halo. Bug is site.css's own. site.css
   is frozen and not edited here, so restoring the system's 3px --deepest
   ring, with the halo lime is allowed to do, belongs in this override. */
.faq summary:focus-visible {
  outline: 3px solid var(--deepest);
  outline-offset: 2px;
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--lime) 55%, transparent);
  border-radius: 2px;
}
.on-dark .faq summary:focus-visible { outline-color: var(--paper); }

/* B8. Without this, Chrome on Android's Auto Dark Theme inverts the page into
   an unmeasured palette, hazard ramp included. */
:root { color-scheme: light; }

/* B2. Print: every .on-dark band would otherwise print --paper on white
   (1:1, invisible), taking the footer's company number with it. */
@media print {
  .topfield__media, .topfield__scrim, .stickybar, .navpanel, .navtoggle { display: none !important; }
  .on-dark, .sec--deep, .foot, .topfield {
    background: #fff !important; color: #000 !important;
  }
  .on-dark *, .foot *, .topfield * { color: #000 !important; }
  .foot__legal, .foot__links { color: #000 !important; }
  a { text-decoration: underline; }
  /* Print the destination of a real link, so a printed page stays usable. */
  main a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .85em; }
  .fold, .lattice { display: none !important; }
  [data-anim], [data-hero] { opacity: 1 !important; transform: none !important; }
}

/* A3. Skip link: no bypass mechanism existed for a sighted keyboard user past
   the header and nav. Lands on the deep field, so takes the light ring. */
.skip {
  position: absolute; inset-inline-start: -9999px; top: 0; z-index: 100;
  padding: .75rem 1.15rem; min-height: var(--tap-min, 44px);
  display: inline-flex; align-items: center;
  background: var(--paper); color: var(--head);
  font: var(--w-medium) 1rem/1 var(--body);
  border: 2px solid var(--deepest); border-radius: var(--r-ctrl, 12px);
}
.skip:focus {
  inset-inline-start: var(--gut, 1rem); top: var(--gut, 1rem);
  outline: 3px solid var(--paper); outline-offset: 2px;
}

/* ── quote form status (backlog Task 1) ────────────────────────────────────
   The loading/success/error region js/app.js writes into. Modelled on
   .docket: same three redundant signals (colour, glyph, word). ERROR reuses
   the hazard "danger" tokens as a genuine stop condition; LOADING borrows
   "info" blue like arrange()'s tone: 'note'; SUCCESS isn't a hazard state so
   takes the site's forest-on-sage pairing instead. */
.formstatus {
  display: flex; align-items: flex-start; gap: .65rem;
  margin: 1rem 0 0; padding: .85rem 1.05rem;
  border: 1.5px solid var(--fs-edge); border-radius: var(--r-ctrl, 12px);
  background: var(--fs-bg);
}
.formstatus[hidden] { display: none; }
.formstatus__tag {
  display: block; margin: 0 0 .35rem;
  font: 500 var(--t-rec)/1.3 var(--mono);
  text-transform: uppercase; letter-spacing: var(--ls-eyebrow);
  color: var(--fs-fg);
}
.formstatus__glyph {
  display: inline-grid; place-items: center; flex: none;
  inline-size: 1.4rem; block-size: 1.4rem; margin-top: .1rem;
  border: 1.5px solid currentColor; border-radius: 50%;
  font: 700 .78rem/1 var(--mono); color: var(--fs-fg);
}
.formstatus__body { font: var(--w-medium) .9375rem/1.55 var(--body); color: var(--fs-fg); }
.formstatus__body strong { font-weight: var(--w-bold, 700); }
.formstatus__body a { color: inherit; text-decoration-thickness: 1.5px; }

.formstatus--error   { --fs-fg: var(--haz-danger-fg, #a11d17); --fs-edge: var(--haz-danger-edge, #e08a82); --fs-bg: var(--haz-danger-bg-light, #fdecea); }
.formstatus--loading { --fs-fg: var(--haz-info-fg, #175a78);   --fs-edge: var(--haz-info-edge, #a9cde0);   --fs-bg: var(--haz-info-bg-light, #e9f2f7); }
.formstatus--success  { --fs-fg: var(--head, #3a5642); --fs-edge: var(--forest, #2b4232); --fs-bg: var(--sage, #e9eee0); }

/* The loading glyph spins rather than showing a character, so it reads as
   "in progress" without relying on the word alone either. Off under reduced
   motion, matching every other animation in this build. */
@media (prefers-reduced-motion: no-preference) {
  .formstatus--loading .formstatus__glyph {
    border-color: var(--fs-edge); border-top-color: var(--fs-fg);
    animation: formstatus-spin .8s linear infinite;
  }
}
@keyframes formstatus-spin { to { transform: rotate(360deg); } }
#main:focus { outline: none; }

/* ── A5. form hints and required labelling (backlog Task 15) ──────────────
   Hints now get an id and controls aria-describedby (site.css's hint span had
   neither). Flags the two OPTIONAL fields rather than the six required ones,
   so the fact is stated once, not six times — the intro sentence covers the
   six. */
.field__opt {
  margin-inline-start: .45rem;
  font: 400 var(--t-eyebrow, .75rem)/1.2 var(--mono);
  letter-spacing: var(--ls-eyebrow, .06em); text-transform: uppercase;
  color: var(--prose);
}
.field__intro { margin: 0 0 1rem; font-size: .9375rem; color: var(--prose); }

/* ── A7. 320px content clipping (backlog Task 18) ──────────────────────────
   site.css:929 hides the trust row's third item below 344px, below the
   project's 320px floor (SC 1.4.10). .trustrow is already flex-wrap: wrap;
   pages.css loads after site.css so this un-hides it without !important. */
@media (max-width: 21.5rem) {
  .trustrow li:nth-child(n+3) { display: inline-flex; }
}

/* ── B5. back to top (backlog Task 19) ─────────────────────────────────────
   In flow, in the footer, no JS, no scroll listener — a floating button would
   be a second fixed overlay this system's rules don't allow. Deliberately no
   scroll-behavior: smooth, which would read as scroll-jacking. */
.totop   { margin: 1.1rem 0 0; font-size: .9375rem; }
.totop a {
  display: inline-flex; align-items: center; min-height: var(--tap-min, 44px);
  color: var(--paper);
}

/* ── job shapes: the typical-job case-study device (backlog Task 12) ───────
   Describes how a job of a given SHAPE usually runs, in the habitual present
   — never a specific job, since none is evidenced (_log/DECISIONS.md). Only
   the spacing between two stacked shapes is new here. */
.jobshape + .jobshape {
  margin-top: clamp(2rem, 5vh, 3rem);
  padding-top: clamp(1.6rem, 4vh, 2.4rem);
  border-top: 1px solid var(--edge);
}
.jobshape h3 { margin: 0 0 .9rem; }
.jobshape .strip { margin: 0 0 1.1rem; }
.jobshape p { margin: 0 0 .8rem; color: var(--prose); }
.jobshape p:last-child { margin-bottom: 0; }

/* .notelabel measured 1.42:1 on the emergency page's paper .pagehead — same
   class of bug as .bignum above: an on-dark colour reused on a light
   surface. Same doubling fix. */
.notelabel { color: var(--brandtext); }
.on-dark .notelabel, .sec--deep .notelabel { color: var(--text-on-deep-quiet); }

/* ── .services li was clipping full sentences on all 38 pages ──────────────
   site.css:317's white-space: nowrap is correct for the short material chips
   it was built for, but 38 pages reuse the class for bulleted sentences — one
   measured 1521px wide in a 1024px viewport (_log/DECISIONS.md has the full
   overflow-gate and keyboard-trap story). Wrapping is safe for both uses. */
.services li {
  white-space: normal;
  align-items: baseline;
}
.services li::before { align-self: baseline; position: relative; top: -0.15em; }
