/* ==========================================================================
   AM Handyman and Maintenance Services
   DIRECTION: "Mitre & Ledger"  (moodboard/DIRECTION.md)

   Three things carry this site and nothing else may fight them:
     1. the 53 degree mitre, taken off the outer leg of the A in the client's
        own monogram. Seams and edges only. One per screenful.
     2. zero radius. The mark has none, so the site has none.
     3. one duotone grade on every photograph, baked into images/site/.
   ========================================================================== */

:root{
  /* colour, sampled off images/brand/logo-a.png ------------------------- */
  --primary:#282828;        /* the charcoal of the M */
  --ink:#1A1A1A;            /* the near-black of HANDYMAN */
  --accent:#D1AE44;         /* the gold of the A */
  --accent-dim:#B49334;     /* hover */
  --accent-ink:#6B540E;     /* 6.42:1 on accent-soft */
  --accent-soft:#F7F1DF;
  --secondary:#5C5A55;      /* 6.27:1 on surface */
  --surface:#F6F4EF;
  --sc-low:#EFEBE2;
  --sc:#DFD9CC;
  --sc-lowest:#FAF8F4;
  --white:#fff;
  --ok:#2F7D4F;             /* a real green. The gold must never mean "go". */
  --err:#C23B3B;

  --shadow-sm:0 1px 2px rgba(40,40,40,.06);
  --shadow:0 10px 30px rgba(40,40,40,.12);
  --shadow-xl:0 24px 54px rgba(40,40,40,.18);

  /* ZERO. No 11px buttons, no 16px cards. */
  --radius:0;

  /* THE SIGNATURE DEVICE ------------------------------------------------ */
  --mitre:53deg;            /* measured off the outer leg of the A */
  --mitre-run:0.75;         /* 1 / tan(53deg): horizontal travel per unit height */
  --m:clamp(88px,16vw,236px);              /* the cut's vertical leg */
  --mr:calc(var(--m) * var(--mitre-run));  /* its horizontal travel, always 0.75 of it */
  --mark:clamp(34px,4.2vw,56px);           /* the monogram that sits in the cut */

  --maxw:1180px;
  --mast:64px;

  /* Chivo: its terminals are sheared on a diagonal, the same cut as the
     monogram and the mitre band. IBM Plex Sans is an engineering-drawing
     voice and is deliberately NOT Inter (PLAYBOOK 07 hard rule 1 bans it).
     The mono is the same superfamily so it is not a third personality. */
  --font:'IBM Plex Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,Roboto,Helvetica,Arial,sans-serif;
  --font-display:'Chivo',var(--font);
  --font-mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}

/* --- reset ---------------------------------------------------------------- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{margin:0;background:var(--surface);color:var(--ink);font-family:var(--font);
  /* `overflow-x:hidden` here made BODY its own scroll container, which silently
     broke every in-page anchor: `contact.html#quote` landed at scrollY 0 with the
     form 2,212px down, so all 6 quote CTAs across 16 pages went nowhere, and a
     second tap was a browser no-op once the URL already carried the hash.
     `clip` suppresses horizontal scroll WITHOUT establishing a scroll container,
     so fragment navigation works again. Do not put `hidden` back. */
  font-size:.95rem;line-height:1.65;overflow-x:clip;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
img,svg,video{max-width:100%}
img{border-style:none}
h1,h2,h3,h4,h5{margin:0;font-family:var(--font-display);color:var(--primary);
  letter-spacing:-.012em;line-height:1.04;text-wrap:balance;font-weight:700}
p{margin:0 0 1em}
p:last-child{margin-bottom:0}
a{color:inherit;text-decoration:none}
ul,ol{margin:0;padding:0}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
input,select,textarea{font:inherit;color:inherit}
[hidden]{display:none!important}
:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
::selection{background:var(--accent);color:var(--primary)}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0}

.wrap{max-width:var(--maxw);margin:0 auto;padding:0 22px}
section{scroll-margin-top:calc(var(--mast) + 16px)}

/* --- type ----------------------------------------------------------------- */
/* The whole drama is the jump from the mono label (.68rem) to the h1.
   Nothing sits in between. */
.eyebrow{display:block;font-family:var(--font-mono);font-weight:500;font-size:.68rem;
  letter-spacing:.16em;text-transform:uppercase;color:var(--accent-ink);
  margin-bottom:14px}
.eyebrow::before{content:"// "}
.on-dark .eyebrow,.hero .eyebrow,.qband .eyebrow,.pband .eyebrow{color:var(--accent)}

h1{font-size:clamp(2.4rem,min(6.4vw,8.4vh),3.3rem);font-weight:900;line-height:.96;
  letter-spacing:-.022em}
h2{font-size:clamp(1.7rem,3.3vw,2.4rem);font-weight:700}
h3{font-size:1.12rem;font-weight:700;letter-spacing:-.008em}
.lead{font-size:clamp(1rem,1.35vw,1.15rem);color:var(--secondary);max-width:36em}
.mono{font-family:var(--font-mono);font-size:.72rem;letter-spacing:.1em;text-transform:uppercase}

.sec{padding:74px 0;background:var(--surface)}
.sec.alt{background:var(--white)}
.sec.low{background:var(--sc-low)}
.sec.dark{background:var(--primary);color:#fff}
.sec.dark h2,.sec.dark h3{color:#fff}
.sec-head{max-width:660px;margin:0 0 40px}
.sec-head.mid{margin-left:auto;margin-right:auto;text-align:center}
/* a heading that must hold ONE line needs more measure than the 660px default
   (the client 2026-07-31: "make it one line, and underline it") */
.sec-head.wide{max-width:960px}
/* centred main section titles (the client 2026-07-30): the rule ornament centres with
   the text. The .rule::after tick extends 26px right of the 64px bar, so the
   composite is 90px wide; centring the bar then shifting 13px left centres it. */
.sec-head.mid .rule{margin-left:auto;margin-right:auto;transform:translateX(-13px)}
.sec-head.mid p{margin-left:auto;margin-right:auto}
.sec-head.mid h2{text-wrap:balance}
.sec-head p{color:var(--secondary);margin-top:14px;max-width:44em}
.sec.dark .sec-head p{color:rgba(255,255,255,.72)}

/* --- buttons -------------------------------------------------------------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;
  font-family:var(--font-display);font-weight:700;font-size:.92rem;letter-spacing:.005em;
  padding:15px 30px;border-radius:0;border:2px solid transparent;
  transition:background .16s,color .16s,border-color .16s;white-space:nowrap;
  min-height:48px}
/* *** LOAD-BEARING CORRECTION TO THE HOUSE STANDARD ***
   PLAYBOOK 01 §4.1 sets .btn-gold to WHITE text on the accent. On this client's
   gold #D1AE44 white scores 2.13:1 and fails outright. Here .btn-gold is
   --primary on --accent, which is 6.91:1. Do not inherit the default. */
.btn-gold{background:var(--accent);color:var(--primary)}
.btn-gold:hover{background:var(--accent-dim)}
.btn-primary{background:var(--primary);color:#fff}
.btn-primary:hover{background:var(--ink)}
.btn-ghost{background:transparent;color:var(--primary);border-color:var(--sc)}
.btn-ghost:hover{border-color:var(--primary)}
.on-dark .btn-ghost,.hero .btn-ghost,.qband .btn-ghost{color:#fff;border-color:rgba(255,255,255,.42)}
.on-dark .btn-ghost:hover,.hero .btn-ghost:hover,.qband .btn-ghost:hover{border-color:#fff}
.btn-sm{padding:11px 20px;font-size:.86rem;min-height:42px}
.btn-block{width:100%}
.btn .svg-ico{width:17px;height:17px}

/* THE ICON SYSTEM. Butt caps and MITRED joins, never round: the sprite in
   site.js is drawn entirely from axis-aligned strokes and 3:4 diagonals
   (53.13deg = --mitre), so a rounded cap would soften the one thing the set is
   built on. One weight, one viewBox, one optical size. */
.svg-ico{width:22px;height:22px;stroke:currentColor;stroke-width:1.8;fill:none;
  stroke-linecap:butt;stroke-linejoin:miter;flex:none}
.svg-ico.fill{fill:currentColor;stroke:none}

/* --- masthead ------------------------------------------------------------- */
.mast{position:sticky;top:0;z-index:60;background:var(--surface);
  border-bottom:1px solid var(--sc)}
.mast-in{height:var(--mast);display:flex;align-items:center;justify-content:space-between;gap:18px}
.brand{display:flex;align-items:center;gap:10px;flex:none}
/* the supplied lockup is a white-ground PNG. multiply drops the white into the
   warm surface without re-cutting the client's artwork. */
/* the client 2026-07-30: logo bumped (was 38/32/34), weighted to mobile.
   Desktop 48 against the 64px bar; phone stays 46: at 320 the 509:110 lockup
   at 48px high (222px wide) + 44px burger + 18px gap exactly fills the wrap,
   zero slack, so 46 is the safe ceiling there. Drawer logo matches. */
.brand .logo-img{height:48px;width:auto;display:block;mix-blend-mode:multiply}
.menu{display:flex;align-items:center;gap:26px}
.menu .lnk{font-size:.88rem;font-weight:500;color:var(--primary);padding:6px 0;
  border-bottom:2px solid transparent;white-space:nowrap}
.menu .lnk:hover,.menu .lnk.active{border-bottom-color:var(--accent)}
.navright{display:flex;align-items:center;gap:16px}
.nav-phone{display:inline-flex;align-items:center;gap:7px;font-family:var(--font-mono);
  font-size:.82rem;font-weight:500;color:var(--primary);white-space:nowrap}
.nav-phone .svg-ico{width:16px;height:16px;color:var(--accent-ink)}

/* The masthead band between the drawer breakpoint (900) and a comfortable
   desktop. Found 2026-07-31 auditing tablet widths: at 905 the full desktop nav
   was still on, but six links + the number + the button no longer fit, so the
   phone number broke into THREE stacked lines ("0451 / 110 / 154") and "Our
   Work" onto two. Nothing overflowed - flex wrapped the text instead - which is
   why the overflow sweep passed it. nowrap above makes the pressure visible
   rather than silently ugly; this tightens the row so it fits, and below 1000
   drops the number (the "Get a quote" button stays, and the drawer and the hero
   both still carry the phone). */
@media(max-width:1080px){
  .menu{gap:18px}
  .menu .lnk{font-size:.84rem}
  .navright{gap:12px}
}
@media(max-width:1000px){
  .nav-phone{display:none}
}

.navdrop{position:relative;display:inline-flex;align-items:center}
.navdrop .dropmenu{position:absolute;top:100%;left:50%;transform:translateX(-50%);
  background:var(--white);border:1px solid var(--sc);box-shadow:var(--shadow-xl);
  padding:8px 0;min-width:290px;display:none;z-index:80}
.navdrop .dropmenu::before{content:"";position:absolute;top:-10px;left:0;right:0;height:10px}
.navdrop:hover .dropmenu,.navdrop:focus-within .dropmenu{display:block}
.dropmenu .dgrp{font-family:var(--font-mono);font-size:.63rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--secondary);padding:10px 18px 6px}
.dropmenu a{display:block;padding:9px 18px;font-size:.87rem;color:var(--primary);
  border-left:2px solid transparent}
.dropmenu a:hover{background:var(--sc-lowest);border-left-color:var(--accent)}

.burger{display:none;width:44px;height:44px;align-items:center;justify-content:center;
  border:1px solid var(--sc);color:var(--primary)}

.drawer{position:fixed;top:0;right:0;bottom:0;width:min(88vw,360px);background:var(--white);
  z-index:90;transform:translateX(102%);transition:transform .26s ease;overflow-y:auto;
  padding:0 0 40px;box-shadow:var(--shadow-xl)}
.drawer.open{transform:none}
.drawer-top{display:flex;align-items:center;justify-content:space-between;
  height:var(--mast);padding:0 18px;border-bottom:1px solid var(--sc)}
.drawer-top .logo-img{height:46px;width:auto;flex:none;mix-blend-mode:multiply}
.drawer>a{display:block;padding:14px 18px;font-family:var(--font-display);font-weight:700;
  font-size:1rem;color:var(--primary);border-bottom:1px solid var(--sc-low)}
.drawer-sub{display:flex;flex-direction:column;background:var(--sc-lowest)}
.drawer-sub .dgrp{font-family:var(--font-mono);font-size:.62rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--secondary);padding:12px 18px 4px}
.drawer-sub a{padding:10px 18px 10px 30px;font-size:.92rem;font-weight:500;
  color:var(--secondary);border-bottom:1px solid var(--sc-low)}
.drawer .btn{margin:16px 18px 0;display:flex}
.drawer a.btn-primary,.drawer a.btn-gold{border-bottom:0}
.scrim{position:fixed;inset:0;background:rgba(26,26,26,.5);z-index:85;opacity:0;
  pointer-events:none;transition:opacity .26s}
.scrim.open{opacity:1;pointer-events:auto}

/* ==========================================================================
   THE 53 DEGREE MITRE
   clip-path, never a rotated pseudo-element, so it never affects layout and
   degrades to a straight edge in anything that cannot render it.
   Seams and edges only. Never a floating shape, a badge, an arrow, a button
   or an icon. Never more than one visible per screenful.
   ========================================================================== */
.mitre-tl{clip-path:polygon(var(--mr) 0, 100% 0, 100% 100%, 0 100%, 0 var(--m))}
.mitre-tr{clip-path:polygon(0 0, calc(100% - var(--mr)) 0, 100% var(--m), 100% 100%, 0 100%)}
.mitre-bl{clip-path:polygon(0 0, 100% 0, 100% 100%, var(--mr) 100%, 0 calc(100% - var(--m)))}

/* --- hero ----------------------------------------------------------------- */
/* PLAYBOOK 02 Fix 9/9b: 62vh class, never full viewport. The stat band must
   peek so the page invites a scroll, and the whole hero must fit a short
   laptop window, which is why the h1 clamp carries a vh term. */
.hero{position:relative;background:var(--ink);color:#fff;overflow:hidden;
  min-height:min(calc(72svh - var(--mast)),660px);display:flex;align-items:center}
.hero .wrap{position:relative;z-index:3;width:100%}
/* The text column must never run onto the photograph. In the split hero the
   photo starts at 54vw, and at 901-1100px a 60ch column reached past it: the
   lead and the mono note sat over the image and measured 3.09:1, an outright AA
   failure, and the DIRECTION forbids the gradient scrim that would normally be
   used to rescue it. So the column is capped to clear the photo instead. Above
   about 1105px 60ch is the smaller of the two and nothing changes. */
.hero-in{padding:clamp(34px,5.4vh,62px) 0 clamp(38px,6vh,68px);
  max-width:min(60ch,calc(50vw - 22px))}
.hero h1{color:#fff;margin-bottom:20px}
/* 04 §6.5 RULE 1: the service H1 IS the keyword and the accent underline wraps
   the WHOLE heading, never part of it. */
/* two-tone hero H1 (the client 2026-07-30): white keyword line, gold benefit line */
/* the client 2026-07-30: secondary pages (about, contact, gallery, guides, legal)
   get a COMPACT title band, max ~40vh: eyebrow + h1 + one line, no CTA pair,
   no photo. Content starts immediately (the page-intent principle, 04 S2.10). */
.hero.hero-compact{min-height:0;height:auto;padding:clamp(36px,5vh,60px) 0 clamp(26px,4vh,44px)}
.hero.hero-compact .hero-in{max-width:60ch;padding-bottom:0}
.hero.hero-compact h1{font-size:clamp(2.1rem,4.6vw,3rem)}

/* the client 2026-07-30 (final ruling): all three hero lines at ONE uniform size,
   Bedrock-style; the pyramid comes from text length, not size changes.
   Breaks: "Handyman in Sydney" / "one visit" / "for the whole list." */
.hero h1 .kw{display:block}
.hero h1 .amb{display:block;color:var(--accent)}
.hero h1 .u{background:linear-gradient(var(--accent),var(--accent)) 0 97%/100% .08em no-repeat;
  -webkit-box-decoration-break:clone;box-decoration-break:clone;padding-bottom:.05em}
/* the same gold underline, available on any section heading (the client 2026-07-31).
   box-decoration-break:clone so it still draws correctly if a line ever wraps. */
.sec-head h2 .u{background:linear-gradient(var(--accent),var(--accent)) 0 96%/100% .07em no-repeat;
  -webkit-box-decoration-break:clone;box-decoration-break:clone;padding-bottom:.06em}
.hero .lead{color:rgba(255,255,255,.76);max-width:34em}
.hero-cta{display:flex;flex-wrap:wrap;gap:12px;margin-top:30px}
.hero-note{margin-top:24px;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.1em;
  text-transform:uppercase;color:rgba(255,255,255,.55)}
.hero-note b{color:var(--accent);font-weight:500}

/* the photograph, right, its top-left corner mitre-cut. The dark hero shows
   through the cut and the gold monogram sits in it. */
.hero-photo{position:absolute;top:0;right:0;bottom:0;width:46%;z-index:1;
  background:var(--primary)}
.hero-photo img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:60% 60%}
/* "Illustrative image" labels are retired sitewide (the client, 2026-07-30). The
   generated-image honesty now lives in alt-text neutrality, no generated file
   in the gallery, no generated og:image (final-gate.sh) and IMAGES-GENERATED.md
   as the provenance record. The old .hero-cap label bar is gone with it. */
/* the gold monogram sits IN the cut, at the centroid of the removed triangle */
.hero-mark{position:absolute;z-index:4;width:var(--mark);height:var(--mark);color:var(--accent);
  top:calc(var(--m)/3 - var(--mark)/2);
  right:calc(46% - var(--mr)/3 - var(--mark)/2)}
.hero::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;
  background:var(--accent);z-index:5}

/* --- stat band, welded to the hero ---------------------------------------- */
.statband{background:var(--primary);color:#fff}
.statband .row{display:grid;grid-template-columns:repeat(4,1fr)}
/* Bedrock-style horizontal trust cells (the client 2026-07-30 v2): icon tile LEFT,
   text block right (bold value over mono subline), vertically centred, tight.
   The rating cell runs the real 4.6/5 ledger meter in the tile slot. Bold
   statband-only glyph variants (i-shield-b/i-pin-b/i-clock-b) are drawn for
   the tile size: bigger silhouettes, fewer interior details, stroke 2.1.
   Zero radius, no mitre cut on plates (DIRECTION bans the cut on icons). */
.statband .st{display:flex;align-items:center;gap:14px;
  padding:18px 22px 18px 0;border-right:1px solid rgba(255,255,255,.14)}
.statband .st:last-child{border-right:0}
.statband .st:not(:first-child){padding-left:22px}
.statband .st-tx{display:flex;flex-direction:column;gap:5px;min-width:0}
.statband b{display:block;font-family:var(--font-display);font-weight:900;
  font-size:clamp(1.35rem,2.2vw,1.7rem);line-height:1;color:#fff}
.statband .st-tx span{display:block;font-family:var(--font-mono);font-size:.64rem;
  letter-spacing:.12em;text-transform:uppercase;color:rgba(255,255,255,.62)}
.statband .st-plate{flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:46px;height:46px;background:rgba(209,174,68,.13)}
.statband .st-ico{width:23px;height:23px;color:var(--accent);display:block}
.statband .st-plate .st-ico use{stroke-width:2.1}
/* the one-word mobile sublines (Fix 2 pattern); desktop shows the full .st-l line.
   Specificity must beat .statband .st-tx span or the short line leaks on desktop. */
.statband .st-tx .st-s{display:none}

/* --- diagnosis rows ------------------------------------------------------- */
.prow{display:grid;grid-template-columns:1fr 1fr;gap:clamp(28px,4.4vw,60px);
  align-items:center}
.prow + .prow{margin-top:clamp(48px,6vw,86px)}
.prow.flip .prow-txt{order:2}
/* a row for a service we have not photographed yet: no padded-out stock photo,
   the checklist takes the second column instead */
.prow.solo{align-items:start}
.prow.solo .prow-img{padding-top:6px}
.prow.solo .check-list{margin-top:0}
.prow h3{font-size:clamp(1.35rem,2.5vw,1.85rem);margin-bottom:14px}
.prow p{color:var(--secondary)}
.prow .btn{margin-top:6px}

/* photo-led service imagery (preflight 12a expects .svc-img on the homepage) */
.svc-img{position:relative;overflow:hidden;background:var(--sc-low);aspect-ratio:4/3}
.svc-img img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.svc-img .cap{position:absolute;left:0;right:0;bottom:0;z-index:3;
  background:rgba(26,26,26,.82);color:rgba(255,255,255,.9);
  font-family:var(--font-mono);font-size:.66rem;letter-spacing:.08em;
  padding:9px 14px;text-transform:uppercase}
.frame{position:relative;overflow:hidden;background:var(--sc-low)}
.frame img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* generated illustrative images are lit far more cinematically than a phone
   photo, so they get pulled back toward the site grade rather than being left
   to out-glamour the real work */
img.gen{filter:brightness(1.08) saturate(.88) contrast(.97)}
/* a generated illustrative image that has not landed yet must leave no grey box */
.gfr{position:relative;overflow:hidden;line-height:0}
.gfr img{width:100%;height:100%;object-fit:cover;display:block}
.gfr.fixed{aspect-ratio:4/3;background:var(--sc-low)}
.gfr.fixed img{position:absolute;inset:0}
.gfr.band{aspect-ratio:21/9;background:var(--sc-low)}
.gfr.band img{position:absolute;inset:0}
/* The generated set is NOT one shape: some files come back 1600x900 and some
   1080x1440. The SLOT owns the ratio and object-fit crops to it, so a portrait
   file can never stretch a row into a column. Never trust the source ratio. */
.gfr.row{aspect-ratio:4/3;background:var(--sc-low)}
.gfr.row img{position:absolute;inset:0}
.gfr.row:empty,.gfr.band:empty,.gfr.fixed:empty{display:none}
/* A caption must never outlive the image it captions. `onerror="this.remove()"`
   empties the .gfr, which :empty then hides, but the .gen-note that follows it
   survived and rendered alone as a stray "ILLUSTRATIVE IMAGE" line above the
   heading. the client reported it twice. Hide the note whenever its image is gone. */
.gfr:empty + .gen-note{display:none}

/* Quote form failure notice. Injected by site.js only when a submission really
   failed, so a lost lead is recoverable by phone instead of silently dropped. */
.qfail{display:none;margin:0 0 18px;padding:14px 16px;background:#FDEDED;
  border-left:4px solid var(--err);color:var(--ink);font-size:.9rem;line-height:1.5}
.qfail strong{display:block;margin-bottom:4px}
.qfail a{display:inline-block;margin:8px 14px 0 0;color:var(--ink);font-weight:600;
  text-decoration:underline;text-underline-offset:3px}
/* the illustrative label. Small, mono, low contrast, and it always sits
   immediately under the image it describes so it reads as a caption. */
.gen-note{font-family:var(--font-mono);font-size:.62rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--secondary);margin-top:8px;display:block}
/* a supporting image partway down a guide. Sits in the prose measure, no mitre:
   the only mitre on a guide is its lead image, which is what makes that one an
   event rather than furniture. */
.gfig{margin:34px 0;padding:0}
.gfig .gfr{aspect-ratio:16/9;background:var(--sc-low)}
.gfig .gfr img{position:absolute;inset:0}
.gfig figcaption{font-family:var(--font-mono);font-size:.62rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--secondary);margin-top:8px}
.gfig .gfr:empty{display:none}
.gfig .gfr:empty + figcaption{display:none}

.check-list{list-style:none;display:flex;flex-direction:column;gap:11px;margin:18px 0 22px}
.check-list li{display:flex;align-items:flex-start;gap:11px;font-size:.92rem;
  color:var(--secondary)}
.check-list .svg-ico{width:19px;height:19px;color:var(--ok);flex:none;margin-top:2px}

/* ==========================================================================
   THE WORKS SCHEDULE
   Thirteen services are not a menu, they are two divisions of a capability
   schedule. Thirteen tiles reads as a bloke who will do anything. Thirteen
   rows in a schedule reads as documentation. Same information, opposite claim.
   Deliberately the calmest, most typographic block on the page.
   ========================================================================== */
.sched{background:var(--white)}
.sched-rows{width:100%;padding-inline:max(22px,calc((100% - var(--maxw))/2))}
.division + .division{margin-top:56px}
.div-head{display:flex;align-items:baseline;gap:16px;padding-bottom:14px;
  border-bottom:2px solid var(--primary)}
.div-head .dnum{font-family:var(--font-mono);font-size:.78rem;letter-spacing:.12em;
  color:var(--accent-ink)}
.div-head h3{font-size:clamp(1.15rem,2vw,1.4rem);text-transform:uppercase;
  letter-spacing:.02em}
.div-head .dcount{margin-left:auto;font-family:var(--font-mono);font-size:.66rem;
  letter-spacing:.14em;text-transform:uppercase;color:var(--secondary)}
.srow{display:grid;grid-template-columns:42px 26px minmax(146px,1.05fr) 2fr 30px;
  align-items:baseline;gap:18px;padding:19px 0;border-bottom:1px solid var(--sc);
  transition:background .14s}
.srow:hover{background:var(--sc-lowest)}
.srow .rn{font-family:var(--font-mono);font-size:.72rem;color:var(--secondary)}
/* the trade mark, so a row is identified by a drawing rather than by a second
   line of description. It is why the scope text on each row could be cut. */
.srow .rm{align-self:center;color:var(--secondary);display:flex}
.srow .rm .svg-ico{width:22px;height:22px}
.srow:hover .rm{color:var(--accent-ink)}
.srow .rt{font-family:var(--font-display);font-weight:700;font-size:1.02rem;
  color:var(--primary)}
.srow .rs{font-size:.88rem;color:var(--secondary)}
.srow .ra{justify-self:end;align-self:center;color:var(--accent-ink);display:flex}
.srow:hover .rt{color:var(--accent-ink)}
.srow:hover .ra{transform:translateX(3px)}
.srow .ra .svg-ico{width:18px;height:18px;transition:transform .14s}

/* --- audience panels ------------------------------------------------------ */
.aud{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--sc)}
.aud .pan{background:var(--sc-low);padding:30px 26px}
.aud .pan .n{font-family:var(--font-mono);font-size:.68rem;letter-spacing:.14em;
  color:var(--accent-ink);display:block;margin-bottom:14px}
.aud .pan h3{margin-bottom:10px}
.aud .pan p{font-size:.88rem;color:var(--secondary)}

/* --- full bleed proof band ------------------------------------------------ */
.pband{position:relative;background:var(--ink);color:#fff;overflow:hidden;
  min-height:clamp(360px,52vh,520px);display:flex;align-items:flex-end}
.pband>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:1}
/* flat --primary at 62%, never a gradient scrim. A gradient scrim is the
   single fastest template tell in the whole reference board. */
.pband::before{content:"";position:absolute;inset:0;background:rgba(40,40,40,.62);z-index:2}
.pband .wrap{position:relative;z-index:3;padding-top:60px;padding-bottom:44px;width:100%}
.pband h2{color:#fff;max-width:16ch}
.pband p{color:rgba(255,255,255,.78);max-width:40em;margin-top:14px}
.pband .pcap{display:inline-block;margin-top:24px;background:var(--accent);
  color:var(--primary);font-family:var(--font-mono);font-size:.68rem;letter-spacing:.12em;
  text-transform:uppercase;padding:10px 20px}

/* The wide image-above-text band (iband) was REMOVED site-wide on 2026-07-30:
   the client rejected the shape itself ("massive image above the text ... looks out of
   place"). The standard side-by-side .prow row is the only diagnosis-row shape.
   Do not reintroduce an image-over-text band, full-bleed or contained. */

/* ==========================================================================
   WORK STRIP  (.wstrip)  - homepage only
   Three real photographs run edge to edge on one hairline, between the two
   longest text blocks on the page. No cards, no headings, mono captions only.
   It exists so the reader gets a look at the actual work before being asked to
   read another list.
   ========================================================================== */
.wstrip{display:grid;grid-template-columns:repeat(3,1fr);gap:2px;background:var(--sc);
  width:100%;margin-top:clamp(44px,5.5vw,76px)}
.wstrip .ws{position:relative;overflow:hidden;aspect-ratio:4/3;background:var(--sc-low);
  line-height:0;display:block}
.wstrip .ws img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.wstrip .ws span{position:absolute;left:0;right:0;bottom:0;z-index:3;
  background:rgba(26,26,26,.82);color:rgba(255,255,255,.9);
  font-family:var(--font-mono);font-size:.64rem;letter-spacing:.08em;
  text-transform:uppercase;padding:9px 14px;line-height:1.5}
.wstrip-note{display:flex;justify-content:space-between;align-items:baseline;
  gap:18px;flex-wrap:wrap;margin-top:16px;font-family:var(--font-mono);
  font-size:.68rem;letter-spacing:.12em;text-transform:uppercase;color:var(--secondary)}
.wstrip-note a{color:var(--accent-ink);display:inline-flex;align-items:center;gap:8px}
.wstrip-note a .svg-ico{width:16px;height:16px}

/* PROOF strip variants. Some trades have fewer than three photographs of their
   OWN work (PHOTOS.md coverage table: fencing 0, office cleaning 0, concreting 1),
   and a three-column grid with two children leaves a hole that reads as a missing
   image. The count is honest, so the grid has to be too. */
.wstrip.two{grid-template-columns:repeat(2,1fr)}
/* width:auto, not the inherited width:100%: 100% plus the margins overflowed
   the viewport (22px at 768, 18px at 320). Found 2026-07-30 compliance sweep. */
.wstrip.one{grid-template-columns:1fr;max-width:820px;width:auto;
  margin-inline:max(22px,calc((100% - var(--maxw))/2))}

/* ==========================================================================
   FIND-IT two-column shell (the client 2026-07-30): image LEFT, router RIGHT.
   The image is supplied by the imagery-architecture pass; until the file
   exists onerror removes .find-img and the router takes the full width.
   Inside the narrower right column the section intro goes back to left
   alignment (centred fought the measure at 44vw). */
.find-grid{display:grid;grid-template-columns:44fr 56fr;gap:clamp(26px,4vw,54px);align-items:center}
/* the client 2026-07-30 ("make the image fit that section, like probably taller"):
   at the two-column register the image frame STRETCHES to the router column's
   full height so the split reads as two equal columns, not a small landscape
   floating beside a tall chip stack. The 16:9 source is cropped by the slot
   (cover); object-position keeps the worker at the door centred in the
   vertical crop. Mobile stacked view keeps the normal landscape ratio. */
@media(min-width:901px){
  .find-grid{align-items:stretch}
  .find-grid .find-router{align-self:center}
  .find-img .gfr.row{height:100%;aspect-ratio:auto;min-height:360px}
  .find-img .gfr.row img{object-position:44% 50%}
}
.find-grid:not(:has(.find-img)){grid-template-columns:1fr}
/* left alignment only once the image column exists; until the imagery pass
   lands the file, the router is full-width and the intro stays centred */
.find-grid:has(.find-img) .find-router .sec-head{margin-left:0;margin-right:0;text-align:left;max-width:none}
.find-grid:has(.find-img) .find-router .sec-head .rule{margin-left:0;margin-right:0;transform:none}
.find-grid:has(.find-img) .find-router .sec-head p{margin-left:0}
.find-router .symps .sy{flex:1 1 44%}
/* an odd last chip spans its row alone, so its content centres rather than
   hanging on the left (the client 2026-07-30) */
.symps .sy:last-child:nth-child(odd){justify-content:center}
@media(max-width:900px){.find-grid{grid-template-columns:1fr}}

/* ==========================================================================
   SYMPTOM ROUTER  (.symps / .sy)
   The works schedule indexes by TRADE NAME, so a customer with a cracked tile
   has to work out for themselves that the answer lives under "Tiling". This
   indexes the same thirteen pages by the words the customer would use out loud.
   A tag field on purpose: the schedule directly below is hairline rows and no
   two adjacent sections may share a component archetype (DIRECTION S4), and a
   thirteen-tile service grid is the "jumble sale" shape DIRECTION disqualified.
   ========================================================================== */
.symps{display:flex;flex-wrap:wrap;gap:1px;background:var(--sc);
  border-top:2px solid var(--primary);border-bottom:1px solid var(--sc)}
.sy{display:inline-flex;align-items:center;gap:11px;background:var(--white);
  padding:15px 17px;font-size:.9rem;color:var(--primary);flex:1 1 auto}
.sy .svg-ico{width:19px;height:19px;color:var(--secondary);flex:none}
.sy .sy-a{width:16px;height:16px;color:var(--accent-ink);transition:transform .14s}
.sy:hover{background:var(--accent-soft);color:var(--accent-ink)}
.sy:hover .svg-ico{color:var(--accent-ink)}
.sy:hover .sy-a{transform:translateX(3px)}
@media(max-width:760px){.sy{flex:1 1 44%}.sy span{flex:1}}
@media(max-width:400px){.sy{flex:1 1 100%}}
/* the client 2026-07-30: a chip left alone on the last row centres its content
   (structural: the odd last chip in the 2-per-row router column). */
.find-router .symps .sy:last-child:nth-child(odd){justify-content:center;flex-grow:1}
.find-router .symps .sy:last-child:nth-child(odd) span{flex:0 0 auto}
/* 2026-07-30 (wiring/packaging pass): index.html was the ONE page overflowing at 320px
   (+9px). Cause: the intended narrow-phone rule two lines up, `@media(max-width:400px)
   {.sy{flex:1 1 100%}}` (0,1,0), is outranked by `.find-router .symps .sy{flex:1 1 44%}`
   (0,3,0) further up the file, so the router chips never left the two-up register on a
   320 screen; the odd last chip's `span{flex:0 0 auto}` above then refused to shrink and
   pushed its trailing arrow icon past the viewport edge. Re-stated at matching
   specificity INSIDE the query so it actually applies, with min-width:0 so a long chip
   label can shrink instead of forcing min-content. */
@media(max-width:400px){
  .find-router .symps .sy{flex:1 1 100%;min-width:0}
  .find-router .symps .sy span{flex:1 1 auto;min-width:0}
  .find-router .symps .sy:last-child:nth-child(odd){justify-content:flex-start}
  .find-router .symps .sy:last-child:nth-child(odd) span{flex:1 1 auto}
}

/* the rating is a strip beside the work, not a section heading of its own */
.rate-note{margin-top:12px;font-size:.86rem;color:var(--secondary);max-width:44em}
.wgrid{margin-top:30px}

/* ==========================================================================
   CONTAINED PHOTO PAIR (.pair2) - two real-photo frames side by side INSIDE
   the wrap at the slab-row landscape ratio, stage tags on the tiles, one-line
   mono labels. Replaces full-bleed 2-tile strips (the client 2026-07-30: no image
   section may outgrow the screen; band shape is dead).
   ========================================================================== */
/* the client 2026-07-31: the pair sat at HALF the height of every other row image
   (measured: pair box 538x201 against a single row frame's 538x404), because
   the 4/3 ratio was on each HALF rather than on the pair. A pair is one frame's
   worth of page, split in two, so the RATIO BELONGS TO THE CONTAINER and each
   half just fills it. Now the pair occupies the same box as any other row
   image, which is what makes the rows read as one system. */
.pair2{display:grid;grid-template-columns:1fr 1fr;gap:2px;background:var(--sc);
  margin-top:30px;aspect-ratio:4/3}
.pair2 .pf{position:relative;overflow:hidden;height:100%;background:var(--sc-low)}
.pair2 .pf img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.pair2 .pf .bdg{position:absolute;top:0;left:0;z-index:4;font-family:var(--font-mono);
  font-size:.62rem;letter-spacing:.12em;text-transform:uppercase;
  padding:7px 12px;background:var(--accent);color:var(--primary)}
.pair2 .pf span.cap{position:absolute;left:0;right:0;bottom:0;z-index:3;
  background:rgba(26,26,26,.82);color:rgba(255,255,255,.9);
  font-family:var(--font-mono);font-size:.64rem;letter-spacing:.08em;
  text-transform:uppercase;padding:9px 14px;line-height:1.5}
/* stacked on a phone, so the container ratio has to go or two frames get
   squashed into one frame's height. Each half takes the ratio back. */
@media(max-width:640px){.pair2{grid-template-columns:1fr;aspect-ratio:auto}
  .pair2 .pf{aspect-ratio:4/3;height:auto}}

/* ==========================================================================
   REVIEWS CAROUSEL (.revs) - house component (Bedrock skeleton), Mitre &
   Ledger skin: zero radius, hairline borders, SQUARE initial chips (charcoal
   chip, gold initial), gold square arrows, NO per-card star rows (hipages
   publishes no per-review stars; the real "Verified customer / Hired job"
   badge sits in that slot). Cards are verbatim hipages texts
   (REVIEWS-HIPAGES.md). 3-up desktop / 2-up tablet / peek mobile.
   ========================================================================== */
.rev-pill{display:flex;align-items:center;justify-content:center;gap:10px;
  margin:-14px auto 26px;font-family:var(--font-mono);font-size:.72rem;
  letter-spacing:.12em;text-transform:uppercase;color:var(--secondary)}
.rev-pill .stars{display:inline-flex;gap:3px;color:var(--accent-ink)}
.rev-pill .stars .svg-ico{width:13px;height:13px}
.rev-pill b{font-family:var(--font-display);font-weight:900;font-size:1.15rem;color:var(--primary)}
.revs{max-width:1160px;margin:0 auto}
.rev-row2{display:flex;align-items:stretch;gap:10px}
.rev-viewport{overflow:hidden;flex:1}
.rev-track{display:flex;transition:transform .55s cubic-bezier(.4,0,.2,1);will-change:transform}
.rev-cell{min-width:88%;box-sizing:border-box;padding:1px;display:flex}
@media(min-width:641px){.rev-cell{min-width:50%}}
@media(min-width:1001px){.rev-cell{min-width:33.3333%}}
.rev-card{flex:1;box-sizing:border-box;margin:0 6px;padding:22px 22px 18px;background:var(--white);
  border:1px solid var(--sc);display:flex;flex-direction:column;gap:12px;min-height:230px}
.rev-card .vbadge{display:inline-flex;align-items:center;gap:7px;font-family:var(--font-mono);
  font-size:.6rem;letter-spacing:.1em;text-transform:uppercase;color:var(--accent-ink)}
.rev-card .vbadge .svg-ico{width:14px;height:14px;color:var(--ok);flex:none}
.rev-card blockquote{margin:0;font-size:.93rem;line-height:1.62;color:var(--primary);
  display:-webkit-box;-webkit-line-clamp:7;-webkit-box-orient:vertical;overflow:hidden}
.rev-card .who{display:flex;align-items:center;gap:11px;margin-top:auto}
.rev-card .who .av{width:38px;height:38px;flex:none;display:grid;place-items:center;
  background:var(--primary);color:var(--accent);font-family:var(--font-display);
  font-weight:900;font-size:1rem}
.rev-card .who b{display:block;color:var(--primary);font-size:.88rem;line-height:1.25}
.rev-card .who .src{display:block;font-family:var(--font-mono);font-size:.62rem;
  letter-spacing:.08em;text-transform:uppercase;color:var(--secondary);margin-top:3px;
  text-decoration:underline;text-underline-offset:2px}
.rev-nav{flex:none;align-self:center;width:44px;height:44px;border:1px solid var(--sc);
  background:var(--white);color:var(--accent-ink);display:grid;place-items:center;cursor:pointer}
.rev-nav:hover{background:var(--primary);border-color:var(--primary);color:var(--accent)}
.rev-nav .svg-ico{width:19px;height:19px}
.rev-dots{display:flex;justify-content:center;gap:7px;margin-top:18px}
/* 24px touch targets (Lighthouse target-size); the visible square stays 9px */
.rev-dot{width:24px;height:24px;border:0;background:none;padding:0;cursor:pointer;position:relative}
.rev-dot::after{content:"";position:absolute;inset:7px;border:1px solid var(--secondary)}
.rev-dot.on::after{background:var(--accent);border-color:var(--accent)}
@media(max-width:640px){.rev-nav{display:none}.rev-card{min-height:0}}

/* ==========================================================================
   SERVICE SLIDER (.ssl) - the client 2026-07-30: the homepage works schedule
   converted to an auto-rotating service slider on the SAME house carousel
   engine as the reviews ([data-reviews] in site.js). Card = the service's own
   hero photo full bleed, charcoal caption plate (zero radius), tiny gold mono
   division label + white service name. Whole card links to the page.
   ========================================================================== */
.ssl-card{position:relative;display:block;flex:1;margin:0 6px;overflow:hidden;
  background:var(--sc-low);aspect-ratio:4/3}
.ssl-card>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  transition:transform .5s ease}
.ssl-card:hover>img{transform:scale(1.04)}
.ssl-plate{position:absolute;left:0;right:0;bottom:0;z-index:2;
  background:rgba(26,26,26,.85);padding:11px 16px 12px;display:flex;
  flex-direction:column;gap:3px}
.ssl-plate i{font-style:normal;font-family:var(--font-mono);font-size:.58rem;
  letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}
.ssl-plate b{font-family:var(--font-display);font-weight:700;color:#fff;
  font-size:1rem;line-height:1.2}

/* ==========================================================================
   FULL-WIDTH CTA STRIP (.fw-band) - the client 2026-07-30, replacing the retired
   "corners" proof band. Slim strip: real photo ground, flat charcoal overlay
   (never a gradient), one headline, one gold CTA. The page's other dark band
   (why-us) is an image split, so this is deliberately the OTHER shape. ~230px
   tall at desktop. No mitre: the strip stays inside the cut budget.
   ========================================================================== */
.fw-band{position:relative;background:var(--ink);color:#fff;overflow:hidden}
.fw-band>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:1}
.fw-band::before{content:"";position:absolute;inset:0;background:rgba(40,40,40,.74);z-index:2}
.fw-band .wrap{position:relative;z-index:3;display:flex;align-items:center;
  justify-content:space-between;gap:22px 34px;flex-wrap:wrap;
  padding-top:52px;padding-bottom:52px}
.fw-band h2{color:#fff;font-size:clamp(1.45rem,2.6vw,2rem);max-width:24ch}
@media(max-width:560px){.fw-band .btn{width:100%}}

/* the merged dark why-us split (the client 2026-07-30): checks + one audience line
   on the dark ground */
.sec.dark .check-list li{color:rgba(255,255,255,.8)}
.aud-line{margin:4px 0 24px;font-family:var(--font-mono);font-size:.68rem;
  letter-spacing:.12em;text-transform:uppercase;color:rgba(255,255,255,.55)}

/* ==========================================================================
   SECTION CTA ROW  (.secta)
   The early capture block was removed on the client's instruction, so the way a
   customer acts from anywhere on the page is a CTA within reach plus the mobile
   sticky bar. Measured at 390: the homepage had one 6,875px stretch (8.1
   screens) and every service page a ~3,400px stretch with nothing that starts
   an enquiry. One of these at the foot of each long section closes it.
   ========================================================================== */
/* CTA follows the section (the client 2026-07-30): every .secta sits in a centred or
   single-column band, so it centres; in-row CTAs are plain .btn inside .prow-txt. */
.secta{display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:center;margin-top:32px}
@media(max-width:560px){.secta{flex-direction:column;align-items:stretch}
  .secta .btn{width:100%;justify-content:center}}
/* six stacked 4:3 frames cost ~1,750px of scroll on a phone for no extra
   information, so the work grid becomes the same swipe rail the strip uses */
@media(max-width:640px){
  /* SPECIFICITY TIE, and this one shipped broken (the client/audit 2026-07-30): the
     element is class="gallery-grid wgrid", and `@640 .gallery-grid{grid-template-
     columns:1fr}` sits LATER in this file at equal specificity (0,1,0), so it won
     and reinstated ONE explicit track. With grid-auto-flow:column the first tile
     landed in that explicit 1fr track, which resolved to 0px inside a scrolling
     container: tile 1 rendered at zero width and 5 of 6 photos were unreachable.
     Compounded on .wstrip the same way. Fixed by raising specificity so the rail
     wins outright rather than by !important. */
  .wgrid.gallery-grid{grid-template-columns:none;grid-auto-flow:column;
    grid-auto-columns:78%;overflow-x:auto;scroll-snap-type:x mandatory;
    scrollbar-width:none;-webkit-overflow-scrolling:touch}
  .wgrid::-webkit-scrollbar{display:none}
  .wgrid .frame{scroll-snap-align:start}
  /* A rail nobody knows to swipe is a one-photo section. The last tile is held
     back from the edge so the next frame always peeks, which is the affordance
     itself (the client's standing "show on mobile that this is scrollable" rule). */
  .wgrid.gallery-grid{padding-right:22%}
  .wstrip{padding-right:24%}
}


/* ==========================================================================
   TIGHT EDITORIAL BLOCK  (.stmt)
   Replaces the three promise cards, which were a third N-across panel grid on
   one page and restated the FAQ. One narrow measure, one large statement, and
   the promises as mono one-liners on hairlines. The calmest thing on the page
   after the schedule.
   ========================================================================== */
/* RESTRUCTURED 2026-07-30 (the client: "just too empty" on the left side, on every
   page carrying this block). It was a 2-col grid with a SHORT statement in the
   left column and the promise list in the right, so the left column ran ~40%
   dead ground on all 13 service pages. The honest options were an image per page
   (no on-subject unused frames exist for most of them, and reusing a page's own
   frame now fails the duplicate check) or removing the empty column. Statement
   goes full width as the block's own lead; promises run as TWO columns beneath
   it, which fills the measure and needs no new assets. */
.stmt{display:block;margin-top:38px;border-top:2px solid var(--primary);padding-top:30px}
.stmt .sq{font-family:var(--font-display);font-weight:700;
  font-size:clamp(1.35rem,2.6vw,1.95rem);color:var(--primary);line-height:1.14;
  letter-spacing:-.014em;max-width:34ch;margin-bottom:26px}
.stmt ul{list-style:none;display:grid;grid-template-columns:1fr 1fr;
  gap:0 clamp(30px,4.6vw,64px);align-items:start}
.stmt li{display:grid;grid-template-columns:20px 1fr;gap:13px;align-items:start;
  padding:14px 0;border-bottom:1px solid var(--sc);font-size:.9rem;color:var(--secondary)}
.stmt li:first-child{padding-top:0}
.stmt li b{color:var(--primary);font-weight:600}
.stmt li .svg-ico{width:19px;height:19px;color:var(--accent-ink);margin-top:1px}

/* --- process rail --------------------------------------------------------- */
.rail{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border-top:2px solid var(--primary)}
.rail .step{padding:26px 24px 6px 0;border-right:1px solid var(--sc)}
.rail .step:last-child{border-right:0}
.rail .step:not(:first-child){padding-left:24px}
.rail .step .n{font-family:var(--font-mono);font-size:.72rem;letter-spacing:.12em;
  color:var(--accent-ink);display:block;margin-bottom:12px}
.rail .step h3{margin-bottom:8px}
.rail .step p{font-size:.88rem;color:var(--secondary)}

/* --- before / after pair tiles -------------------------------------------- */
/* PHOTOS.md: three genuine pairs, all GALLERY grade not slider grade. A wipe
   slider needs near pixel-aligned framing; only the garden bed pair would
   carry one, and one slider on its own is not worth the component. So: two
   labelled tiles, captioned to exactly what is visible. */
.ba{display:grid;grid-template-columns:1fr 1fr;gap:2px;background:var(--sc);
  border:0;border-radius:0}
.ba.wide{max-height:62vh}
.ba .half{position:relative;overflow:hidden;aspect-ratio:1;background:var(--sc-low)}
.ba.wide .half{aspect-ratio:4/3}
.ba .half img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.ba .bdg{position:absolute;top:0;left:0;z-index:4;font-family:var(--font-mono);
  font-size:.64rem;letter-spacing:.14em;text-transform:uppercase;padding:7px 13px}
.ba .half.b .bdg{background:var(--primary);color:#fff}
.ba .half.a .bdg{background:var(--accent);color:var(--primary)}
/* captions are LABELS, not paragraphs (the client 2026-07-30): one short mono line max */
.ba-cap{margin-top:10px;font-family:var(--font-mono);font-size:.66rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--secondary)}
.ba-grid{display:grid;grid-template-columns:1fr;gap:44px}

/* --- gallery -------------------------------------------------------------- */
/* Register decision 2026-07-30 (CSS-REQUESTS #1): stays 3-col at desktop.
   Topclass's dedicated gallery page runs the same 3-col .gallery-grid (its
   4-col .imgal is a homepage-only component), so parity keeps 3-col here and
   the tagged pair adjacency tuned for 3-col holds. Phones stay 1-col, same
   as topclass, so the captions stay readable. */
.gallery-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:2px;background:var(--sc)}
.gallery-grid .frame{aspect-ratio:4/3;background:var(--sc-low)}
/* CSS-REQUESTS #2: 26 tiles on a 3-col register leave one empty beige cell in
   the last row. When the tile count is ≡2 mod 3, the LAST tile spans the two
   remaining columns: the row height stays on the register (set by its 4:3
   neighbour) and the finale tile renders wide instead of leaving a hole.
   Desktop only: at the 2-col and 1-col registers 26 divides cleanly. */
@media(min-width:901px){
  .gallery-grid .frame:last-child:nth-child(3n+2){grid-column:span 2;aspect-ratio:auto}
}
.gallery-grid .gcap{position:absolute;left:0;right:0;bottom:0;z-index:3;
  background:rgba(26,26,26,.84);color:rgba(255,255,255,.92);font-size:.78rem;
  padding:10px 14px;line-height:1.45}

/* --- reviews -------------------------------------------------------------- */
/* the rating reads as ledger cells rather than five-point stars: a star cannot
   be built from 53deg diagonals, and a borrowed star glyph is the one thing in
   the icon set that would still look like an icon font. Four full cells and one
   half cell, against the printed 4.6 and the hipages attribution. */
.rate{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.rate .stars{display:flex;gap:4px;color:var(--accent-ink)}
.rate .stars .svg-ico{width:21px;height:21px}
.rate b{font-family:var(--font-display);font-weight:900;font-size:1.5rem}
.rate .src{font-family:var(--font-mono);font-size:.7rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--secondary)}

/* --- areas + map ---------------------------------------------------------- */
#covmap{height:420px;width:100%;background:var(--sc-low);z-index:1;position:relative;
  border:1px solid var(--sc)}
.leaflet-container{font-family:var(--font)}

/* --- FAQ ------------------------------------------------------------------ */
.faq-wrap{max-width:820px;margin-inline:auto;display:flex;flex-direction:column;
  border-top:2px solid var(--primary)}
.faq{border-bottom:1px solid var(--sc)}
.faq button{width:100%;display:flex;justify-content:space-between;align-items:flex-start;
  gap:18px;padding:20px 2px;text-align:left}
.faq button h3{font-size:1rem;font-weight:700}
.faq .chev{color:var(--accent-ink);transition:transform .24s;flex:none;margin-top:2px}
.faq.open .chev{transform:rotate(180deg)}
.faq .ans{max-height:0;overflow:hidden;transition:max-height .3s ease}
.faq.open .ans{max-height:460px}
.faq .ans p{padding:0 2px 20px;color:var(--secondary);font-size:.92rem;max-width:66ch}

/* --- quote band + form ---------------------------------------------------- */
/* mirrors the cut the hero opened, closing the shape at the bottom of the page */
.qband{position:relative;background:var(--ink);color:#fff;padding:0 0 78px;
  margin-top:0}
/* The seam leg was the full --m (up to 236px) ON TOP of both sections paddings,
   which read as a screen of empty beige before the form (the client: "get rid of that
   useless space"). Compact leg here; the cut still reads. */
.qband-cut{height:clamp(76px,9vw,124px);background:var(--ink);
  clip-path:polygon(0 100%, calc(100% - var(--mr)) 100%, 100% 0, 100% 100%)}
/* the cut IS the transition, so the section above it does not also need a full
   bottom padding or the page opens a hole before the closing band */
.sec:has(+ .qband-cut){padding-bottom:14px}
.qband h2{color:#fff}
.qband .lead{color:rgba(255,255,255,.76)}
/* the client 2026-07-30: the form card centres vertically against the taller left
   column at desktop ("the quote has to be in the middle"); left column keeps
   its top flow. Mobile stacking unchanged (single column).
   The centring must target the GRID CHILD: the form sits inside a plain div
   wrapper (form + form-done), so `.qform{align-self:center}` was a no-op.
   Fixed 2026-07-30 (job 2): centre the wrapper via :has(); harmless where
   unsupported (falls back to top-aligned, the pre-fix behaviour). */
/* The form card CENTRES against the left column (the client 2026-07-30: "the quote
   has to be in the middle... so it is centered and visually pleasing"). The card
   height changes per wizard step, so centre rather than top-align and let it
   settle around the column midpoint. */
.qgrid{display:grid;grid-template-columns:0.85fr 1.15fr;gap:clamp(30px,4vw,64px);
  align-items:center;padding-top:20px}
.qgrid>div:has(.qform){align-self:center}
.qform{background:var(--white);color:var(--ink);padding:clamp(24px,3vw,38px)}
/* the card names itself. On mobile the form now leads the section (.qside is
   ordered below it), so without this the first thing a person met was a row of
   numbered step chips with nothing saying what they were starting. */
/* the client 2026-07-31: the heading sits ABOVE the white card, not inside it, so the
   section names itself on the dark band and the card starts on the first
   question. It lives in the FORM's grid cell (not .qside) so the mobile order
   swap carries it with the form. */
/* sized as the biggest heading on the page bar the hero h1: this form is the
   site's main lead capture, so it outranks the ordinary section heads
   (the client 2026-07-31, "bigger bolder since it is the main lead capture"). */
.qform-title{font-family:var(--font-display);font-weight:900;
  font-size:clamp(2.15rem,4.4vw,3.05rem);color:#fff;letter-spacing:-.03em;
  margin:0 0 24px;line-height:1.04;text-wrap:balance;text-align:center}
/* the accent underline sits under the WORDS, not the column, so it is drawn as a
   background on an inline span rather than a border on the block. clone keeps it
   correct if the heading wraps to two lines on a phone. */
/* the mark carries the emphasis in accent gold and sits OUTSIDE the underlined
   span, so the gold rule stops with the words rather than running under it. */
.qform-title .ex{color:var(--accent);font-weight:inherit;font-style:normal}
.qform-title span{background:linear-gradient(var(--accent),var(--accent)) 0 96%/100% .07em no-repeat;
  -webkit-box-decoration-break:clone;box-decoration-break:clone;padding-bottom:.06em}
@media(max-width:900px){.qform-title{margin-bottom:16px}}

/* --- choice tiles (the client 2026-07-31, "make first page have these options") ---
   A radio group presented as tappable tiles: icon over label, whole tile is the
   hit target. Replaces the plain radio rows for the two questions that actually
   change how a job is quoted - who is asking, and what kind of property. Zero
   radius per the direction; the selected state is a solid accent border plus a
   tint, never a shadow. */
.tiles{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.tiles.t3{grid-template-columns:repeat(3,1fr)}
.tile{position:relative;display:block;cursor:pointer}
.tile input{position:absolute;opacity:0;width:1px;height:1px}
.tile span{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:7px;text-align:center;min-height:84px;padding:14px 10px;
  border:1px solid var(--sc);background:var(--white);
  font-size:.86rem;font-weight:600;color:var(--primary);
  transition:border-color .14s,background .14s}
.tile .svg-ico{width:22px;height:22px;color:var(--secondary);transition:color .14s}
.tile:hover span{border-color:var(--secondary)}
.tile input:checked + span{border-color:var(--accent-ink);background:var(--sc-lowest);
  box-shadow:inset 0 0 0 1px var(--accent-ink)}
.tile input:checked + span .svg-ico{color:var(--accent-ink)}
.tile input:focus-visible + span{outline:2px solid var(--accent-ink);outline-offset:2px}
/* an unanswered required group, flagged by the wizard validator */
.tiles.missing .tile span{border-color:var(--error,#ba1a1a)}
@media(max-width:400px){.tiles.t3{grid-template-columns:1fr 1fr}
  .tile span{min-height:74px;font-size:.8rem}}
.qside .qpoint{border-top:1px solid rgba(255,255,255,.16);padding:16px 0}
.qside .qpoint b{display:block;font-family:var(--font-display);font-size:.96rem;color:#fff}
.qside .qpoint span{font-size:.86rem;color:rgba(255,255,255,.66)}
.qphone{display:block;font-family:var(--font-display);font-weight:900;
  font-size:clamp(1.6rem,3.4vw,2.4rem);color:var(--accent);letter-spacing:-.02em;
  margin:6px 0 4px}

.wiz-progress{display:flex;align-items:center;gap:8px;margin-bottom:26px}
.wiz-progress span{display:flex;align-items:center;gap:7px;font-family:var(--font-mono);
  font-size:.64rem;letter-spacing:.1em;text-transform:uppercase;color:var(--secondary)}
.wiz-progress span i{width:22px;height:22px;display:grid;place-items:center;
  background:var(--sc);color:var(--secondary);font-style:normal;font-size:.68rem}
.wiz-progress span.on{color:var(--primary)}
.wiz-progress span.on i{background:var(--accent);color:var(--primary)}
.wiz-progress em{flex:1;height:1px;background:var(--sc)}
.wstep{display:none}
.wstep.on{display:block}
.wiz-q{font-family:var(--font-display);font-weight:700;font-size:1.08rem;
  color:var(--primary);margin-bottom:18px}
/* the contact-first reassurance line under the step-1 question (PLAYBOOK 03 §4:
   step 1 collects name/mobile so the lead is usable even if the visitor bails) */
.wiz-note{margin:-12px 0 18px;font-size:.84rem;color:var(--secondary)}
.field{margin-bottom:16px}
.field label{display:block;font-size:.84rem;font-weight:600;color:var(--primary);
  margin-bottom:7px}
.field label span{font-weight:400;color:var(--secondary)}
.field input,.field select,.field textarea{width:100%;padding:13px 14px;
  background:var(--sc-lowest);border:1px solid var(--sc);border-radius:0;font-size:.95rem}
.field input:focus,.field select:focus,.field textarea:focus{border-color:var(--accent-ink);
  outline:2px solid var(--accent-soft);outline-offset:0}
.field-2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.opts{display:flex;flex-wrap:wrap;gap:8px}
.opts label{display:inline-flex;align-items:center;min-height:44px;padding:10px 16px;
  border:1px solid var(--sc);background:var(--sc-lowest);font-size:.86rem;cursor:pointer;
  margin:0}
.opts label:has(input:checked){border-color:var(--primary);background:var(--accent-soft);
  color:var(--accent-ink);font-weight:600}
.opts input{position:absolute;opacity:0;width:0;height:0}
/* .field label{display:block} outranks a bare .drop, so the photo drop was
   rendering as a block and its two lines ran together. */
.field label.drop,.drop{display:flex;flex-direction:column;align-items:center;gap:6px;padding:26px 18px;
  border:2px dashed var(--sc);background:var(--sc-lowest);text-align:center;cursor:pointer;
  color:var(--primary)}
.drop:hover{border-color:var(--accent-ink);background:var(--accent-soft)}
.drop .svg-ico{width:30px;height:30px;color:var(--accent-ink)}
.drop b{font-family:var(--font-display);font-size:.98rem}
.drop span{font-size:.82rem;color:var(--secondary)}
.thumbs{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap}
.thumbs img{width:70px;height:70px;object-fit:cover;border:1px solid var(--sc)}
/* Back on the left, forward on the right (the client 2026-07-31). The forward action
   is the one being asked for, so it takes the end of the row and holds that
   position whether or not Back is present - `margin-left:auto` on the forward
   pair does that without needing a spacer element. */
.wiz-nav{display:flex;gap:10px;margin-top:22px;align-items:center}
.wiz-nav .wiz-next,.wiz-nav [type=submit]{margin-left:auto}
.wiz-err{margin-top:12px;color:var(--err);font-size:.85rem;display:none}
.wiz-err.show{display:block}
.form-foot{margin-top:14px;font-size:.78rem;color:var(--secondary)}
.form-foot a{text-decoration:underline}
.form-done{display:none;background:var(--white);padding:40px 30px;text-align:center}
.form-done.show{display:block}
.form-done .ok{width:54px;height:54px;margin:0 auto 16px;background:var(--ok);color:#fff;
  display:grid;place-items:center}
.form-done .ok .svg-ico{width:28px;height:28px;stroke-width:2.4}

/* --- prose (guides, legal) ------------------------------------------------ */
/* The measure stays at 68ch - centring the LINES of a privacy policy would wreck
   its readability - but the COLUMN is centred in the page. Left-aligned it sat
   hard against the left edge with the whole right half of a 1440 screen empty,
   which is what read as "not centred" (the client 2026-07-31). Applies to the guides
   too, which use the same component. */
.prose{max-width:68ch;margin-inline:auto;font-size:1rem;line-height:1.75;color:var(--secondary)}
.prose h2{font-size:clamp(1.4rem,2.4vw,1.8rem);margin:44px 0 14px;color:var(--primary)}
.prose h3{font-size:1.08rem;margin:30px 0 10px;color:var(--primary)}
.prose p{margin:0 0 1.1em}
.prose ul,.prose ol{margin:0 0 1.3em;padding-left:22px}
.prose li{margin-bottom:.5em}
.prose a{color:var(--accent-ink);text-decoration:underline;text-underline-offset:2px}
.prose strong{color:var(--primary)}
.prose .pull{font-family:var(--font-mono);font-size:.92rem;line-height:1.7;
  color:var(--primary);border-left:2px solid var(--accent);padding:4px 0 4px 20px;
  margin:30px 0}
.prose table{width:100%;border-collapse:collapse;margin:0 0 1.4em;font-size:.9rem}
.prose th,.prose td{text-align:left;padding:11px 12px;border-bottom:1px solid var(--sc)}
.prose thead th{background:var(--primary);color:#fff;font-family:var(--font-display)}
/* buttons inside prose must not inherit the prose link styling */
.prose a.btn{text-decoration:none;color:var(--primary)}
.prose a.btn-primary{color:#fff}
/* a VS/comparison table stays a real scrollable TABLE on mobile, never cards */
.vs-cards{overflow-x:auto;-webkit-overflow-scrolling:touch;position:relative}
.vs-cards table{min-width:560px}

/* --- guide cards ---------------------------------------------------------- */
.glist{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--sc)}
.gcard{background:var(--white);display:flex;flex-direction:column}
.gcard .frame{aspect-ratio:16/10}
.gcard .bd{padding:24px 22px 26px;display:flex;flex-direction:column;flex:1}
.gcard .k{font-family:var(--font-mono);font-size:.64rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--accent-ink);margin-bottom:10px}
.gcard h3{margin-bottom:9px;font-size:1.04rem}
.gcard p{font-size:.87rem;color:var(--secondary);flex:1}
.gcard .more{margin-top:16px;font-family:var(--font-mono);font-size:.68rem;
  letter-spacing:.12em;text-transform:uppercase;color:var(--accent-ink);
  display:inline-flex;align-items:center;gap:7px}
.gcard:hover .more{gap:11px}
.topic-rail{display:flex;gap:0;border-top:1px solid var(--sc);border-bottom:1px solid var(--sc);
  overflow-x:auto;scrollbar-width:none}
.topic-rail::-webkit-scrollbar{display:none}
.topic-rail span{flex:none;padding:15px 24px;font-family:var(--font-mono);font-size:.68rem;
  letter-spacing:.13em;text-transform:uppercase;color:var(--secondary);
  border-right:1px solid var(--sc);white-space:nowrap}
.topic-rail span:first-child{padding-left:0;color:var(--accent-ink)}

.feat{display:grid;grid-template-columns:1.1fr .9fr;gap:clamp(26px,4vw,56px);
  align-items:center}

/* --- footer --------------------------------------------------------------- */
.foot{background:var(--primary);color:rgba(255,255,255,.68);padding:60px 0 0;
  font-size:.88rem}
.foot h3,.foot h4{color:#fff;font-size:.76rem;font-family:var(--font-mono);
  font-weight:500;letter-spacing:.16em;text-transform:uppercase;margin-bottom:16px}
.foot-grid{display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr;gap:34px}
.foot a:hover{color:var(--accent)}
.foot ul{list-style:none;display:flex;flex-direction:column;gap:9px}
.foot-contact{display:flex;flex-direction:column;gap:9px}
.foot-contact a{display:inline-flex;align-items:center;gap:10px}
.foot-contact .svg-ico{width:17px;height:17px;color:var(--accent);flex:none}
/* box-sizing:border-box is global, so an img with a height AND padding gets
   squashed. The plate needs content-box. */
.foot .logo-img{box-sizing:content-box;height:38px;width:auto;background:#fff;
  padding:9px 13px;margin-bottom:20px;display:block}
.accred{display:flex;flex-wrap:wrap;gap:0;margin-top:46px;border-top:1px solid rgba(255,255,255,.14)}
.accred .a{padding:16px 22px 16px 0;font-family:var(--font-mono);font-size:.66rem;
  letter-spacing:.11em;text-transform:uppercase;color:rgba(255,255,255,.5);
  border-right:1px solid rgba(255,255,255,.14)}
.accred .a:last-child{border-right:0}
.accred .a:not(:first-child){padding-left:22px}
.foot-bot{margin-top:0;border-top:1px solid rgba(255,255,255,.14);padding:20px 0 26px;
  display:flex;flex-wrap:wrap;gap:8px 22px;font-size:.78rem;
  font-family:var(--font-mono);letter-spacing:.05em}

/* --- mobile sticky call bar ----------------------------------------------
   OFF by default (house rule: the client removed it on Brennan and Top Class), then
   opted IN for this client only, on mobile only. For a maintenance
   trade the call IS the conversion, and the teardown found we were the best
   made site in the category and the worst at asking for the business.
   Message sits beside Call because whoever is on the tools cannot answer a ringing
   phone with a drill in hand, and plenty of customers will not ring at all.
   ------------------------------------------------------------------------- */
/* SWITCHED OFF at the client's instruction, 2026-07-30: "get rid of the call and free
   quote bottom sticky on mobile". The bar covered content while scrolling (his
   screenshot: it sat over a section eyebrow). The markup stays in the pages so a
   future client can opt back in with one rule; the body padding compensation goes
   with it or every page carries 56px of dead space at the bottom. */
.mcall{display:none!important}

/* --- scroll reveal -------------------------------------------------------- */
.reveal{opacity:0;transform:translateY(14px);transition:opacity .6s ease,transform .6s ease}
.reveal.vis{opacity:1;transform:none}

/* ==========================================================================
   signature motifs
   The one bespoke flourish, carried into LIGHT body sections so the device is
   not only a dark-band trick: the ledger rule. A 2px charcoal rule with a gold
   mitre notch cut out of its right end, echoing the A. Used on section heads
   in light sections only.
   ========================================================================== */
.rule{display:block;height:2px;background:var(--primary);width:64px;margin-bottom:22px;position:relative}
.rule::after{content:"";position:absolute;right:-26px;top:0;width:22px;height:2px;
  background:var(--accent);clip-path:polygon(0 0,100% 0,100% 100%,3px 100%)}
.sched .div-head .dnum::before{content:""}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media(max-width:1080px){
  .aud{grid-template-columns:repeat(2,1fr)}
  .rail{grid-template-columns:repeat(2,1fr)}
  .rail .step:nth-child(2){border-right:0}
  .rail .step:nth-child(3),.rail .step:nth-child(4){border-top:1px solid var(--sc)}
  .glist{grid-template-columns:repeat(2,1fr)}
}
@media(max-width:900px){
  .menu,.navright{display:none}
  .burger{display:flex}
  /* PLAYBOOK 02 Fix 11, section 1: title -> paragraph -> IMAGE -> button.
     display:contents lifts the hero's text out of its wrapper so the full-bleed
     photo can be interleaved between the lead and the CTA. The gutter moves onto
     the individual items, because contents drops the wrapper's padding. */
  .hero{min-height:auto;display:flex;flex-direction:column;align-items:stretch}
  .hero .wrap,.hero .hero-in{display:contents}
  .hero .eyebrow{order:1;padding:34px 22px 0}
  .hero h1{order:2;padding:0 22px}
  .hero .lead{order:3;padding:0 22px;max-width:none}
  .hero .qphone{order:4;padding:0 22px}
  .hero-photo{order:5;position:relative;width:100%;margin:22px 0 0;
    height:clamp(215px,38svh,310px)}
  .hero-cta{order:6;padding:22px 22px 0;margin-top:0}
  .hero-note{order:7;padding:16px 22px 34px;margin-top:0}
  .hero-mark{display:none}
  .prow{grid-template-columns:1fr;gap:22px}
  .prow .prow-txt{order:2!important}
  .prow .prow-img{order:1!important}
  .stmt .sq{margin-bottom:20px}
  .stmt ul{grid-template-columns:1fr;gap:0}
  .qgrid{grid-template-columns:1fr;gap:32px}
  /* the client 2026-07-31: "swap the two sections around for mobile, the quote form
     is first". Stacked, .qside put the phone, hours, insurance and warranty
     copy - a full screen of reading - ABOVE the form, so the thing the section
     exists to do arrived last. Ordering ONLY .qside (not the form wrapper) is
     deliberate: the form wrapper keeps the default order:0, so the swap holds
     even where :has() is unsupported instead of silently reverting. */
  .qside{order:2}
  .feat{grid-template-columns:1fr}
  .foot-grid{grid-template-columns:1fr 1fr}
  .gallery-grid{grid-template-columns:repeat(2,1fr)}
  .srow{grid-template-columns:34px 24px 1fr 26px;gap:11px;row-gap:5px}
  .srow .rn{grid-column:1;grid-row:1}
  .srow .rm{grid-column:2;grid-row:1}
  .srow .rt{grid-column:3;grid-row:1}
  .srow .ra{grid-column:4;grid-row:1}
  .srow .rs{grid-column:3/5;grid-row:2}
}
@media(max-width:760px){
  .sec{padding:56px 0}
  /* statband on phones = the PLAYBOOK 02 Fix 2 valrow pattern (the client 2026-07-30,
     "turn it one row or skinny two rows", Bedrock is the reference): ONE compact
     row of four, icon above a short bold value, tight padding. The long sublines
     hide, but the attribution does NOT disappear: each cell keeps a one-word
     mono subline (hipages / insured / coverage / workmanship) because the rating
     must never render without its source (INTAKE honesty rule 1). */
  .statband .row{grid-template-columns:repeat(4,1fr)}
  /* two skinny rows of two, every cell still icon-left text-right (the client v2) */
  .statband .row{grid-template-columns:repeat(2,1fr)}
  .statband .st{gap:9px;padding:11px 8px 11px 10px;min-width:0;
    border-right:1px solid rgba(255,255,255,.14);border-bottom:1px solid rgba(255,255,255,.14)}
  .statband .st:nth-child(even){border-right:0}
  .statband .st:nth-child(n+3){border-bottom:0}
  .statband .st:not(:first-child){padding-left:10px}
  .statband .st-plate{width:32px;height:32px}
  .statband .st-ico{width:18px;height:18px}
    .statband .st-tx{gap:3px}
  .statband b{font-size:.8rem;line-height:1.1;letter-spacing:0}
  .statband .st-tx .st-l{display:none}
  .statband .st-tx .st-s{display:block;margin-top:0;font-size:.56rem;letter-spacing:.08em}
  .ba .half{aspect-ratio:4/5}
  .ba.wide .half{aspect-ratio:1}
  #covmap{height:300px}
}
/* SE-class screens: step the value down so "All Sydney" holds ONE line per cell */
@media(max-width:360px){
  .statband b{font-size:.7rem}
}
@media(max-width:640px){
  .aud{grid-template-columns:1fr}
  .rail{grid-template-columns:1fr}
  .rail .step{border-right:0;padding-left:0!important;padding-right:0;
    border-top:1px solid var(--sc)}
  .rail .step:first-child{border-top:0}
  .glist{grid-template-columns:1fr}
  .gallery-grid{grid-template-columns:1fr}
  /* three stacked photos cost ~660px of scroll on a phone for no extra
     information, so the strip becomes a swipe rail instead */
  .wstrip{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:76%;
    overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;
    -webkit-overflow-scrolling:touch}
  .wstrip::-webkit-scrollbar{display:none}
  .wstrip .ws{aspect-ratio:4/3;scroll-snap-align:start}
  .wstrip.two{grid-template-columns:none}
  /* width:auto is load-bearing: the base .wstrip{width:100%} plus these margins
     overflowed the viewport by 18px at 320 (found 2026-07-30 compliance sweep) */
  .wstrip.one{grid-auto-flow:row;grid-template-columns:1fr;margin-inline:18px;width:auto}
  /* the client 2026-07-31: first/last name stay SIDE BY SIDE on mobile. Two short
     fields on one row read as one question and keep the form visibly short;
     stacking them made step 1 look twice as long as it is. Only the name pair
     is exempt - mobile and email are long enough to need the full width. */
  .field-2{grid-template-columns:1fr}
  .field-2.pair-tight{grid-template-columns:1fr 1fr;gap:10px}
  .hero-cta .btn{width:100%}
  .topic-rail span{padding:13px 16px}
}
@media(max-width:560px){
  :root{--mast:58px}
  .wrap{padding:0 18px}
  .hero .eyebrow,.hero h1,.hero .lead,.hero .qphone,.hero-cta,.hero-note{padding-inline:18px}
  .foot-grid{grid-template-columns:1fr}
  .brand .logo-img{height:46px}
  .prose{font-size:.97rem}
}
/* the h1 floor ignores both axes, so step it down on SE-class screens.
   This block must stay AFTER the h1 rule: equal specificity, source order wins. */
@media(max-width:760px) and (max-height:700px){
  h1{font-size:2.35rem}
}

@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .reveal{opacity:1;transform:none;transition:none}
  *{animation-duration:.001ms!important;transition-duration:.001ms!important}
}

@media print{
  .mast,.drawer,.scrim,.qband,.foot{display:none}
}

/* --- in-prose cross links (guides <-> money pages) ------------------------ */
.gxref{margin-top:24px;font-size:.9rem;color:var(--secondary)}
.gxref a{color:var(--accent-ink);text-decoration:underline;text-underline-offset:2px}
.prose .gxref{border-left:2px solid var(--accent);padding:2px 0 2px 18px;margin:26px 0}
.faq-wrap + .gxref{margin-top:26px;font-family:var(--font-mono);font-size:.72rem;
  letter-spacing:.08em;text-transform:uppercase}
.qsms{margin:12px 0 20px}
.qsms .svg-ico{width:16px;height:16px}

/* --- coverage regions -----------------------------------------------------
   Replaces a 44px silent scroller holding 53 suburb chips, 94% of which were
   unreachable at 354px. Six regions a person can actually read; the full
   suburb list still ships in areaServed for search. Flat cells on a hairline
   grid, the same device as the audience panels, zero radius.

   2026-07-31 (the client): the six-cell GRID became ONE ROTATING ROW - "just make it
   one row and rotating, as the map will do all the visual telling". The map
   below carries the coverage story, so the region names only need to pass by,
   not occupy six rows of page. Runs on the house [data-reviews] engine (one
   engine, every instance) so it inherits auto-advance, hover pause, swipe,
   dots and the reduced-motion opt-out for free. */
.regs{margin:0 0 26px}
.regs .rev-cell{min-width:33.3333%}
.regs .rg{flex:1;background:var(--white);padding:20px 20px 22px;
  border-right:1px solid var(--sc)}
.regs .rg b{display:block;font-family:var(--font-display);font-size:.98rem;
  color:var(--primary);margin-bottom:7px}
.regs .rg span{display:block;font-size:.84rem;color:var(--secondary);line-height:1.55}
/* the engine reports 3 visible >1000, 2 >640, 1 below: the cell widths follow it
   so a cell is never a part-slice at rest */
@media(max-width:1000px){.regs .rev-cell{min-width:50%}}
@media(max-width:640px){.regs .rev-cell{min-width:100%}
  .regs .rg{border-right:0}}

/* the section whose whole argument is that a mitre is either right or obvious
   cannot be too dark to show its own cut. 62% flat was the DIRECTION's number
   for a scrim over TYPE; this band's job is the photograph, so it runs lighter
   and the caption bar carries the contrast instead. */
.pband::before{background:rgba(40,40,40,.46)}
.pband h2,.pband p{text-shadow:0 1px 14px rgba(26,26,26,.55)}
/* a portrait generated frame gets a portrait slot rather than being cropped to
   death inside a landscape one */
/* .gfr.row.tall retired 2026-07-30: every .prow image slot renders the single
   landscape row ratio (the client: "the standard should be the second one", the slab row). */

/* ==========================================================================
   GALLERY LIGHTBOX (parity adoption 2026-07-30: topclass house component,
   re-skinned Mitre & Ledger: zero radius, charcoal scrim, mono caption).
   And the BEFORE/AFTER stage tags on gallery pair tiles.
   ========================================================================== */
.gtag{position:absolute;top:0;left:0;z-index:4;background:var(--primary);
  color:var(--accent);font-family:var(--font-mono);font-size:.68rem;
  letter-spacing:.12em;text-transform:uppercase;padding:7px 12px}
.gallery-grid .frame.lb-click{cursor:zoom-in}
#lb{position:fixed;inset:0;z-index:200;display:none}
#lb.open{display:block}
#lb .lb-back{position:absolute;inset:0;background:rgba(26,26,26,.92);border:0;cursor:default}
#lb .lb-box{position:relative;z-index:1;height:100%;display:flex;flex-direction:column;
  align-items:center;justify-content:center;padding:34px 72px;gap:14px}
#lb .lb-body{max-width:min(1100px,100%);display:flex;align-items:center;justify-content:center}
#lb .lb-img{max-width:100%;max-height:76vh;box-shadow:var(--shadow-xl)}
#lb .lb-cap{color:#fff;font-family:var(--font-mono);font-size:.74rem;letter-spacing:.1em;
  text-transform:uppercase;text-align:center;opacity:.92}
#lb .lb-x,#lb .lb-nav{position:absolute;z-index:2;width:46px;height:46px;background:var(--primary);
  color:var(--accent);display:grid;place-items:center;font-size:1.5rem;line-height:1;border:0;cursor:pointer}
#lb .lb-x:hover,#lb .lb-nav:hover{background:var(--accent);color:var(--primary)}
#lb .lb-x{top:14px;right:16px}
#lb .lb-nav{top:50%;transform:translateY(-50%)}
#lb .lb-prev{left:14px}
#lb .lb-next{right:14px}
@media(max-width:640px){#lb .lb-box{padding:20px 12px 56px}
  #lb .lb-nav{top:auto;bottom:8px;transform:none}
  #lb .lb-prev{left:calc(50% - 52px)} #lb .lb-next{right:calc(50% - 52px)}}

/* the coverage map with no basemap tiles (CDN blocked/offline). The polygon and
   the base pin still draw, so present it as an intentional plain diagram on the
   brand surface rather than a broken grey box. */
#covmap.covmap-flat{background:var(--sc-lowest)}
#covmap.covmap-flat .leaflet-tile-pane{display:none}
#covmap.covmap-flat::after{content:"The Sydney area we cover";position:absolute;
  left:0;right:0;bottom:0;z-index:500;padding:9px 14px;
  background:rgba(40,40,40,.82);color:#fff;
  font-family:var(--font-mono);font-size:.64rem;letter-spacing:.08em;text-transform:uppercase}
