/* ============================================================
   APC Premium Powder Coating — CANONICAL GLOBAL STYLESHEET
   Phase 6A output. Linked ONCE per page by Phase 6B; never inlined,
   never duplicated per page.

   GENERATED by _build/merge-6a-css.mjs — DO NOT EDIT THIS FILE.
   Edit 05_design_preview/css/apc-global.css or apc-ref.css and re-run
   the build; stageAssets() regenerates this file before copying it.

   WHAT THIS IS: the client-approved stylesheet that renders the 13
   approved templates, assembled from Skill 5's two files in their
   original cascade order:
       1. apc-global.css   (base system)
       2. apc-ref.css      (reference-informed component layer)
   apc-ref.css OVERRIDES apc-global.css at equal specificity in four
   places, so the order below is load-bearing — concatenating the other
   way silently changes the rendering.

   WHAT THIS IS NOT: it is not generated from
   _COMMON/wooley-canonical-R1-R56.css. That file is Wooley's own
   consolidated client stylesheet (their palette, written for Elementor),
   not a client-agnostic hardening layer; merging it would overwrite
   APC's brand. The 13 named R-rules it stands for were instead
   reconciled against this stylesheet — 13/13 SATISFIED, 0 gaps. See the
   standing decision in 00_Build_Standards.md.

   TOKENS: @import'ed on the first line below, per the Phase 6A spec
   ("loaded first — either via a plain CSS @import at the top of
   {client_slug}-global.css, or inlined as its first :root block").
   tokens.css remains the ONLY file permitted to contain literal hexes,
   px sizes or font names.

   Emitted: 2026-08-19
   ============================================================ */
@import url("tokens.css");

/* ============================================================
   PART 1 of 2 — apc-global.css (Skill 5 base system)
   ============================================================ */
/* ============================================================
   APC Premium Powder Coating — GLOBAL STYLESHEET
   Emitted by: Skill 5 v9 · Direction "COASTAL STEEL"
   DISCIPLINE: no literal hexes / sizes / font names below this header.
               Everything var()s into tokens.css.
   ============================================================ */
/* @import lifted to the top of this file by Phase 6A */

/* === RESET ================================================== */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{
  margin:0;font-family:var(--font-body);font-size:var(--fs-body);
  line-height:var(--lh-body);color:var(--text-body);background:var(--page);
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
img,svg,video{max-width:100%;height:auto;display:block}
a{color:var(--red);text-decoration:none}
a:hover{text-decoration:underline}
:focus-visible{outline:var(--bw-default) solid var(--red);outline-offset:3px}
ul,ol{margin:0 0 1em;padding-left:1.15em}
li{margin-bottom:var(--sp-2)}

/* === LAYOUT ================================================= */
.container{max-width:var(--container-max);margin-inline:auto;padding-inline:var(--gutter)}
.container--narrow{max-width:800px}
.band{max-width:var(--band-max);margin-inline:auto}
/* R-no-padless-section: there is deliberately NO .section--flush class. */
.section{padding-block:var(--section-py);position:relative}
.section--tight{padding-block:var(--sp-12)}
@media(max-width:768px){.section{padding-block:var(--section-py-sm)}}

.section--wash{background:var(--wash)}
.section--white{background:var(--white)}
.section--navy{background:var(--navy)!important;color:var(--text-on-dark)}
.section--navy h1,.section--navy h2,.section--navy h3,.section--navy h4{color:var(--text-on-dark)}
.section--navy p{color:var(--text-muted-dark)}
/* §1.9 dark-section rule: accent text on navy is ONLY the derived tint. */
.section--navy .secthead strong,.section--navy .intro strong{color:var(--red-on-dark)}
.section--navy .eyebrow{color:var(--label-on-dark)}
.section--navy a:not(.btn){color:var(--red-on-dark)}
.section--grain::before{
  content:"";position:absolute;inset:0;background:var(--grain);pointer-events:none;
}
.section--grain>*{position:relative;z-index:1}

/* === TYPOGRAPHY ============================================= */
h1,h2{
  font-family:var(--font-display);font-weight:400;line-height:var(--lh-display);
  letter-spacing:var(--tr-display);text-transform:uppercase;
  margin:0 0 var(--sp-4);color:var(--text-on-light);
}
h3,h4,h5,h6{
  font-family:var(--font-body);font-weight:700;line-height:var(--lh-tight);
  margin:0 0 var(--sp-3);color:var(--text-on-light);text-transform:none;
}
h1{font-size:var(--fs-h1)}
h2{font-size:var(--fs-h2)}
h3{font-size:var(--fs-h3)}
h4{font-size:var(--fs-h4)}
p{margin:0 0 1em;max-width:var(--measure-body)}
.lead{font-size:var(--fs-lead);max-width:var(--measure-lead);line-height:1.55}
small,.caption{font-size:var(--fs-caption);color:var(--steel-ink)}

/* Skill 4 authors a long desktop H1 and a short mobile one. Both live inside
   the SINGLE <h1> so crawlers see one heading; CSS picks which is visible. */
.h1-desktop{display:none}
.h1-mobile{display:inline}
@media(min-width:769px){.h1-desktop{display:inline}.h1-mobile{display:none}}

/* === M7 #1 + #2 — accent-rule section marker + eyebrow underline ===
   Sections never open with a bare centred H2. */
.eyebrow{
  display:inline-block;font-size:var(--fs-eyebrow);font-weight:600;
  text-transform:uppercase;letter-spacing:var(--tr-nav);color:var(--steel-ink);
  margin:0 0 var(--eyebrow-gap);padding-bottom:var(--sp-1);
  border-bottom:var(--bw-default) solid var(--red);
}
.secthead{margin-bottom:var(--sp-10);max-width:var(--measure-lead)}
/* Header + a right-hand action. NEVER put display:flex on .secthead itself —
   its ::before accent rule becomes a flex item and detaches from the heading. */
.sechead-row{
  display:flex;justify-content:space-between;align-items:flex-end;
  gap:var(--sp-8);margin-bottom:var(--sp-10);flex-wrap:wrap;
}
.sechead-row .secthead{margin-bottom:0}
@media(max-width:700px){.sechead-row{align-items:flex-start}}
.secthead--center{margin-inline:auto;text-align:center}
.secthead--center .eyebrow{display:inline-block}
/* QA (Nicole, Aug 11): the accent rule that used to open a section is gone.
   It sat directly above the eyebrow, which carries its own red underline, so
   every section opened with two red lines a few pixels apart. Checked all 57
   sectheads in the build — every one has an eyebrow, so nothing was relying on
   this rule as its only marker. The eyebrow underline is now the sole marker. */

/* === SIGNATURE: the diagonal swoosh cut (from the logo mark) ========= */
.swoosh-rule{height:6px;background:var(--swoosh);border:0}
.swoosh-edge{position:relative;overflow:hidden}
.swoosh-edge::after{
  content:"";position:absolute;inset-block:0;right:-2px;width:74px;
  background:var(--swoosh);
  clip-path:polygon(64% 0,100% 0,36% 100%,0 100%);
}

/* === BUTTONS (M7 #3 — CTA character + arrow-hover) ================== */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-3);
  min-height:var(--btn-h);padding:var(--sp-4) var(--sp-8);
  font-family:var(--font-body);font-weight:700;font-size:.875rem;
  text-transform:uppercase;letter-spacing:var(--tr-btn);
  border:var(--bw-default) solid transparent;border-radius:var(--radius);
  cursor:pointer;transition:background var(--dur) var(--ease),
    border-color var(--dur) var(--ease),color var(--dur) var(--ease);
  text-decoration:none!important;text-align:center;
}
.btn__arrow{transition:transform var(--dur) var(--ease);display:inline-block}
.btn:hover .btn__arrow{transform:translateX(5px)}
.btn--primary{background:var(--red);color:var(--white);border-color:var(--red)}
.btn--primary:hover{background:var(--red-deep);border-color:var(--red-deep)}
.btn--secondary{background:var(--navy);color:var(--white);border-color:var(--navy)}
.btn--secondary:hover{background:var(--navy-3);border-color:var(--navy-3)}
.btn--ghost{background:transparent;color:var(--navy);border-color:var(--navy)}
.btn--ghost:hover{background:var(--navy);color:var(--white)}
/* on a dark or red band the primary inverts to white */
.btn--invert{background:var(--white);color:var(--red);border-color:var(--white)}
.btn--invert:hover{background:var(--wash);border-color:var(--wash);color:var(--red-deep)}
.btn--ghost-light{background:transparent;color:var(--white);border-color:var(--white)}
.btn--ghost-light:hover{background:var(--white);color:var(--navy)}
.btn-row{display:flex;flex-wrap:wrap;gap:var(--sp-4);margin-top:var(--sp-8)}
.btn-row .btn{flex:0 1 auto}
/* QA (Nicole, Aug 11): buttons must run the full width on mobile, on every page.
   They already stacked, but .btn-row is a flex item that shrink-wraps to its
   content, so width:100% on the button was 100% of a narrow box — the buttons
   stayed label-width. The row itself has to claim the line first. */
@media(max-width:520px){
  .btn-row{flex-direction:column;width:100%}
  .btn-row .btn{width:100%}
}
/* At 360px the longest label ("Get My Free Estimate") measures 234px inside a
   244px box and breaks. Trimming the inline padding buys 24px — the label stays
   full, which matters more than the padding at that size. */
@media(max-width:400px){
  .btn{padding-inline:var(--sp-5);font-size:.8125rem;letter-spacing:.04em}
}

/* === BEFORE / AFTER SLIDER ==========================================
   Both photos in one frame with a draggable divider, wearing the ORIGINAL
   pair's styling: red rule across the top, square-ish corners, rectangular
   uppercase tags with the red "After". The navy surround and pill labels of
   the earlier reference pass are gone.

   The photos fill the section edge to edge, at the short height the section
   used to have (client request). Those two together REQUIRE a crop: the
   photos are 3:2, and 1192px wide at 3:2 is 795px tall. 3:1 brings it back to
   ~397px — the height it had before — by showing the middle band of each
   photo. object-position biases that band upward so the rail cap, the thing
   the section is about, stays in frame. Below 720px the frame goes back to
   the photos' own 3:2, where the full shot fits without cropping.

   The drag surface is a real <input type="range"> over the frame at opacity 0
   — that buys keyboard control, touch and the arrow/Home/End semantics for
   free, where a custom pointer handler would need much more code. --pos is
   the divider position, set inline so the frame renders split before ui.js
   runs, and updated on input. */
.bafter__frame{
  position:relative;overflow:hidden;border-radius:var(--radius);
  aspect-ratio:3/1;background:var(--navy);
  -webkit-user-select:none;user-select:none;
}
@media(max-width:720px){ .bafter__frame{aspect-ratio:3/2} }
/* The red rule OVERLAYS the top edge instead of being a border-top. A real
   border sits inside the box, leaving the photo area 4px short of 3:2, which
   object-fit would then trim off — that is an image-scale change, and this
   pass is explicitly not allowed one. As an overlay it costs no height. */
.bafter__frame::before{
  content:"";position:absolute;top:0;left:0;right:0;z-index:4;
  height:var(--bw-accent);background:var(--red);pointer-events:none;
}
.bafter__frame img{
  position:absolute;inset:0;display:block;
  width:100%;height:100%;object-fit:cover;
  /* The 3:1 frame keeps only the middle half of a 3:2 photo. Centred, that cut
     the top off both rails — the one thing this section asks you to compare —
     so the band is biased up. No effect below 720px, where the frame matches
     the photo and nothing is cropped. */
  object-position:center 20%;
}
/* the BEFORE shot is clipped to everything left of the divider. clip-path on
   the wrapper, not a width on it — a narrowing box would squash the photo. */
.bafter__reveal{position:absolute;inset:0;clip-path:inset(0 calc(100% - var(--pos)) 0 0)}
.bafter__line{
  position:absolute;top:0;bottom:0;left:var(--pos);
  width:var(--bw-default);margin-left:calc(var(--bw-default) / -2);
  background:var(--white);pointer-events:none;
}
.bafter__grip{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:var(--tap);height:var(--tap);border-radius:50%;
  display:grid;place-items:center;
  background:var(--white);color:var(--navy);
  box-shadow:0 2px 12px rgba(13,27,42,.4);
}
.bafter__grip svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:2}
.bafter__range{
  position:absolute;inset:0;width:100%;height:100%;margin:0;
  opacity:0;cursor:ew-resize;
  touch-action:pan-y;          /* vertical still scrolls the page */
}
/* the range itself is invisible, so the focus ring has to land on the grip */
.bafter:focus-within .bafter__grip{outline:var(--bw-default) solid var(--red);outline-offset:3px}
/* tags exactly as the original pair wore them: square-ish, uppercase, tracked,
   navy for Before and the brand red for After */
.bafter__tag{
  position:absolute;bottom:var(--sp-4);pointer-events:none;
  padding:var(--sp-2) var(--sp-4);border-radius:var(--radius);
  font-size:var(--fs-eyebrow);font-weight:700;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--white);
}
.bafter__tag--before{left:var(--sp-4);background:rgba(13,27,42,.92)}
.bafter__tag--after{right:var(--sp-4);background:var(--red)}

/* === HEADER (sticky, navy, M11 3-tier budget) ======================= */
/* WHITE HEADER (by request) — the full-colour logo could not carry the navy, so
   the bar goes white and everything that was tuned for a dark ground flips with
   it: nav links, the chevrons, the burger and the phone badge. The mobile drawer
   stays navy, so .nav__link is re-lightened inside the drawer breakpoint below —
   without that, the drawer's links would be navy on navy. */
.hdr{
  position:sticky;top:0;z-index:900;background:var(--white);
  border-bottom:var(--bw-thin) solid var(--hair);
}
.hdr__inner{
  max-width:var(--band-max);margin-inline:auto;padding:var(--sp-3) var(--gutter);
  display:flex;align-items:center;gap:var(--sp-6);
}
/* No plate — the full-colour logo sits directly on the navy (QA, Nicole:
   "replace with transparent logo"). A white plate was tried and reverted.
   For the record, since this keeps coming up: only 35% of the logo's opaque
   pixels clear 3:1 against navy and 26.7% clear 4.5:1, because the letterform
   interiors are dark navy and red. WCAG 1.4.3 exempts logotypes from any
   contrast minimum, and legibility here comes from the chrome outlines that
   define the letterforms rather than the fills. The footer takes the other
   route — a white knockout of the same mark. */
.brand{display:flex;align-items:center;flex-shrink:0;min-height:var(--tap)}
.brand__plate{
  display:inline-flex;align-items:center;
}
.brand__plate img{height:60px;width:auto}
.nav{display:flex;align-items:center;gap:var(--sp-1);margin-left:auto}
.nav__item{position:relative}
.nav__link{
  display:inline-flex;align-items:center;gap:6px;padding:var(--sp-3) var(--sp-3);
  font-size:.8125rem;font-weight:600;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--navy);position:relative;
  text-decoration:none!important;white-space:nowrap;
}
/* M7 #4 — animated ::after underline, never a colour swap */
.nav__link::after{
  content:"";position:absolute;left:var(--sp-3);right:var(--sp-3);bottom:6px;
  height:var(--bw-default);background:var(--red);
  transform:scaleX(0);transform-origin:left;transition:transform var(--dur) var(--ease);
}
.nav__link:hover::after,.nav__item:hover .nav__link::after,
.nav__link[aria-current="page"]::after{transform:scaleX(1)}
.nav__link--accent{color:var(--red)}
.nav__chev{width:8px;height:8px;border-right:2px solid currentColor;
  border-bottom:2px solid currentColor;transform:rotate(45deg) translateY(-2px);opacity:.7}

/* --- mega menu (M7 #7 — title+subtitle cards, never a flat <ul>) --- */
.mega{
  position:absolute;top:100%;left:0;z-index:950;
  min-width:640px;padding:var(--sp-8);
  background:var(--white);border-top:var(--bw-accent) solid var(--red);
  box-shadow:0 24px 48px rgba(13,27,42,.22);
  display:none;
}
.mega--wide{min-width:860px}
.nav__item:hover .mega,.nav__item:focus-within .mega{display:block}
/* hover-bridge so the pointer can cross the gap without closing */
.nav__item::after{content:"";position:absolute;left:0;right:0;top:100%;height:14px}
.mega__grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-6)}
.mega__grid--2{grid-template-columns:repeat(2,1fr)}
.mega__col>.mega__colhead{
  font-size:var(--fs-caption);font-weight:700;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--steel-ink);
  padding-bottom:var(--sp-2);margin-bottom:var(--sp-3);
  border-bottom:var(--bw-thin) solid var(--hair);
}
.mega__card{
  display:block;padding:var(--sp-3);border-left:var(--bw-default) solid transparent;
  transition:background var(--dur) var(--ease),border-color var(--dur) var(--ease);
  text-decoration:none!important;
}
.mega__card:hover{background:var(--wash);border-left-color:var(--red)}
.mega__t{display:block;font-weight:700;font-size:.9375rem;color:var(--navy)}
.mega__d{display:block;font-size:var(--fs-caption);color:var(--steel-ink);margin-top:2px}
.mega__foot{
  margin-top:var(--sp-6);padding-top:var(--sp-4);
  border-top:var(--bw-thin) solid var(--hair);
}

/* --- M11 HEADER BUDGET — MEASURED, not guessed -----------------------
   RE-MEASURED after the logo went 44px -> 60px (client request). The taller
   mark is also 53px wider, which pushed one tier over its budget:
     >=1501  logo 60  lockup 200  row 1415  (fits 1453 at 1501)
     <=1500  logo 54  lockup 180  row 1325  (nav tightens)
     <=1399  phone text drops     row 1198
     <=1279  drawer               row  322
   The row was 1325 against 1322 of space at 1370px, so the phone-text drop
   moved 1369 -> 1399. Floor for the full row is 1373px.
   Re-measure this ladder if the lockup, nav labels or CTA copy change. */
/* flex-shrink:0 + nowrap: as a flex child of .hdr__inner the CTA was being
   squeezed at the bottom of each tier (1140px) and breaking its own label */
.hdr__cta{margin-left:var(--sp-4);flex-shrink:0;white-space:nowrap}
.brand,.phonebadge{flex-shrink:0}
.hdr__burger{display:none;background:none;border:0;padding:var(--sp-3);cursor:pointer}
.hdr__burger span{display:block;width:24px;height:2px;background:var(--navy);margin:5px 0}

@media(max-width:1500px){        /* tighten the nav — 7 items resolve to 699px */
  .nav__link{padding-inline:10px;font-size:.75rem}
  .brand img{height:54px}
  .brand__name{font-size:1.35rem}
}
/* Tier B (drop the phone text, <=1399px) is NOT here — .phonebadge__t is
   declared display:flex in apc-ref.css, which loads later, so a media query in
   this file loses to it at equal specificity. The rule lives in apc-ref.css. */
@media(max-width:1439px){        /* -6px of logo height; the plate padding it
                                    used to shrink no longer exists */
  .brand__plate img{height:54px}
}
/* RE-MEASURED for the full logo plate: it needs 1231px of header where the old
   mark+text lockup needed 1112px, so the drawer starts 140px earlier. */
@media(max-width:1279px){        /* below the plate floor -> drawer */
  /* Get A Quote stays in the bar instead of hiding into the drawer (client
     request: visible without a single tap, the way the bottom bar is). It sits
     inline next to the burger while there is room; under 480px the logo (180px)
     and burger (48px) leave too little for the label, so it wraps to a
     full-width row of its own — see the 479px block below. */
  .hdr__cta{display:inline-flex;order:2;margin-left:auto}
  .brand{order:1}
  .hdr__burger{display:block;order:3;margin-left:0}

  /* ---- MOBILE DRAWER ----------------------------------------------
     The burger toggled a class that had no styles behind it, so the menu
     did nothing at all below 1279px — every nav link was unreachable on a
     phone. This is the panel it was supposed to open.

     The panel opens BELOW the header, not over it (client request: the logo
     and the toggle icon must both stay fully visible while the menu is open).

     The offset CANNOT be a constant. .hdr is sticky, and the preview topbar
     above it scrolls away, so the header's bottom edge sits at 79px once
     scrolled but at topbar+79px at the top of the page. ui.js measures it and
     publishes --hdr-bottom when the drawer opens; the token is only the
     no-JS fallback, correct in the scrolled case. */
  .nav{
    position:fixed;top:var(--hdr-bottom,var(--hdr-h-m));bottom:0;right:0;left:auto;
    width:min(400px,100%);z-index:1000;
    display:flex;flex-direction:column;align-items:stretch;gap:0;margin:0;
    background:var(--navy);border-left:var(--bw-accent) solid var(--red);
    /* the sticky CTA bar sits at z-9999, above this panel — the drawer's last
       item would sit behind it without clearance. The old 80px of top padding
       cleared the floating close button, which the visible burger replaces. */
    padding:0 0 calc(var(--mobilebar-h) + var(--sp-6));overflow-y:auto;
    transform:translateX(100%);visibility:hidden;
    transition:transform var(--dur) var(--ease),visibility var(--dur) var(--ease);
  }
  /* .nav is a DESCENDANT of .hdr, so it lives inside the header's own stacking
     context — raising .hdr's z-index orders it against the page, never against
     its own child. The logo and the burger have to outrank the panel directly,
     inside that context. This is what actually keeps them visible; the offset
     above just stops the panel from sitting on them in the first place. */
  .brand,.hdr__burger{position:relative;z-index:1002}
  body.is-navopen .nav{transform:translateX(0);visibility:visible}
  body.is-navopen{overflow:hidden}

  .nav__item{border-bottom:var(--bw-thin) solid var(--hair-dark)}
  .nav__item::after{display:none}          /* kill the desktop hover-bridge */
  .nav__link{
    display:flex;justify-content:space-between;width:100%;
    padding:var(--sp-5) var(--sp-6);font-size:.9375rem;min-height:var(--tap);
    color:var(--white);            /* the drawer panel is still navy */
  }
  .nav__link--accent{color:var(--red-on-dark)}
  .nav__link::after{display:none}          /* underline is a desktop affordance */
  .nav__chev{transition:transform var(--dur) var(--ease)}
  .nav__item.is-open .nav__chev{transform:rotate(-135deg) translateY(-2px)}

  /* submenus become in-flow accordions, not floating panels */
  .mega{
    position:static;display:none;min-width:0;width:auto;padding:0 0 var(--sp-4);
    background:var(--navy-2);border-top:0;box-shadow:none;
  }
  .nav__item.is-open .mega{display:block}
  .nav__item:hover .mega,.nav__item:focus-within .mega{display:none}
  .nav__item.is-open:hover .mega{display:block}
  .mega__grid,.mega__grid--2{grid-template-columns:1fr;gap:0}
  .mega__col>.mega__colhead{
    padding:var(--sp-4) var(--sp-6) var(--sp-2);margin:0;border-bottom:0;
    color:var(--label-on-dark);
  }
  .mega__card{padding:var(--sp-3) var(--sp-6);border-left:0;min-height:var(--tap);
    display:flex;flex-direction:column;justify-content:center}
  .mega__card:hover{background:var(--navy-3);border-left-color:transparent}
  .mega__t{color:var(--white);font-size:.875rem}
  .mega__d{display:none}                   /* subtitles are noise in a drawer */
  .mega__foot{margin:0;padding:var(--sp-3) var(--sp-6) 0;border-top-color:var(--hair-dark)}

  /* drawer footer: the actions the header CTA and phone badge lose */
  .nav__actions{display:flex;flex-direction:column;gap:var(--sp-3);padding:var(--sp-6)}
  .nav__actions .btn{width:100%}

  /* The floating close button is retired now that the burger stays visible and
     already toggles both ways (ui.js). Keeping it would stack two close
     controls a few pixels apart, one in the bar and one just under it.
     Hidden rather than deleted — the markup and its JS listener are harmless,
     and this is one line to undo. */
  .hdr__close{display:none}
  /* the burger becomes the X instead. 7px is the span pitch: 2px bars with
     5px collapsed margins, so centres sit at 6 / 13 / 20. */
  .hdr__burger span{transition:transform var(--dur) var(--ease),opacity var(--dur) var(--ease)}
  body.is-navopen .hdr__burger span:nth-child(1){transform:translateY(7px) rotate(45deg)}
  body.is-navopen .hdr__burger span:nth-child(2){opacity:0}
  body.is-navopen .hdr__burger span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}

  /* Starts below the bar, same anchor as the panel. Full-viewport, the scrim
     sat over the header's white background (it is a descendant of .hdr, so the
     bar's own background cannot paint above it) and turned the bar grey — the
     logo stayed legible but no longer read as the white header it is. */
  .navscrim{
    position:fixed;top:var(--hdr-bottom,var(--hdr-h-m));bottom:0;left:0;right:0;
    z-index:999;background:rgba(13,27,42,.6);
    opacity:0;visibility:hidden;transition:opacity var(--dur) var(--ease),visibility var(--dur) var(--ease);
  }
  body.is-navopen .navscrim{opacity:1;visibility:visible}
}
/* Phones: ONE row — logo left, Get A Quote in the middle, burger hard right
   (client reference). Everything here exists to buy the horizontal room that
   makes that fit; at the desktop sizes the three items need 477px of content
   box and a 360px phone offers 312.
     logo   54px -> 40px tall  (180 -> 134 wide)
     button 201px -> ~124      (11px label, tighter padding, no arrow)
     burger  48px -> 40px      (8px padding rather than 12)
     gaps    24px -> 8px
   The button gets margin-inline:auto so it sits centred in whatever space is
   left between the logo and the burger rather than hugging either one.
   Re-measure this if the label or the logo changes — it is tight by design. */
@media(max-width:599px){
  :root{--hdr-h-m:69px}          /* .brand's 44px min-height wins + 12x2 + 1 */
  .hdr__inner{gap:var(--sp-2)}
  .brand__plate img{height:40px}
  .hdr__burger{order:3;margin-left:0;padding:var(--sp-2)}
  .hdr__cta{
    order:2;margin-inline:auto;
    --btn-h:40px;padding:var(--sp-2) var(--sp-4);
    font-size:.6875rem;letter-spacing:.04em;
  }
  .hdr__cta .btn__arrow{display:none}   /* ~20px of pure decoration */
}
/* Trade the gutter before the label. This has to fire by 399 and not lower:
   at 360 with the full 24px gutter the row had 3px of slack, which is not a
   margin, it is luck. */
@media(max-width:399px){
  .hdr__inner{padding-inline:var(--sp-3)}
}
/* Smallest phones still in use (SE-class, 320px). Even with the tighter gutter
   the row ran 13px over, so the logo gives up the last 20px. */
/* Was 339. The FINAL APC mark is proportionally wider than the one it
   replaced (ratio 3.64 vs 3.33), which pushed the 340-359 band 5px past its
   budget at the 40px logo size. The smaller step now starts at 359. */
@media(max-width:359px){
  .brand__plate img{height:34px}
  .hdr__inner{gap:var(--sp-1)}
}
@media(min-width:1280px){
  .nav__actions,.hdr__close,.navscrim{display:none}
}

/* === HERO PATTERNS ================================================== */
.hero{position:relative;background:var(--navy);color:var(--text-on-dark);overflow:hidden}
.hero__grid{
  max-width:var(--band-max);margin-inline:auto;
  display:grid;grid-template-columns:1.05fr .95fr;align-items:stretch;
}
.hero__copy{padding:var(--sp-24) var(--sp-16) var(--sp-24) max(var(--gutter),calc((100vw - var(--container-max))/2));
  display:flex;flex-direction:column;justify-content:center;position:relative;z-index:2}
.hero__media{position:relative;min-height:560px;background:var(--navy-2)}
.hero__media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* The copy sits in its OWN column, so the photo needs no full scrim — only a
   short blend into the navy panel so the raked cut doesn't read as a hard seam. */
.hero__media::after{
  content:"";position:absolute;inset-block:0;left:0;width:34%;z-index:1;
  background:linear-gradient(to right,var(--navy),rgba(13,27,42,0));
}
/* the raked brand cut between copy and photo */
.hero__media::before{
  content:"";position:absolute;inset-block:-2px;left:-1px;width:96px;z-index:2;
  background:var(--swoosh);
  clip-path:polygon(58% 0,100% 0,42% 100%,0 100%);
}
.hero h1{color:var(--white);margin-bottom:var(--sp-5);max-width:16ch}
.hero .lead{color:var(--text-muted-dark);margin-bottom:0}
.hero__eyebrow{color:var(--label-on-dark);border-bottom-color:var(--red-on-dark)}
@media(max-width:960px){
  .hero__grid{grid-template-columns:1fr}
  .hero__copy{padding:var(--sp-16) var(--gutter);order:2}
  .hero__media{min-height:300px;order:1}
  .hero__media::before{display:none}
  /* on mobile ::after stops being the blend gradient and becomes the swoosh rule —
     reset every property the desktop rule set or they leak through */
  .hero__media::after{
    inset-block:auto;left:0;right:0;width:auto;top:auto;bottom:-1px;height:8px;
    background:var(--swoosh)
  }
}

/* text over any image always gets a scrim, capped at .45 */
.scrim::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(to top,rgba(13,27,42,.82),rgba(13,27,42,.34) 55%,rgba(13,27,42,.12));
}

/* === CARDS (M7 #5 — top accent border, flat, NO drop shadow) ========= */
.card{
  background:var(--white);border-top:var(--bw-accent) solid var(--red);
  border-inline:var(--bw-thin) solid var(--hair);border-bottom:var(--bw-thin) solid var(--hair);
  padding:var(--sp-8);display:flex;flex-direction:column;
  transition:border-top-color var(--dur) var(--ease),transform var(--dur) var(--ease);
}
.card:hover{border-top-color:var(--navy);transform:translateY(-3px)}
/* Tile with a photo across the top. The media cancels the card's own padding
   with a negative margin rather than the card dropping to padding:0, so every
   other .card on the site is untouched. The icon chip then rides up over the
   image's lower edge — the badge-on-photo device from the reference — and keeps
   its white ground so the glyph reads whatever the photo does behind it. */
.card--media{overflow:hidden}
.card__media{
  display:block;margin:calc(var(--sp-8) * -1) calc(var(--sp-8) * -1) 0;
  aspect-ratio:16/10;overflow:hidden;background:var(--wash);
}
/* --wash is a light placeholder, which flashes white on a dark card while the
   lazy image is still decoding. On .card--dark it holds the dark surface. */
.card--dark .card__media{background:var(--navy-3)}
/* <picture> is inline by default, so the img's height:100% had nothing to
   resolve against and the box rendered as its empty background */
.card__media picture{display:block;width:100%;height:100%}
.card__media img{width:100%;height:100%;object-fit:cover;display:block}
/* -24px against a 56px chip leaves it sitting a little under halfway over the
   photo, which is where the reference puts it */
.card--media .ichip{position:relative;z-index:1;margin-top:calc(var(--sp-6) * -1)}
.card--blue{border-top-color:var(--blue)}
.card--dark{
  background:var(--navy-2);border-inline-color:var(--hair-dark);
  border-bottom-color:var(--hair-dark);color:var(--text-muted-dark);
}
.card--dark h3,.card--dark h4{color:var(--white)}
.card__eyebrow{
  font-size:var(--fs-caption);font-weight:700;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--steel-ink);margin-bottom:var(--sp-2)
}
.card__body{flex:1}
/* min-height keeps the inline card link a legal 44px tap target (WCAG 2.5.5) */
.card__link{
  margin-top:var(--sp-5);font-weight:700;font-size:.875rem;text-transform:uppercase;
  letter-spacing:var(--tr-btn);color:var(--red);display:inline-flex;gap:var(--sp-2);
  align-items:center;text-decoration:none!important;
  min-height:var(--tap);align-self:flex-start;
}
.card__link .btn__arrow{transition:transform var(--dur) var(--ease)}
.card__link:hover .btn__arrow{transform:translateX(4px)}
.card--dark .card__link{color:var(--red-on-dark)}

/* icon chip — brand board style: outlined rounded square */
.ichip{
  width:56px;height:56px;flex:0 0 56px;display:grid;place-items:center;
  border:var(--bw-default) solid var(--hair);border-radius:var(--radius-lg);
  color:var(--red);margin-bottom:var(--sp-5);background:var(--white);
}
.ichip svg{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
.ichip--blue{color:var(--blue)}
.section--navy .ichip{border-color:var(--hair-dark);background:var(--navy-2);color:var(--red-on-dark)}

/* === GRIDS — last-row centring is baked in ========================== */
.grid{display:grid;gap:var(--sp-6)}
.grid--2{grid-template-columns:repeat(2,1fr)}
.grid--3{grid-template-columns:repeat(3,1fr)}
.grid--4{grid-template-columns:repeat(4,1fr)}
/* R-orphan-card-centering: an incomplete last row centres instead of hanging left */
.grid--auto{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--sp-6)}
.grid--auto>*{flex:1 1 300px;max-width:400px}
@media(max-width:900px){.grid--3,.grid--4{grid-template-columns:repeat(2,1fr)}}
@media(max-width:640px){.grid--2,.grid--3,.grid--4{grid-template-columns:1fr}}

/* === STATS — oversized numerals (round-2 editorial move) ============= */
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--sp-8)}
.stat__n{
  font-family:var(--font-display);font-size:var(--fs-display-xl);line-height:.85;
  color:var(--red);display:block;letter-spacing:.01em;
}
.section--navy .stat__n{color:var(--red-on-dark)}
.stat__l{
  display:block;margin-top:var(--sp-3);font-size:var(--fs-caption);font-weight:600;
  text-transform:uppercase;letter-spacing:var(--tr-nav);color:var(--steel-ink);
}
.section--navy .stat__l{color:var(--label-on-dark)}
/* R-proximity-stats-2x2-mobile */
@media(max-width:900px){.stats{grid-template-columns:repeat(2,1fr);gap:var(--sp-10) var(--sp-6)}}

/* === TRUST STRIP ==================================================== */
.trust{
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-6) var(--sp-10);
  padding-block:var(--sp-6);
}
.trust__item{display:flex;align-items:center;gap:var(--sp-3);font-size:.9375rem;font-weight:600}
.trust__item svg{width:22px;height:22px;flex:0 0 22px;stroke:var(--red);fill:none;stroke-width:1.8}
.section--navy .trust__item svg{stroke:var(--red-on-dark)}
.stars{color:var(--red);letter-spacing:2px}
.section--navy .stars{color:var(--red-on-dark)}

/* === PROCESS — numbered vertical timeline =========================== */
.steps{list-style:none;padding:0;margin:0;counter-reset:s}
.steps__item{
  position:relative;padding-left:var(--sp-20);padding-bottom:var(--sp-12);counter-increment:s;
}
.steps__item::before{
  content:counter(s,decimal-leading-zero);position:absolute;left:0;top:-10px;
  font-family:var(--font-display);font-size:3.25rem;line-height:1;color:var(--red);
}
.steps__item::after{
  content:"";position:absolute;left:26px;top:var(--sp-10);bottom:0;width:2px;background:var(--hair);
}
.steps__item:last-child{padding-bottom:0}
.steps__item:last-child::after{display:none}
.section--navy .steps__item::before{color:var(--red-on-dark)}
.section--navy .steps__item::after{background:var(--hair-dark)}
@media(max-width:640px){.steps__item{padding-left:var(--sp-16)}}

/* === TABLES — collapse to cards on mobile =========================== */
.tblwrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
.tbl{width:100%;border-collapse:collapse;background:var(--white);font-size:.9375rem}
.tbl th{
  background:var(--navy);color:var(--white);text-align:left;font-weight:700;
  font-size:var(--fs-caption);text-transform:uppercase;letter-spacing:var(--tr-nav);
  padding:var(--sp-4) var(--sp-5);
}
.tbl td{padding:var(--sp-4) var(--sp-5);border-bottom:var(--bw-thin) solid var(--hair);vertical-align:top}
/* An inline icon in a cell. icon() emits <svg viewBox> with no width or height,
   so with no rule here it fills its container — the comparison table on
   /powder-coating-vs-paint/ rendered five 241x241px check marks and tore the
   layout apart. Sized to match .ticklist svg, the other place the same icon
   sits beside text. */
/* display:inline-block is the load-bearing part — the reset at the top of this
   file sets img,svg,video{display:block}, which put the tick on its own line
   above the label instead of beside it. flex sizing would do nothing here: the
   cell is not a flex container. */
.tbl td svg{display:inline-block;width:17px;height:17px;vertical-align:-3px;
  margin-right:var(--sp-2);stroke:var(--red);fill:none;stroke-width:2}
/* A note under a table sat flush against its bottom border. Scoped to the
   adjacent sibling rather than added to .caption, which is a shared class used
   in several places that do not want the extra space. */
.tblwrap + .caption{display:block;margin-top:var(--sp-4)}
.tbl tbody tr:nth-child(even){background:var(--wash)}
.tbl td:first-child{font-weight:600;color:var(--navy)}
/* R-mobile-card-table */
@media(max-width:700px){
  .tbl,.tbl tbody,.tbl tr,.tbl td{display:block;width:100%}
  .tbl thead{display:none}
  .tbl tr{margin-bottom:var(--sp-5);border:var(--bw-thin) solid var(--hair);
    border-top:var(--bw-accent) solid var(--red);background:var(--white)}
  .tbl td{border-bottom:var(--bw-thin) solid var(--hair);padding:var(--sp-5) var(--sp-5);
    padding-left:45%;position:relative}
  .tbl td:last-child{border-bottom:0}
  .tbl td::before{
    content:attr(data-th);position:absolute;left:var(--sp-4);width:40%;
    font-size:var(--fs-caption);font-weight:700;text-transform:uppercase;
    letter-spacing:var(--tr-nav);color:var(--steel-ink);
    /* a label that wraps ("Powder coating") sat hard against the cell border */
    padding-bottom:3px;
  }
}

/* === FAQ — single-open accordion, native <details> ==================
   Centred block: at 1240px a 900px accordion pinned left leaves a dead
   340px gutter. The block centres; the question/answer text stays left. */
.faq{max-width:900px;margin-inline:auto}
.faq__i{border-bottom:var(--bw-thin) solid var(--hair);background:transparent}
.faq__i[open]{background:var(--white)}
.faq__q{
  cursor:pointer;list-style:none;padding:var(--sp-5) var(--sp-12) var(--sp-5) var(--sp-5);
  font-weight:700;font-size:1.0625rem;color:var(--navy);position:relative;
}
.faq__q::-webkit-details-marker{display:none}
.faq__q::after{
  content:"";position:absolute;right:var(--sp-5);top:50%;width:11px;height:11px;
  border-right:var(--bw-default) solid var(--red);border-bottom:var(--bw-default) solid var(--red);
  transform:translateY(-70%) rotate(45deg);transition:transform var(--dur) var(--ease);
}
.faq__i[open] .faq__q::after{transform:translateY(-30%) rotate(-135deg)}
.faq__a{padding:0 var(--sp-12) var(--sp-6) var(--sp-5)}
.faq__a p{margin-bottom:0}
.section--navy .faq__i{border-bottom-color:var(--hair-dark)}
.section--navy .faq__i[open]{background:var(--navy-2)}
.section--navy .faq__q{color:var(--white)}
.section--navy .faq__q::after{border-color:var(--red-on-dark)}

/* === CALLOUT / QUICK-ANSWER BOX ===================================== */
.callout{
  background:var(--white);border-left:var(--bw-accent) solid var(--red);
  padding:var(--sp-6) var(--sp-8);margin-block:var(--sp-8);
}
.callout__k{
  font-size:var(--fs-caption);font-weight:700;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--red);margin-bottom:var(--sp-2);display:block;
}
.callout p:last-child{margin-bottom:0}
.callout--dark{background:var(--navy-2);border-left-color:var(--red-on-dark);color:var(--text-muted-dark)}
.callout--dark .callout__k{color:var(--red-on-dark)}

/* === CTA BAND (Peak-End) ============================================ */
.ctaband{background:var(--red);color:var(--white);position:relative;overflow:hidden}
/* the navy wedge stays as the base — it is what shows if the image is slow or
   fails, and it is what the scrim below is tinting toward */
.ctaband::before{
  content:"";position:absolute;inset-block:0;right:0;width:38%;
  background:var(--navy);clip-path:polygon(28% 0,100% 0,100% 100%,0 100%);
}
/* QA (Nicole, Aug 11): photo in the wedge so it is not a flat dark slab.
   Same clip-path as ::before so the diagonal reads as one shape. The scrim is
   the load-bearing part: the ghost button is white-on-transparent and sits over
   this wedge, and the photo's ground is near-white, so without a tint the
   button would vanish into it. Measured across .30-.70 with the buttons hidden and the
   worst backdrop pixel under the ghost button scanned: contrast is flat at
   5.88:1 throughout, because that button lands on the red band rather than the
   photo. So the scrim is set for legibility of the WEDGE, not the button. */
.ctaband__media{
  position:absolute;inset-block:0;right:0;width:38%;
  clip-path:polygon(28% 0,100% 0,100% 100%,0 100%);
  overflow:hidden;
}
.ctaband__media img{width:100%;height:100%;object-fit:cover;object-position:70% 50%}
.ctaband__media::after{
  content:"";position:absolute;inset:0;background:rgba(13,27,42,.68);
}
.ctaband__inner{
  position:relative;z-index:1;display:flex;flex-wrap:wrap;align-items:center;
  justify-content:space-between;gap:var(--sp-8);padding-block:var(--sp-16);
}
.ctaband h2{color:var(--white);margin-bottom:var(--sp-3);max-width:20ch}
.ctaband p{color:rgba(255,255,255,.92);margin-bottom:0;max-width:46ch}
.ctaband .btn-row{margin-top:0;flex-shrink:0}
/* below 900 the wedge would be a sliver behind the stacked buttons — the
   original design drops it, and the photo goes with it */
@media(max-width:900px){.ctaband::before,.ctaband__media{display:none}}
/* Above that breakpoint the band is ONE row: copy left, buttons right.
   flex-wrap decides line-breaking from each item's BASE size BEFORE any
   shrinking, so the copy block (518px — its h2 is capped at 20ch and its p at
   46ch) plus a flex-shrink:0 button row only had ~640px of headroom inside the
   1192px container. "Browse Sarasota Services" fits at 576px; "Browse Lakewood
   Ranch Services" is 648px, which overflowed by 6px and dropped the buttons to a
   second line, adding 90px of empty red. The approved template only ever
   rendered the short city name, so this never surfaced until the LSP category
   built all five cities.
   nowrap makes the copy block shrink instead of wrapping — it only engages when
   the row would otherwise have broken, so every page that already fit is
   pixel-unchanged. The mobile stack still comes from the wrap that applies
   below 900px, which is the same breakpoint the wedge uses. */
@media(min-width:901px){.ctaband__inner{flex-wrap:nowrap}}

/* === FORMS ========================================================== */
.form{display:grid;gap:var(--sp-5)}
.field{display:flex;flex-direction:column;gap:var(--sp-2)}
.field label{font-size:.875rem;font-weight:600;color:var(--navy)}
.field .req{color:var(--red)}
.field input,.field select,.field textarea{
  font-family:var(--font-body);font-size:1rem;padding:var(--sp-4);
  border:var(--bw-thin) solid var(--steel);border-radius:var(--radius);
  background:var(--white);color:var(--text-body);min-height:var(--tap);width:100%;
}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:var(--bw-default) solid var(--red);outline-offset:1px;border-color:var(--red)
}
.field textarea{min-height:132px;resize:vertical}
.field__hint{font-size:var(--fs-caption);color:var(--steel-ink)}
/* the whole label is the hit area, so the 20px box is not the tap target */
.consent{display:flex;gap:var(--sp-3);align-items:flex-start;font-size:.875rem;
  cursor:pointer;padding-block:var(--sp-2);min-height:var(--tap)}
.consent input{width:20px;height:20px;min-height:0;flex:0 0 20px;margin-top:2px;accent-color:var(--red)}
.form__row{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-5)}
@media(max-width:640px){.form__row{grid-template-columns:1fr}}
/* skeleton placeholder for an async/embedded form area — never an empty box */
.skeleton{background:var(--wash);border:var(--bw-thin) dashed var(--steel);
  padding:var(--sp-8);text-align:center;color:var(--steel-ink);font-size:.875rem}

/* === FOOTER (M7 #6 — a designed element, not a link list) =========== */
.ftr{background:var(--navy);color:var(--text-muted-dark);position:relative;overflow:hidden}
.ftr::before{content:"";display:block;height:6px;background:var(--swoosh)}
/* machined texture so the largest dark mass on the page is not a flat fill */
.ftr::after{
  content:"";position:absolute;inset:6px 0 0;background:var(--grain);
  pointer-events:none;
}
.ftr>*{position:relative;z-index:1}

/* --- contact strip: the footer's focal element ---------------------
   NAP used to be buried in the narrow brand column where the address wrapped
   across three lines. It is now a raised full-width panel, so the details a
   local-service visitor actually came for read first. */
.ftr__contact{
  display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-1);
  background:var(--navy-2);border-top:var(--bw-accent) solid var(--red);
  margin-top:var(--sp-16);
}
.ftr__c{
  display:flex;align-items:flex-start;gap:var(--sp-4);
  padding:var(--sp-6);text-decoration:none!important;
  border-right:var(--bw-thin) solid var(--hair-dark);
  transition:background var(--dur) var(--ease);min-height:var(--tap);
}
.ftr__c:last-child{border-right:0}
a.ftr__c:hover{background:var(--navy-3);text-decoration:none}
.ftr__cico{
  width:42px;height:42px;flex:0 0 42px;display:grid;place-items:center;
  border:var(--bw-thin) solid var(--hair-dark);border-radius:50%;
  background:var(--navy);
}
.ftr__cico svg{width:19px;height:19px;stroke:var(--red-on-raised);fill:none;stroke-width:1.8}
.ftr__ck{
  display:block;font-size:.6875rem;font-weight:700;text-transform:uppercase;
  letter-spacing:.12em;color:var(--label-on-dark);margin-bottom:3px;
}
.ftr__cv{display:block;font-size:.9375rem;font-weight:600;color:var(--white);line-height:1.45}
@media(max-width:820px){
  .ftr__contact{grid-template-columns:1fr}
  .ftr__c{border-right:0;border-bottom:var(--bw-thin) solid var(--hair-dark)}
  .ftr__c:last-child{border-bottom:0}
}

/* --- link columns ---
   Services carries the longest labels ("Wheel & Rim Powder Coating"), so it
   gets the widest track — the old even split wrapped four of its six links. */
.ftr__main{
  display:grid;grid-template-columns:1.15fr 1.3fr 1fr 1.05fr .9fr;gap:var(--sp-10);
  padding-block:var(--sp-16) var(--sp-12);
}
/* The horizontal lockup's red wordmark measures 1.39:1 on navy but 12.51:1 on
   white, so the footer sets it on a white chip — never straight on the navy. */
/* Spacing here is tighter than the link columns on purpose: the chip, blurb and
   social row are three separate blocks, so the default rhythm stacked up and made
   column one visibly taller and airier than its neighbours. */
/* The chip's white ground and red rule are gone: the footer now uses a white
   knockout of the logo, which sits straight on the navy. Keeping the plate would
   have put a white logo on a white box. */
.ftr__brandchip{
  display:inline-block;margin-bottom:var(--sp-4);
}
.ftr__brandchip img{height:46px;width:auto}
/* The chip became an <a> so the mark links home, which drops it into the
   `.ftr__main a` list-link treatment below — padding, a 5px hover slide and a
   red accent dot. Same collision the social icons already have, so it gets the
   same two overrides. (0,2,0) beats `.ftr__main a` at (0,1,1) on specificity. */
.ftr__main .ftr__brandchip{padding-block:0}
.ftr__main .ftr__brandchip:hover{transform:none}
.ftr__main .ftr__brandchip::before{display:none}

/* --- social ------------------------------------------------------- */
.ftr__social{margin-top:var(--sp-5)}
.ftr__socialrow{display:flex;gap:var(--sp-2);margin-top:var(--sp-3);flex-wrap:wrap}
.ftr__soc{
  width:var(--tap);height:var(--tap);flex:0 0 var(--tap);
  display:grid!important;place-items:center;background:var(--navy-2);
  border:var(--bw-thin) solid var(--hair-dark);color:var(--white);
  transition:background var(--dur) var(--ease),border-color var(--dur) var(--ease),
    transform var(--dur) var(--ease);
}
.ftr__soc:hover{background:var(--red);border-color:var(--red);transform:translateY(-2px)}
.ftr__main .ftr__soc:hover{transform:translateY(-2px)}       /* beat the link slide */
.ftr__main .ftr__soc::before{display:none}                    /* no accent dot here */
.ftr__soc svg{width:19px;height:19px;fill:currentColor;stroke:none}
.ftr__pending{
  margin:var(--sp-3) 0 0;font-size:.6875rem;color:var(--label-on-dark);
  max-width:32ch;line-height:1.4;
}

/* --- powered by ---------------------------------------------------- */
.ftr__eb{display:inline-flex;align-items:center;opacity:.85;
  transition:opacity var(--dur) var(--ease)}
.ftr__eb:hover{opacity:1;text-decoration:none}
.ftr__eb img{height:26px;width:auto}
.ftr__blurb{font-size:.9375rem;max-width:34ch}
.ftr__h{
  font-size:var(--fs-caption);font-weight:700;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--white);margin-bottom:var(--sp-5);
  padding-bottom:var(--sp-2);border-bottom:var(--bw-default) solid var(--red);display:inline-block;
}
.ftr ul{list-style:none;padding:0;margin:0}
.ftr li{margin-bottom:var(--sp-2)}
/* links get an inline accent marker on hover instead of a bare colour swap */
.ftr__main a{
  display:inline-block;color:var(--text-muted-dark);font-size:.9375rem;
  line-height:1.45;padding-block:5px;text-decoration:none;position:relative;
  transition:color var(--dur) var(--ease),transform var(--dur) var(--ease);
}
.ftr__main a:hover{color:var(--white);text-decoration:none;transform:translateX(5px)}
.ftr__main a::before{
  content:"";position:absolute;left:-11px;top:50%;width:5px;height:5px;
  background:var(--red);opacity:0;transform:translateY(-50%);
  transition:opacity var(--dur) var(--ease);
}
.ftr__main a:hover::before{opacity:1}
/* 30ch, not 34: the track is ~218px wide, so a 34ch cap never bound the text and
   the paragraph ran five lines — taller than any link column beside it */
.ftr__blurb{font-size:.875rem;max-width:30ch;line-height:1.55;margin-bottom:0}
/* three tracks so the EB badge is centred on the BAR, not merely spaced between
   its neighbours — space-between would drift it whenever the copy length changes */
.ftr__bar{
  border-top:var(--bw-thin) solid var(--hair-dark);padding-block:var(--sp-6);
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
  gap:var(--sp-5) var(--sp-8);font-size:var(--fs-caption);
}
.ftr__bar a{color:var(--text-muted-dark);text-decoration:none}
.ftr__bar a:hover{color:var(--white);text-decoration:underline}
/* two deliberate lines, broken at the sentence — not left to reflow mid-clause */
.ftr__copy{justify-self:start;line-height:1.55}
.ftr__copy span{display:block}
.ftr__eb{justify-self:center}
.ftr__legal{justify-self:end;display:flex;gap:var(--sp-5);flex-wrap:wrap}
@media(max-width:900px){
  .ftr__bar{grid-template-columns:1fr;justify-items:center;text-align:center}
  .ftr__copy,.ftr__eb,.ftr__legal{justify-self:center}
  .ftr__legal{justify-content:center}
}
/* Breakpoints sit where the tracks actually stop fitting the longest label
   ("Wheel & Rim Powder Coating"), measured — not at round numbers.
   5 tracks need a 1240px viewport (Services track 248px); at 1101 it was 215px
   and wrapped. 3 tracks need 941px (259px); at 821 it was 221px and wrapped. */
@media(max-width:1239px){.ftr__main{grid-template-columns:1.2fr 1fr 1fr;gap:var(--sp-10) var(--sp-8)}}
@media(max-width:940px){.ftr__main{grid-template-columns:1fr 1fr;gap:var(--sp-8)}}
/* 2 tracks need 601px (260px each); at 481 they were 200px and wrapped */
@media(max-width:600px){.ftr__main{grid-template-columns:1fr}}

/* === M7 #8 — sticky mobile CTA bar =================================
   show-by-default + hide on desktop. NEVER display:none + a max-width
   override — the cascade order silently hides it. */
.mobilebar{
  display:flex;position:fixed;bottom:0;left:0;right:0;z-index:9999;
  background:var(--navy);border-top:var(--bw-default) solid var(--red);
}
/* Icon BESIDE the label. The stacked app-tab-bar form was forced by the old
   third cell: at 3-up a cell was 116-144px and "Free Estimate" needed 116px of
   label, so inline it wrapped at every phone width. At 2-up a cell is ~195px on
   a 390px phone and the labels are "Call" and "Specials", so the row form fits
   with room to spare — and two stacked tabs read as a truncated tab bar.
   Same reason the label and icon step up: the cramped 3-up sizes are no longer
   what the space calls for. Floor is still 320px. */
.mobilebar a{
  flex:1;display:flex;align-items:center;justify-content:center;
  gap:var(--sp-2);padding:var(--sp-2) var(--sp-3);color:var(--white);
  /* the 2px top border is part of the bar, so subtract it or the bar overshoots
     the space body reserves for it */
  height:calc(var(--mobilebar-h) - var(--bw-default));
  font-weight:700;font-size:.8125rem;text-transform:uppercase;text-align:center;
  letter-spacing:.04em;text-decoration:none!important;line-height:1.15;
  white-space:nowrap;
}
.mobilebar a+a{border-left:var(--bw-thin) solid var(--hair-dark)}
.mobilebar a.is-accent{background:var(--red)}
/* the red/navy edge is its own divider — the hairline on top of it just reads
   as a lighter stripe down the join */
.mobilebar a.is-accent+a{border-left:0}
.mobilebar svg{width:20px;height:20px;flex:0 0 20px;stroke:currentColor;fill:none;stroke-width:2}
/* the bar's height and the space reserved for it are ONE token — if they drift,
   the bar silently covers the last section of every page */
body{padding-bottom:var(--mobilebar-h)}
@media(min-width:769px){.mobilebar{display:none}body{padding-bottom:0}}

/* === COUPON TICKET (specials — perforated stub, unlike any other card) === */
.ticket{
  display:flex;background:var(--navy);color:var(--text-muted-dark);
  min-height:190px;position:relative;overflow:hidden;
}
.ticket__body{flex:1;padding:var(--sp-6) var(--sp-6) var(--sp-6) var(--sp-8)}
.ticket__body h3{color:var(--white);font-size:1.125rem;margin-bottom:var(--sp-2)}
.ticket__body p{font-size:.9375rem;margin-bottom:0;color:var(--text-muted-dark)}
.ticket__k{
  display:inline-block;font-size:var(--fs-caption);font-weight:700;
  text-transform:uppercase;letter-spacing:var(--tr-nav);color:var(--red-on-dark);
  margin-bottom:var(--sp-3);padding-bottom:var(--sp-1);
  border-bottom:var(--bw-default) solid var(--red-on-dark);
}
.ticket__stub{
  flex:0 0 108px;background:var(--red);display:grid;place-items:center;
  padding:var(--sp-4);position:relative;
}
/* the perforation */
.ticket__stub::before{
  content:"";position:absolute;left:-1px;inset-block:0;width:2px;
  background:repeating-linear-gradient(to bottom,
    var(--navy) 0 7px, transparent 7px 14px);
}
.ticket__price{
  font-family:var(--font-display);font-size:1.5rem;line-height:1.05;color:var(--white);
  text-align:center;letter-spacing:.02em;
}
.ticket__fine{
  display:block;padding:var(--sp-3) var(--sp-8);font-size:.6875rem;
  color:var(--label-on-dark);border-top:var(--bw-thin) solid var(--hair-dark);
}
/* full-size ticket for the specials page itself — the homepage strip is the
   teaser, this is the coupon you actually read the fine print on */
.ticket--lg{flex-direction:column;min-height:0}
.ticket--lg .ticket__top{display:flex}
.ticket--lg .ticket__body{padding:var(--sp-8)}
.ticket--lg .ticket__body h3{font-size:var(--fs-h3);margin-bottom:var(--sp-3)}
.ticket--lg .ticket__body p{font-size:1rem}
.ticket--lg .ticket__stub{flex:0 0 150px}
.ticket--lg .ticket__price{font-size:2rem}
.ticket--lg .ticket__foot{
  display:flex;flex-wrap:wrap;gap:var(--sp-4);align-items:center;
  justify-content:space-between;padding:var(--sp-5) var(--sp-8);
  border-top:var(--bw-thin) dashed var(--hair-dark);
}
.ticket--lg .ticket__fine{padding:0;border:0;max-width:52ch;flex:1 1 280px}
@media(max-width:560px){
  .ticket--lg .ticket__top{flex-direction:column}
  .ticket--lg .ticket__stub{flex:0 0 auto;padding:var(--sp-5)}
  .ticket--lg .ticket__stub::before{
    inset:-1px 0 auto 0;width:auto;height:2px;
    background:repeating-linear-gradient(to right,var(--navy) 0 7px,transparent 7px 14px);
  }
}

/* client-supplied offers not yet provided — an explicit, designed empty state.
   Never an empty box (Copy_Structure_CRO), and never a fabricated offer. */
.offerslot{
  border:var(--bw-default) dashed var(--steel);background:var(--wash);
  padding:var(--sp-12);text-align:center;
}
.offerslot__k{
  display:inline-block;padding:var(--sp-2) var(--sp-5);background:var(--navy);
  color:var(--white);font-size:.6875rem;font-weight:700;text-transform:uppercase;
  letter-spacing:.1em;margin-bottom:var(--sp-5);
}
.offerslot p{margin-inline:auto;max-width:56ch}
.offerslot code{
  font-size:.8125rem;background:var(--white);padding:2px 6px;
  border:var(--bw-thin) solid var(--hair);
}

/* === OVERSIZED NUMERAL (round-2 editorial move) ===================== */
/* QA (Nicole, Aug 11): centre the statistic within its own column. It was
   vertically centred but left-aligned, so it sat hard against the gutter and
   the whitespace all pooled between it and the copy on the right. */
.bignum{display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center}
/* Centred in its grid cell it still READ as sitting left, because the empty
   space to its right is the cell's remainder PLUS the 64px column gap before
   the copy starts. Padding by the gap shifts the centred block right by half
   of it, so the numeral sits centred on the run from the container edge to
   where the copy begins — which is the gap the eye actually compares.
   Only above 900px: below that .split is one column and there is no gap. */
@media(min-width:901px){
  .bignum{padding-left:var(--sp-16)}
}
.bignum__n{
  font-family:var(--font-display);font-size:clamp(5rem,3rem + 9vw,10rem);
  line-height:.8;color:var(--red);letter-spacing:.01em;
}
.bignum__u{font-size:.45em;vertical-align:super;margin-left:.04em}
.bignum__l{
  margin-top:var(--sp-5);font-size:1.0625rem;font-weight:600;
  color:var(--text-body);max-width:24ch;line-height:1.4;
}
.section--navy .bignum__n{color:var(--red-on-dark)}
.section--navy .bignum__l{color:var(--text-muted-dark)}

/* === PULL QUOTE / TESTIMONIAL ======================================= */
.quote{
  margin:var(--sp-16) 0 0;padding:var(--sp-8) var(--sp-10);
  background:var(--white);border-left:var(--bw-accent) solid var(--red);
  max-width:820px;
}
.quote p{
  font-size:1.375rem;line-height:1.5;color:var(--navy);font-weight:500;
  margin-bottom:var(--sp-5);max-width:none;
}
.quote p::before{content:"\201C"}
.quote p::after{content:"\201D"}
.quote cite{
  font-style:normal;font-size:var(--fs-caption);font-weight:700;
  text-transform:uppercase;letter-spacing:var(--tr-nav);color:var(--steel-ink);
}
.section--navy .quote{background:var(--navy-2);border-left-color:var(--red-on-dark)}
.section--navy .quote p{color:var(--white)}
.section--navy .quote cite{color:var(--label-on-dark)}

/* === MAP SLOT ======================================================= */
/* R-map-graceful-degradation: the embed sits ON TOP of a labelled placeholder,
   so a blocked/slow/offline map degrades to a branded panel instead of a
   broken white box. */
.mapslot{
  border:var(--bw-thin) solid var(--hair);border-top:var(--bw-accent) solid var(--blue);
  background:var(--wash);display:flex;flex-direction:column;min-height:420px;
  position:relative;
}
.mapslot::before{
  content:"Map loading — " attr(data-place);
  position:absolute;inset:0 0 34px;display:grid;place-items:center;text-align:center;
  padding:var(--sp-8);font-size:.875rem;font-weight:600;color:var(--steel-ink);
  background:var(--wash) var(--dotgrid);background-size:16px 16px;
}
.mapslot iframe{flex:1;min-height:360px;position:relative;z-index:1;background:transparent}
.mapslot__cap{
  margin:0;padding:var(--sp-3) var(--sp-4);font-size:.6875rem;color:var(--steel-ink);
  border-top:var(--bw-thin) solid var(--hair);background:var(--wash);max-width:none;
}

/* === UTILITIES ====================================================== */
.u-center{text-align:center}
.u-mt0{margin-top:0}
.u-mb0{margin-bottom:0}
.split{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-16);align-items:center}
.split--wide{grid-template-columns:1.25fr .75fr}
.split--narrow{grid-template-columns:.75fr 1.25fr}
/* Text side favoured over the media side. Exists because a 1fr/1fr split leaves
   the copy column 564px, which is 13px short of the widest two-column tick item
   (283px + 283px + gap). Gives 632px -> 306px cells, 23px of headroom. */
.split--textwide{grid-template-columns:.88fr 1.12fr}
/* Media matches the HEIGHT of the copy beside it. A fixed aspect ratio cannot:
   the copy reflows with viewport and content, so any single ratio overhangs at
   some width (measured 4/5 at 552px = 690px against 451-496px of text). */
.split--stretch{align-items:stretch}
/* figure carries a default 1em block margin — it survives u-mb0 on the top side
   and leaves the media exactly 17px short of the row it is meant to fill.
   --media-inset drops the media's top edge to the EYEBROW rather than the very
   top of the column, so it no longer runs past the section's accent rule
   (measured: column top → eyebrow 28px, → H2 67px). */
.split--stretch>figure{
  align-self:stretch;margin:var(--media-inset,28px) 0 0;
  min-height:340px;aspect-ratio:auto;
}
.split--stretch>figure img{width:100%;height:100%;object-fit:cover}
/* the img's height:100% needs a full-height chain to the figure; <picture> sits
   in between and is inline by default, which would collapse it */
.split--stretch>figure picture{display:block;height:100%}
/* and the copy column's last paragraph must not push a trailing margin either */
.split--stretch>div>*:last-child{margin-bottom:0}
.split--stretch>div>.prose>p:last-child{margin-bottom:0}
@media(max-width:900px){
  /* stacked: no column to align to, so the inset would just be a stray gap */
  .split--stretch>figure{margin-top:0;min-height:0;aspect-ratio:16/10}
}
@media(max-width:900px){
  .split,.split--wide,.split--narrow,.split--textwide{grid-template-columns:1fr;gap:var(--sp-10)}
}
.media{position:relative;overflow:hidden}
/* QA (Nicole): a <figure> carries the browser's default 40px inline margin, so
   the image sat 40px narrower on each side than the callout directly above it.
   Zeroed so it spans the prose column. Scoped to .article — that class exists
   only on the resource-page template, so figures elsewhere keep their spacing. */
.article .media{margin-inline:0}
/* Same default-figure-margin problem inside a split: 40px each side would inset
   the media from its own grid column. .split--stretch already zeroes it below;
   this covers a figure in a plain .split. */
.split>figure{margin-inline:0}
/* <picture> is inline by default, so an img inside one has no definite parent
   height for .media img's height:100% to resolve against */
.media picture{display:block}
.media img{width:100%;height:100%;object-fit:cover}
.media--tall{aspect-ratio:4/5}
.media--wide{aspect-ratio:16/10}
.media--square{aspect-ratio:1}
/* a photo always sits in a framed slot, never bare on the page */
.media--framed{border:var(--bw-thin) solid var(--hair);
  border-top:var(--bw-accent) solid var(--red);background:var(--white)}
.chips{display:flex;flex-wrap:wrap;gap:var(--sp-2)}
.chip{
  display:inline-flex;align-items:center;padding:6px var(--sp-4);
  background:var(--white);border:var(--bw-thin) solid var(--hair);
  border-radius:100px;font-size:var(--fs-caption);font-weight:600;color:var(--navy);
  text-decoration:none!important;transition:all var(--dur) var(--ease);
}
.chip:hover{border-color:var(--red);color:var(--red)}
.chip--on{background:var(--navy);border-color:var(--navy);color:var(--white)}
.section--navy .chip{background:var(--navy-2);border-color:var(--hair-dark);color:var(--white)}

/* breadcrumb */
.crumbs{font-size:var(--fs-caption);color:var(--steel-ink);padding-block:var(--sp-4)}
.crumbs a{color:var(--steel-ink);text-decoration:none}
.crumbs a:hover{color:var(--red);text-decoration:underline}
.crumbs span{margin-inline:var(--sp-2);opacity:.5}
/* on any dark hero the breadcrumb must take the dark-surface label colour —
   --steel-ink is tuned for --wash and lands at 2.92:1 on navy */
.hero .crumbs,.hero .crumbs a,
.pagehero .crumbs,.pagehero .crumbs a,
.herofull .crumbs,.herofull .crumbs a{color:var(--text-muted-dark)}
.hero .crumbs a:hover,.pagehero .crumbs a:hover,.herofull .crumbs a:hover{color:var(--white)}

/* preview-only ribbon */
.previewnote{
  background:var(--navy-2);color:var(--label-on-dark);font-size:var(--fs-caption);
  text-align:center;padding:var(--sp-2);letter-spacing:.04em;
}
.previewnote strong{color:var(--red-on-dark)}

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


/* ============================================================
   PART 2 of 2 — apc-ref.css (Skill 5 reference-informed layer)
   Loads AFTER part 1 and deliberately overrides it in places —
   e.g. the .phonebadge__t breakpoint only works from here.
   ============================================================ */
/* ============================================================
   APC — REFERENCE-INFORMED COMPONENTS (M5)
   Structural language adapted from the operator-selected reference
   (gramen-next.netlify.app), re-expressed entirely in APC's own brand.

   REFERENCE OVERLAY RULE — analysed, NOT cloned. What is borrowed here is
   section inventory, proportion and rhythm only. No markup, imagery, copy,
   colour or type from the reference is reproduced. Measured values that
   informed these components:
     container 1200/15px · H1 64px w700 lh77 · CTA #EA1826 r0 14px uppercase
     card surface #102039 r0 shadow:none · rhythm #151515 / #fff / #102039
   APC's equivalents come from the client brand board, not from the above.
   ============================================================ */

/* === TRANSPARENT HEADER OVER A FULL-BLEED HERO ====================== */
/* A transparent header over a photo cannot guarantee contrast for its own text —
   the scrim beneath it varies with the image. So the header carries its OWN
   top-down gradient: white nav copy is always backed by >=.72 navy no matter what
   the hero photo does behind it. Measured white-on-that-blend >= 9:1. */
/* The homepage used to float a transparent header over its hero with a navy
   gradient for contrast. With a white bar that device no longer applies — a
   white gradient over a photo would read as a smear — so the homepage now uses
   the same solid white sticky header as every other page. */
.hdr--over{
  position:sticky;top:0;left:auto;right:auto;z-index:900;
  background:var(--white);
  border-bottom:var(--bw-thin) solid var(--hair);
}
.hdr--over .hdr__inner{padding-bottom:var(--sp-3)}
.hdr--over.is-stuck{background:var(--white);border-bottom-color:var(--hair)}
.pagetop{position:relative}

/* circular phone badge — brand board's icon-chip idiom, made round */
.phonebadge{display:flex;align-items:center;gap:var(--sp-3);text-decoration:none!important;min-height:var(--tap)}
/* QA (Nicole, Aug 11): drop the call badge on mobile. Below 1369px it collapses
   to a bare red ring with no label, which reads as an unexplained icon; the
   number is still one tap away in the drawer and in the sticky bottom bar. */
@media(max-width:720px){ .phonebadge{display:none} }
.phonebadge__ring{
  width:46px;height:46px;flex:0 0 46px;border-radius:50%;background:var(--red);
  display:grid;place-items:center;
}
.phonebadge__ring svg{width:20px;height:20px;stroke:var(--white);fill:none;stroke-width:2}
.phonebadge__t{display:flex;flex-direction:column;line-height:1.25}
.phonebadge__k{font-size:.6875rem;font-weight:600;color:var(--steel-ink);
  text-transform:uppercase;letter-spacing:.08em}
.phonebadge__n{font-size:1rem;font-weight:700;color:var(--navy)}
/* Tier B of the measured header ladder — see apc-global.css. This file loads
   after apc-global, and .phonebadge__t{display:flex} above outranks anything
   apc-global puts in a media query at equal specificity, so this breakpoint has
   to live HERE. Was 1369; raised to 1399 when the logo went 60px (the wider
   lockup needs 1373 to keep the full row). */
@media(max-width:1399px){.phonebadge__t{display:none}}

/* === FULL-BLEED HERO ================================================ */
.herofull{
  position:relative;min-height:min(88vh,760px);display:flex;align-items:center;
  background:var(--navy);overflow:hidden;
}
.herofull__bg{position:absolute;inset:0}
.herofull__bg img{width:100%;height:100%;object-fit:cover}
/* QA (Nicole, Aug 11): lighten the overlay. Was .94/.78/.42 — the photo was
   barely readable. Pulled back to .86/.68/.30, which is as far as it goes while
   the white h1 still clears AA over the text side (measured after the change). */
.herofull__bg::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,rgba(13,27,42,.86) 0 26%,rgba(13,27,42,.68) 46%,rgba(13,27,42,.30) 100%);
}
.herofull__inner{position:relative;z-index:2;width:100%;padding-block:var(--sp-32) var(--sp-24)}
/* width is a px measure, not a ch measure: ch tracks the BODY font, but this
   headline is set in Bebas, so a ch cap wraps at the wrong place */
.herofull h1{color:var(--white);max-width:min(var(--hero-measure),100%);margin-bottom:var(--sp-6)}
.herofull .lead{color:var(--text-muted-dark);max-width:58ch}
.herofull .eyebrow{color:var(--white);border-bottom-color:var(--red-on-dark)}
@media(max-width:768px){
  .herofull{min-height:0}
  /* Was --sp-24 (96px) on top against 64px below, so the headline sat pushed
     down with a dead band above the eyebrow. Levelled to 64px top and bottom.
     .herofull is the HOMEPAGE hero only — inner pages use .pagehero and are
     deliberately untouched. */
  .herofull__inner{padding-block:var(--sp-16) var(--sp-16)}
  .herofull__bg::after{background:linear-gradient(180deg,rgba(13,27,42,.92),rgba(13,27,42,.72))}
}

/* vertical social rail — reference device, APC colours */
.socialrail{
  position:absolute;right:var(--sp-6);top:50%;transform:translateY(-50%);z-index:3;
  display:flex;flex-direction:column;gap:var(--sp-2);align-items:center;
}
.socialrail a{
  width:38px;height:38px;display:grid;place-items:center;background:rgba(255,255,255,.1);
  border:var(--bw-thin) solid rgba(255,255,255,.22);color:var(--white);
  transition:background var(--dur) var(--ease);
}
.socialrail a:hover{background:var(--red);border-color:var(--red)}
.socialrail svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2}
.socialrail__l{
  writing-mode:vertical-rl;font-size:.625rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--label-on-dark);margin-top:var(--sp-3);
}
@media(max-width:1100px){.socialrail{display:none}}

/* === INTERIOR PAGE HERO ==============================================
   Compact sibling of .herofull for every page that is not the homepage:
   same navy + scrimmed photo language, a third of the height, and a
   breadcrumb. Codified here because 11 of the 13 templates use it. */
.pagehero{position:relative;background:var(--navy);overflow:hidden}
.pagehero__bg{position:absolute;inset:0}
.pagehero__bg img{width:100%;height:100%;object-fit:cover}
/* The wheels shot composes its subject in the LEFT ~65% with bare floor on the
   right, so at desktop — where this hero crops vertically only, never
   horizontally — that right third rendered as dead space while the wheels sat
   under the headline. Mirroring moves them into it, and the scrim below runs
   dark-left to light-right, so flipped they land under the LIGHTEST part of it
   and the empty floor becomes the backdrop the text wanted.
   Keyed to the image, not to the page: the next spoke to be built inherits this
   template but not this photo's composition, and must not be flipped blind.
   Wheels are near-symmetric, so the mirror is not readable as one. */
.pagehero__bg img[src*="wheel-powder-coating-hero"]{transform:scaleX(-1)}
/* Two lines, not three (client request). Only .pagehero h1 and .herofull h1
   read --hero-measure, and .lead is capped separately at 64ch, so widening it
   here moves the headline and nothing else. */
.pagehero--wide{--hero-measure:960px}
/* The wider headline reaches 960px instead of 640px, which carries it out of
   the scrim's dark end and over the wheels — measured 3.30:1 -> 2.87:1 at
   1024px, i.e. under the 3:1 that 68px large text needs. The mid stop is pulled
   right and deepened so the dark end covers the headline's new width. The 100%
   stop is untouched, so the wheels at the far right keep their contrast. */
.pagehero--wide .pagehero__bg::after{
  background:linear-gradient(100deg,
    rgba(13,27,42,.88) 0 34%,
    rgba(13,27,42,.74) 62%,
    rgba(13,27,42,.30) 100%);
}
/* QA: matched to the homepage hero so all 13 pages read the same. Was
   95deg .95/.8/.5 with stops at 42%/62% — noticeably heavier than the homepage,
   which is why the interior pages looked murkier than the front door. Now the
   identical gradient to .herofull__bg::after above, angle and stops included. */
.pagehero__bg::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,rgba(13,27,42,.86) 0 26%,rgba(13,27,42,.68) 46%,rgba(13,27,42,.30) 100%);
}
.pagehero__inner{position:relative;z-index:2;padding-block:var(--sp-24) var(--sp-16)}
.pagehero h1{color:var(--white);max-width:min(var(--hero-measure),100%);margin-bottom:var(--sp-5)}
.pagehero .lead{color:var(--text-muted-dark);max-width:64ch;margin-bottom:0}
.pagehero .eyebrow{color:var(--white);border-bottom-color:var(--red-on-dark)}
.pagehero .btn-row{margin-top:var(--sp-8)}
/* Was --sp-20 (80px) on top against 48px below, so the headline sat pushed down
   with a dead band above the eyebrow — the same asymmetry the homepage hero had.
   Levelled to 48px top and bottom: the identical 32px reduction applied to
   .herofull__inner, so both hero types now open at the same rhythm on mobile.
   Covers all 66 inner pages; /404/ and /thank-you/ carry no hero section. */
@media(max-width:768px){.pagehero__inner{padding-block:var(--sp-12) var(--sp-12)}}

/* === ARTICLE — the resource page's signature =========================
   Every other template runs container-width. This one is a NARROW CENTRED
   MEASURE with a byline bar — an article reads as an article, and 68ch is where
   long prose stops fatiguing. Also the template the 15 blog posts inherit. */
.article{max-width:760px;margin-inline:auto}
.article > * { max-width: none; }
.article p{font-size:1.0625rem;line-height:1.7;max-width:none}
.article h2{margin-top:var(--sp-16);margin-bottom:var(--sp-5)}
.article h2:first-child{margin-top:0}
.article h3{margin-top:var(--sp-10);margin-bottom:var(--sp-3)}
.article ul,.article ol{margin-bottom:var(--sp-8)}
.article li{margin-bottom:var(--sp-3);line-height:1.6}

.artmeta{
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-3) var(--sp-6);
  padding:var(--sp-5) 0;margin-bottom:var(--sp-10);
  border-block:var(--bw-thin) solid var(--hair);
  font-size:var(--fs-caption);color:var(--steel-ink);
}
.artmeta__i{display:flex;align-items:center;gap:var(--sp-2);font-weight:600}
.artmeta__i svg{width:16px;height:16px;stroke:var(--red);fill:none;stroke-width:2}
.artmeta__cat{
  padding:3px var(--sp-3);background:var(--navy);color:var(--white);
  font-size:.625rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;
}

/* key figures inline in prose — a cost guide is read for its numbers */
.keyfigs{
  display:grid;grid-template-columns:repeat(2,1fr);gap:var(--sp-1);
  background:var(--hair);border:var(--bw-thin) solid var(--hair);
  margin-block:var(--sp-10);
}
.keyfig{background:var(--white);padding:var(--sp-6)}
.keyfig__n{
  display:block;font-family:var(--font-display);font-size:2.25rem;line-height:1;
  color:var(--red);margin-bottom:var(--sp-2);
}
.keyfig__k{display:block;font-weight:700;color:var(--navy);font-size:.9375rem;margin-bottom:2px}
.keyfig__d{display:block;font-size:.8125rem;color:var(--steel-ink)}
@media(max-width:560px){.keyfigs{grid-template-columns:1fr}}

/* pull quote — an editorial beat inside long prose */
.artquote{
  margin:var(--sp-12) 0;padding:var(--sp-8) var(--sp-10);
  background:var(--navy);color:var(--white);border-left:var(--bw-accent) solid var(--red);
}
.artquote p{font-size:1.25rem;line-height:1.5;color:var(--white);margin:0}
.artquote strong{color:var(--red-on-dark)}

/* === RESOURCE INDEX — the resources hub's signature ==================
   Grouped, numbered rows under category headings. The blog index next door is a
   card grid, so this cannot be one too — and a numbered index reads as a library
   rather than a feed, which is what a set of evergreen guides actually is. */
.resgroup{margin-bottom:var(--sp-16)}
.resgroup:last-child{margin-bottom:0}
.resgroup__k{
  display:flex;align-items:baseline;gap:var(--sp-4);margin-bottom:var(--sp-5);
  padding-bottom:var(--sp-3);border-bottom:var(--bw-default) solid var(--navy);
}
.resgroup__n{font-family:var(--font-display);font-size:1.5rem;line-height:1;color:var(--red)}
.resgroup__t{
  font-size:var(--fs-caption);font-weight:700;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--navy);
}
.resrow{
  display:grid;grid-template-columns:1fr auto;gap:var(--sp-6);align-items:center;
  padding:var(--sp-6) var(--sp-6) var(--sp-6) 0;
  border-bottom:var(--bw-thin) solid var(--hair);
  text-decoration:none!important;
  transition:background var(--dur) var(--ease),padding-left var(--dur) var(--ease);
}
.resrow:hover{background:var(--white);padding-left:var(--sp-6)}
.resrow__t{
  display:block;font-size:1.125rem;font-weight:700;color:var(--navy);
  margin-bottom:var(--sp-2);
}
.resrow:hover .resrow__t{color:var(--red)}
.resrow__d{display:block;font-size:.9375rem;color:var(--text-body);max-width:70ch}
.resrow__meta{display:flex;align-items:center;gap:var(--sp-3);margin-top:var(--sp-3)}
.resrow__type{
  display:inline-block;padding:3px var(--sp-3);background:var(--wash);
  border:var(--bw-thin) solid var(--hair);font-size:.625rem;font-weight:700;
  text-transform:uppercase;letter-spacing:.08em;color:var(--steel-ink);
}
.resrow__vol{font-size:.6875rem;font-weight:700;color:var(--red);
  text-transform:uppercase;letter-spacing:.08em}
.resrow__go{
  width:44px;height:44px;flex:0 0 44px;display:grid;place-items:center;
  border:var(--bw-thin) solid var(--hair);color:var(--red);font-weight:700;
  transition:background var(--dur) var(--ease),border-color var(--dur) var(--ease),
    color var(--dur) var(--ease);
}
.resrow:hover .resrow__go{background:var(--red);border-color:var(--red);color:var(--white)}
@media(max-width:640px){
  .resrow{grid-template-columns:1fr;gap:var(--sp-4)}
  .resrow__go{display:none}
}

/* featured resource — the colour catalog is the highest-volume page on the site */
.resfeat{
  display:grid;grid-template-columns:1fr .9fr;align-items:stretch;
  background:var(--navy);color:var(--text-muted-dark);
  border-top:var(--bw-accent) solid var(--red);overflow:hidden;
}
.resfeat__body{padding:var(--sp-12);display:flex;flex-direction:column;justify-content:center}
.resfeat__body h3{color:var(--white);font-size:var(--fs-h3);margin-bottom:var(--sp-4)}
.resfeat__body p{max-width:46ch}
/* The photo fills whatever height the copy column needs. It is absolutely
   positioned so it can never BE the thing that sets the panel height — that was
   the old swatch grid's failure mode: the cells dictated the height and crushed
   the copy beside them. min-height is the floor for the stacked layout, where
   there is no copy column to borrow height from.
   display:block on the <picture> matters: it is inline by default, so
   height:100% on the img would resolve against nothing and collapse. */
.resfeat__media{position:relative;min-height:260px;background:var(--navy-2)}
.resfeat__media picture{display:block;position:absolute;inset:0}
.resfeat__media img{display:block;width:100%;height:100%;object-fit:cover}
@media(max-width:820px){
  .resfeat{grid-template-columns:1fr}
  .resfeat__media{min-height:220px}
  .resfeat__body{padding:var(--sp-8)}
}

/* === BLOG INDEX — featured post, cluster filters, post grid, pager ===
   The only editorial-index template. A featured lead post at double width, then
   a uniform grid, then real pagination — an index that silently shows the first
   ten of fifteen posts is the same defect as not publishing the other five. */
.feature{
  display:grid;grid-template-columns:1.15fr .85fr;align-items:stretch;
  background:var(--white);border-top:var(--bw-accent) solid var(--red);
  border-inline:var(--bw-thin) solid var(--hair);border-bottom:var(--bw-thin) solid var(--hair);
}
.feature__media{position:relative;min-height:340px;background:var(--navy-2)}
.feature__media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.feature__body{display:flex;flex-direction:column;justify-content:center;padding:var(--sp-10)}
.feature__body h3{font-size:var(--fs-h3);margin-bottom:var(--sp-4)}
.feature__body h3 a{color:var(--navy);text-decoration:none}
.feature__body h3 a:hover{color:var(--red);text-decoration:none}
@media(max-width:820px){
  .feature{grid-template-columns:1fr}
  .feature__media{min-height:220px;order:1}
  .feature__body{order:2;padding:var(--sp-8)}
}

.postmeta{
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-3);
  font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
  color:var(--steel-ink);margin-bottom:var(--sp-4);
}
.postmeta__c{color:var(--red)}
.postmeta__dot{width:4px;height:4px;border-radius:50%;background:var(--hair)}

/* R-orphan-card-centering, and _DESIGN_STANDARD_LOCK.md §1.10: "card grids centre
   an incomplete last row". This was a 3-col CSS grid, so the blog index's 8 tiles
   laid out 3+3+2 and left a visible empty cell in the bottom-right. A grid cannot
   centre a short final row — the columns are fixed — so this uses the same flex
   pattern .grid--auto already uses for the rule.
   flex-basis is an exact third (minus the two gaps) and flex-grow is 0, so tiles
   keep their width instead of stretching to fill the row; only their alignment
   changes. Rows that are already full look identical. */
.postgrid{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--sp-6)}
.postgrid>*{flex:0 1 calc((100% - var(--sp-6) * 2) / 3)}
@media(max-width:960px){.postgrid>*{flex-basis:calc((100% - var(--sp-6)) / 2)}}
@media(max-width:640px){.postgrid>*{flex-basis:100%}}
.post{
  display:flex;flex-direction:column;background:var(--white);
  border:var(--bw-thin) solid var(--hair);border-top:var(--bw-accent) solid var(--red);
  transition:transform var(--dur) var(--ease),border-top-color var(--dur) var(--ease);
}
.post:hover{transform:translateY(-3px);border-top-color:var(--navy)}
.post__media{position:relative;aspect-ratio:16/10;background:var(--wash);overflow:hidden}
.post__media img{width:100%;height:100%;object-fit:cover}
.post__body{padding:var(--sp-6);display:flex;flex-direction:column;flex:1}
/* 1.45 not 1.35: a two-line card title measured 43px, 1px under the 44px tap
   target. It is the card's primary link, so it must clear it. */
.post__body h3{font-size:1.0625rem;line-height:1.45;margin-bottom:var(--sp-3)}
.post__body h3 a{display:block}
.post__body h3 a{color:var(--navy);text-decoration:none}
.post__body h3 a:hover{color:var(--red);text-decoration:none}
.post .card__link{margin-top:auto}

/* pagination — explicit, because 15 posts do not fit one page */
.pager{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:var(--sp-2);margin-top:var(--sp-16);
}
.pager a,.pager span{
  min-width:var(--tap);min-height:var(--tap);padding:0 var(--sp-4);
  display:inline-flex;align-items:center;justify-content:center;
  border:var(--bw-thin) solid var(--hair);background:var(--white);
  font-size:.9375rem;font-weight:700;color:var(--navy);text-decoration:none;
}
.pager a:hover{border-color:var(--red);color:var(--red);text-decoration:none}
.pager .is-on{background:var(--navy);border-color:var(--navy);color:var(--white)}
.pager .is-off{color:var(--steel-ink);background:var(--wash);cursor:default}
.pager__count{
  width:100%;text-align:center;margin-top:var(--sp-4);
  font-size:var(--fs-caption);color:var(--steel-ink);border:0;background:none;
}

/* === COVERAGE BAND + CITY INDEX — the service-areas signature ========
   This page is a directory, so it gets a directory's density: one wide coverage
   panel, then a full-width row PER CITY carrying that city's three LSP links
   inline. Not the compact 4-up nearby rail (that is the city hub's device) and
   not a card grid. */
.coverage{display:grid;grid-template-columns:1fr .8fr;gap:var(--sp-1);
  background:var(--hair);border-top:var(--bw-accent) solid var(--red)}
.coverage .mapslot{border:0;min-height:400px}
.coverage__card{background:var(--navy);color:var(--text-muted-dark);padding:var(--sp-10)}
.coverage__card h2{color:var(--white);font-size:var(--fs-h3);font-family:var(--font-body);
  font-weight:700;text-transform:none;letter-spacing:0;margin-bottom:var(--sp-6)}
.reach{list-style:none;margin:0 0 var(--sp-8);padding:0}
.reach li{
  display:grid;grid-template-columns:76px 1fr;gap:var(--sp-4);align-items:baseline;
  padding:var(--sp-4) 0;border-bottom:var(--bw-thin) solid var(--hair-dark);margin:0;
}
.reach li:last-child{border-bottom:0}
.reach__d{
  font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.12em;
  color:var(--red-on-dark);
}
.reach__c{font-size:.9375rem;font-weight:600;color:var(--white)}
@media(max-width:900px){
  .coverage{grid-template-columns:1fr}
  .coverage .mapslot{min-height:300px}
}

.cityindex{border-top:var(--bw-thin) solid var(--hair)}
.cityrow{
  display:grid;grid-template-columns:290px 1fr auto;gap:var(--sp-8);align-items:center;
  padding:var(--sp-8) 0;border-bottom:var(--bw-thin) solid var(--hair);
}
.cityrow__name{display:block;font-family:var(--font-display);font-size:2rem;
  line-height:1;color:var(--navy);text-transform:uppercase;letter-spacing:.01em}
.cityrow__role{display:block;margin-top:var(--sp-2);font-size:.8125rem;color:var(--steel-ink)}
.cityrow__badge{
  /* margin-bottom matches the margin-top: the badge had top spacing only, so
     the chip row underneath sat flush against it — measured 0px at EVERY
     width, not only on mobile. Even margins sit it properly between the role
     line above and the chips below. */
  display:inline-block;margin-top:var(--sp-3);margin-bottom:var(--sp-3);
  padding:3px var(--sp-3);
  background:var(--navy);color:var(--white);font-size:.625rem;font-weight:700;
  text-transform:uppercase;letter-spacing:.1em;
}
@media(max-width:1000px){
  .cityrow{grid-template-columns:1fr;gap:var(--sp-5);align-items:start}
}

/* === QUICK-ANSWER PANEL — the LSP's signature ========================
   LSPs are the shortest money pages and are written in answer-engine format, so
   the direct answer is lifted OUT of the prose and pulled up over the hero edge.
   Nothing else on the site overlaps a section boundary. */
/* The -64px pull-up was for when this card sat directly under the hero and was
   meant to overlap it. It now follows the trust strip, where that overlap just
   ate the section's top padding (48 - 64 = -16) and jammed the card against the
   strip. Dropped, so the section's own padding reads evenly top and bottom. */
.qanswer{
  position:relative;z-index:3;
  background:var(--white);border-top:var(--bw-accent) solid var(--red);
  border-inline:var(--bw-thin) solid var(--hair);border-bottom:var(--bw-thin) solid var(--hair);
  padding:var(--sp-10);
}
.qanswer__k{
  display:inline-block;font-size:var(--fs-caption);font-weight:700;
  text-transform:uppercase;letter-spacing:var(--tr-nav);color:var(--red);
  margin-bottom:var(--sp-3);padding-bottom:var(--sp-1);
  border-bottom:var(--bw-default) solid var(--red);
}
.qanswer__q{
  font-family:var(--font-body);font-weight:700;font-size:var(--fs-h3);
  color:var(--navy);margin-bottom:var(--sp-4);
}
.qanswer__a{font-size:var(--fs-lead);line-height:1.55;max-width:74ch;margin-bottom:var(--sp-8)}
.qanswer__facts{
  display:grid;grid-template-columns:repeat(4,1fr);gap:var(--sp-1);
  background:var(--hair);border:var(--bw-thin) solid var(--hair);
}
.qanswer__f{background:var(--white);padding:var(--sp-5) var(--sp-6)}
.qanswer__fk{
  display:block;font-size:.6875rem;font-weight:700;text-transform:uppercase;
  letter-spacing:.1em;color:var(--steel-ink);margin-bottom:var(--sp-2);
}
.qanswer__fv{display:block;font-family:var(--font-display);font-size:1.75rem;
  line-height:1;color:var(--navy)}
/* dark variant — same component on a navy band, no inline style overrides */
.qanswer__facts--dark{background:var(--hair-dark);border-color:var(--hair-dark)}
.qanswer__facts--dark .qanswer__f{background:var(--navy-2)}
.qanswer__facts--dark .qanswer__fk{color:var(--label-on-dark)}
.qanswer__facts--dark .qanswer__fv{color:var(--white)}
@media(max-width:900px){
  .qanswer{padding:var(--sp-8) var(--sp-6)}
  .qanswer__facts{grid-template-columns:repeat(2,1fr)}
}
@media(max-width:480px){.qanswer__facts{grid-template-columns:1fr}}

/* === LOCATION PANEL — the location hub's signature ===================
   A location page's job is to prove APC is actually THERE, so the map and NAP sit
   immediately under the hero rather than in a footer strip. The map is wider than
   the detail card because the map is the evidence. */
.locpanel{display:grid;grid-template-columns:1.35fr .65fr;gap:var(--sp-1);
  background:var(--hair);border-top:var(--bw-accent) solid var(--red)}
.locpanel .mapslot{border:0;min-height:430px}
.locpanel__card{background:var(--navy);color:var(--text-muted-dark);padding:var(--sp-10)}
.locpanel__card h2{color:var(--white);font-size:var(--fs-h3);font-family:var(--font-body);
  font-weight:700;text-transform:none;letter-spacing:0;margin-bottom:var(--sp-6)}
.locpanel__row{display:flex;gap:var(--sp-4);align-items:flex-start;margin-bottom:var(--sp-5)}
.locpanel__row svg{width:20px;height:20px;flex:0 0 20px;margin-top:3px;
  stroke:var(--red-on-dark);fill:none;stroke-width:1.8}
.locpanel__k{
  display:block;font-size:.6875rem;font-weight:700;text-transform:uppercase;
  letter-spacing:.12em;color:var(--label-on-dark);margin-bottom:2px;
}
.locpanel__v{display:block;font-size:.9375rem;font-weight:600;color:var(--white);line-height:1.45}
/* a standalone phone link is an ACTION, not inline prose — it needs a real
   44px target rather than the 19px its text alone would give it */
.locpanel__v a{color:var(--white);display:inline-flex;align-items:center;
  min-height:var(--tap);text-decoration:underline;text-underline-offset:3px}
.locpanel__v a:hover{color:var(--red-on-dark)}
.locpanel__badge{
  display:inline-block;margin-bottom:var(--sp-6);padding:6px var(--sp-4);
  background:var(--red);color:var(--white);font-size:.6875rem;font-weight:700;
  text-transform:uppercase;letter-spacing:.1em;
}
.locpanel .btn{width:100%;padding-inline:var(--sp-4)}
@media(max-width:900px){
  .locpanel{grid-template-columns:1fr}
  .locpanel .mapslot{min-height:320px}
}

/* nearby-city rail — a location page always offers the next city over */
.nearby{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--sp-4)}
.nearby a{
  display:block;padding:var(--sp-5) var(--sp-6);background:var(--white);
  border:var(--bw-thin) solid var(--hair);border-left:var(--bw-accent) solid var(--red);
  text-decoration:none!important;transition:transform var(--dur) var(--ease);
}
.nearby a:hover{transform:translateX(4px)}
/* photo across the top of each city tile — same negative-margin trick as
   .card__media, cancelling the tile's own padding without changing the tile */
.nearby a{overflow:hidden}
.nearby__media{
  display:block;aspect-ratio:16/9;overflow:hidden;background:var(--wash);
  margin:calc(var(--sp-5) * -1) calc(var(--sp-6) * -1) var(--sp-4);
}
.nearby__media picture{display:block;width:100%;height:100%}
.nearby__media img{width:100%;height:100%;object-fit:cover;display:block}
.nearby__n{display:block;font-weight:700;color:var(--navy);margin-bottom:2px}
.nearby__d{display:block;font-size:.8125rem;color:var(--steel-ink)}
@media(max-width:900px){.nearby{grid-template-columns:repeat(2,1fr)}}
@media(max-width:520px){.nearby{grid-template-columns:1fr}}

/* === PRICE BAND — the service spoke's signature ======================
   A spoke answers one narrow question, and the question is almost always "what
   does it cost". So the figure leads, immediately under the hero, instead of
   sitting in a table two thirds down the page as it does on a hub. */
/* --navy-2, not --navy: this band now wears the feature strip's layout, and the
   strip sits on the raised surface — on --navy it read as a slightly different
   dark to every other page's strip. Changing the ground changes which red is
   allowed on it: tokens.css permits --red-on-dark ONLY on --navy and
   --red-on-raised ONLY on --navy-2, so the figure and the icons move with it. */
.priceband{background:var(--navy-2);color:var(--text-on-dark);position:relative;overflow:hidden}
/* The diagonal swoosh that used to sit at the right edge is gone. It occupied
   the 220px the Get A Quote button stood in; with the button removed the facts
   grid runs the full width, and the fourth cell landed on top of it — white
   label over the swoosh's white stripe, effectively invisible. The reference
   this band was matched to carries no swoosh either. */
/* NO padding-block. It used to add 40px above and below the grid, which showed
   as an empty navy strip under the last row — the cells carry their own
   breathing room, exactly as the feature strip's do. */
.priceband__inner{position:relative;z-index:1}
/* Every cell wears the feature strip's layout — icon stacked over a bold label
   with its detail line beneath, red corner flag. FIVE equal columns now: the
   price is one of them rather than an oversized figure beside them.

   Dividers are the 1px GRID GAP showing the container's colour through, not a
   border-right on each cell. That matters because the column count changes
   three times below: with borders, every breakpoint needs its own :nth-child
   rules to strip the trailing edge of each row, and they go stale the moment
   the cell count changes. The gap draws correct hairlines in both directions at
   any column count, for free. Same device .trustpanel uses. */
.priceband__facts{
  display:grid;grid-template-columns:repeat(5,1fr);
  gap:var(--bw-thin);background:var(--hair-dark);
}
/* padding matches .featstrip__i exactly — that strip is the reference this band
   was asked to match, and with the band's own padding gone the cells are the
   only thing setting the band's height */
.priceband__f{
  display:block;position:relative;overflow:hidden;
  padding:var(--sp-10) var(--sp-8);background:var(--navy-2);
}
/* and, like .featstrip--inset, the outer cells drop their edge padding so the
   first icon lines up with the page content rather than sitting 32px inside it */
@media(min-width:1101px){
  .priceband__f:first-child{padding-left:0}
  .priceband__f:last-child{padding-right:0}
}
.priceband__f svg{
  display:block;width:30px;height:30px;margin-bottom:var(--sp-4);
  stroke:var(--red-on-raised);fill:none;stroke-width:1.6;
}
.priceband__f>span{display:block;font-size:1.0625rem;font-weight:700;color:var(--white)}
.priceband__f small{
  display:block;margin-top:var(--sp-2);font-weight:500;
  color:var(--text-muted-dark);font-size:.875rem;
}
/* The price takes the same white as every other label (client request) — it is
   now indistinguishable in format AND colour, marked out only by its tag icon.
   The --price class stays on the cell as the hook if that is ever revisited. */
/* the same red corner flag the feature strip cells carry */
.priceband__f::after{
  content:"";position:absolute;right:0;bottom:0;width:28px;height:28px;
  background:var(--red);clip-path:polygon(100% 0,100% 100%,0 100%);
  transition:width var(--dur) var(--ease),height var(--dur) var(--ease);
}
.priceband__f:hover::after{width:40px;height:40px}
/* five cells need one more step than four did — 5 / 3 / 2 / 1. No per-row
   border fixes at any of them: the gap handles the dividers. */
@media(max-width:1100px){ .priceband__facts{grid-template-columns:repeat(3,1fr)} }
@media(max-width:760px){  .priceband__facts{grid-template-columns:repeat(2,1fr)} }
@media(max-width:520px){
  .priceband__facts{grid-template-columns:1fr}
  /* single column: every cell is both first and last on its row, so the inline
     padding stops earning its keep — 32px of it on top of the container's 24px
     gutter pushed the content 56px in on a phone. Same rule .featstrip--inset
     applies at its own one-column breakpoint. */
  .priceband__f{padding-inline:0}
}

/* === STICKY JUMP-RAIL + LONG-FORM ===================================
   The service hub's spine. Hubs are the longest pages on the site, so they get
   an in-page nav that tracks position (scroll-spy in ui.js). Nothing else on the
   site uses a persistent rail, which is what makes the hub read as a hub. */
.toc-layout{display:grid;grid-template-columns:262px 1fr;gap:var(--sp-16);align-items:start}
.toc{position:sticky;top:var(--sp-24)}
.toc__k{
  display:block;font-size:var(--fs-caption);font-weight:700;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--steel-ink);margin-bottom:var(--sp-4);
  padding-bottom:var(--sp-2);border-bottom:var(--bw-default) solid var(--red);
}
.toc__list a{
  display:block;padding:var(--sp-3) var(--sp-4);font-size:.9375rem;font-weight:600;
  color:var(--steel-ink);border-left:var(--bw-default) solid var(--hair);
  text-decoration:none;transition:color var(--dur) var(--ease),
    border-color var(--dur) var(--ease),background var(--dur) var(--ease);
  min-height:var(--tap);display:flex;align-items:center;
}
.toc__list a:hover{color:var(--navy);background:var(--white);text-decoration:none}
.toc__list a.is-on{color:var(--navy);border-left-color:var(--red);background:var(--white)}
.toc__cta{margin-top:var(--sp-6)}
.toc__cta .btn{width:100%;padding-inline:var(--sp-4)}
@media(max-width:960px){
  .toc-layout{grid-template-columns:1fr;gap:var(--sp-10)}
  /* horizontal scroller on narrow screens — a sticky rail would eat the viewport */
  .toc{position:static;overflow-x:auto;-webkit-overflow-scrolling:touch}
  .toc__list{display:flex;gap:var(--sp-2);min-width:max-content}
  .toc__list a{border-left:0;border-bottom:var(--bw-default) solid var(--hair);white-space:nowrap}
  .toc__list a.is-on{border-bottom-color:var(--red)}
  .toc__cta{display:none}
}
.longform>section{scroll-margin-top:var(--sp-20)}
.longform>section+section{margin-top:var(--sp-20)}
.longform h2{margin-bottom:var(--sp-5)}

/* definition rows — markets/audiences stated as prose, not as cards */
.deflist{list-style:none;margin:0;padding:0}
.deflist li{
  display:grid;grid-template-columns:190px 1fr;gap:var(--sp-6);margin:0;
  padding:var(--sp-5) 0;border-top:var(--bw-thin) solid var(--hair);
}
.deflist li:last-child{border-bottom:var(--bw-thin) solid var(--hair)}
.deflist dt,.deflist__k{
  font-weight:700;color:var(--navy);display:flex;align-items:flex-start;gap:var(--sp-3);
}
.deflist__k svg{width:20px;height:20px;flex:0 0 20px;margin-top:2px;
  stroke:var(--red);fill:none;stroke-width:1.8}
.deflist p{margin:0;font-size:.9375rem}
@media(max-width:640px){
  .deflist li{grid-template-columns:1fr;gap:var(--sp-2)}
}

/* Finish swatches — CSS renderings, not photographs (no per-finish photo exists
   in the asset set). All four are the SAME navy on purpose: what separates the
   finishes is how each one handles light, not the colour.

   The previous set drew all four as near-identical dark rectangles — a slight
   gradient apart — so none of them read as its label. Each is now built around
   the thing that actually distinguishes it:
     matte    no specular at all. Light scatters evenly, so the panel is dead
              flat with only a whisper of falloff to stop it reading as a hole.
     satin    ONE broad, soft, feathered sheen. Low contrast, wide, no edges.
     gloss    a NARROW, hard-edged, near-white streak over a mirror gradient —
              high contrast, which is the whole difference from satin.
     texture  stipple with relief: light speckles offset against dark ones, so
              the surface reads as raised grain rather than as printed dots. */
.finishes{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--sp-4)}
.finish{text-align:center}
.finish__sw{
  height:78px;margin-bottom:var(--sp-3);border:var(--bw-thin) solid var(--hair);
  background:var(--navy);
}
.finish--matte .finish__sw{
  background:
    radial-gradient(130% 100% at 50% 0%, rgba(255,255,255,.05) 0, transparent 72%),
    var(--navy);
}
.finish--satin .finish__sw{
  background:
    linear-gradient(102deg,
      transparent 0 10%,
      rgba(255,255,255,.10) 26%,
      rgba(255,255,255,.19) 46%,
      rgba(255,255,255,.10) 66%,
      transparent 90%),
    var(--navy);
}
.finish--gloss .finish__sw{
  background:
    linear-gradient(102deg,
      transparent 0 33%,
      rgba(255,255,255,.28) 37%,
      rgba(255,255,255,.92) 43%,
      rgba(255,255,255,.92) 47%,
      rgba(255,255,255,.28) 51%,
      transparent 55%),
    linear-gradient(180deg, #33547a 0, var(--navy) 52%, #0a1421 100%);
}
.finish--texture .finish__sw{
  background:
    radial-gradient(rgba(255,255,255,.26) 1.1px, transparent 1.3px) 0 0 / 6px 6px,
    radial-gradient(rgba(0,0,0,.5) 1.1px, transparent 1.3px) 3px 3px / 6px 6px,
    var(--navy);
}
.finish__l{font-size:.8125rem;font-weight:700;color:var(--navy)}
@media(max-width:640px){.finishes{grid-template-columns:repeat(2,1fr)}}

/* === ALTERNATING SERVICE ROWS =======================================
   The services overview's spine. Full-bleed to the band, media and copy swap
   sides down the page, and each row carries an oversized index numeral. Nothing
   here is a card grid — that is the homepage's device, not this page's. */
.srow{
  display:grid;grid-template-columns:1fr 1fr;align-items:stretch;
  max-width:var(--band-max);margin-inline:auto;
}
.srow__media{position:relative;min-height:440px;background:var(--navy-2)}
.srow__media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.srow__body{
  display:flex;flex-direction:column;justify-content:center;
  padding:var(--sp-20) clamp(var(--sp-8),6vw,var(--sp-24));
  background:var(--white);
}
.srow:nth-child(even) .srow__body{background:var(--wash)}
/* flip: media moves to the right on alternate rows */
.srow--flip .srow__media{order:2}
.srow__n{
  font-family:var(--font-display);font-size:clamp(3rem,2rem + 3vw,4.5rem);
  line-height:1;color:var(--red);display:block;margin-bottom:var(--sp-3);
}
.srow h2{margin-bottom:var(--sp-4)}
.srow__body p{max-width:52ch}
.srow__meta{
  display:flex;flex-wrap:wrap;gap:var(--sp-2) var(--sp-6);margin-bottom:var(--sp-6);
  font-size:var(--fs-caption);font-weight:700;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--steel-ink);
}
.srow__meta strong{color:var(--red)}
@media(max-width:900px){
  .srow{grid-template-columns:1fr}
  .srow__media{min-height:280px;order:1!important}
  .srow__body{order:2;padding:var(--sp-16) var(--gutter)}
}

/* === CONTACT CHANNEL CARDS ==========================================
   Three oversized targets at the top of the contact page. Deliberately not the
   footer's compact strip: here the channel IS the content, so each is a full
   card with its own accent border and a stated response expectation. */
.channels{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-6)}
.channel{
  display:block;background:var(--white);padding:var(--sp-8);
  border-top:var(--bw-accent) solid var(--red);
  border-inline:var(--bw-thin) solid var(--hair);border-bottom:var(--bw-thin) solid var(--hair);
  text-decoration:none!important;transition:transform var(--dur) var(--ease),
    border-top-color var(--dur) var(--ease);
}
.channel:hover{transform:translateY(-3px);border-top-color:var(--navy)}
.channel__ico{
  width:52px;height:52px;display:grid;place-items:center;margin-bottom:var(--sp-5);
  border:var(--bw-thin) solid var(--hair);border-radius:50%;
}
.channel__ico svg{width:22px;height:22px;stroke:var(--red);fill:none;stroke-width:1.8}
.channel__k{
  display:block;font-size:var(--fs-caption);font-weight:700;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--steel-ink);margin-bottom:var(--sp-2);
}
/* The email is one unbreakable string — 27 chars since the address moved to
   apcpremiumservices.com. Without an explicit break rule it forced the card wider
   than a 320px phone and the whole section was clipped — body{overflow-x:hidden}
   hid the horizontal scrollbar, so it just looked cut off. The break properties
   below therefore STAY as the safety net; they are what stops a clip if the
   address ever gets longer again.
   1.15rem (operator-set) is the largest size at which the current address fits on
   ONE line in the desktop card — at the previous 1.25rem it needed 332px against
   315px available and broke mid-word as "…services.co / m", which is unreadable
   and uncopyable. Narrow-screen sizes are set in the media queries below, each
   measured against its own available width rather than scaled by ratio. */
.channel__v{display:block;font-size:1.15rem;font-weight:700;color:var(--navy);
  line-height:1.35;margin-bottom:var(--sp-2);
  overflow-wrap:anywhere;word-break:break-word;hyphens:auto}

/* THE EMAIL VALUE ONLY — scoped by href so no markup change is needed.
   1.15rem is the intended size and is what renders wherever the address fits.
   It does not fit everywhere: the 3-up grid squeezes each card to 243px of inner
   width around 1024px, and a 27-character address in 700-weight Montserrat needs
   305px at 1.15rem. The old rules let it break mid-word there — "…services.co /
   m" — which is unreadable and cannot be copied in one go.
   5.9cqi is 5.9% of the CARD's inner width. Because the text's width is a fixed
   multiple of its font-size (~16.6x for this string), a percentage-of-container
   size keeps the ratio constant at every card width: 5.9 x 16.6 = 98% of the
   space available, so it always lands on one line with a hair of margin. min()
   then caps it so it never exceeds the intended 1.15rem — at 1440px the cap wins
   and the size is exactly 1.15rem.
   Scoped to the email because it is the only long value: applying it to all three
   cards dragged the phone number and street address down with it for no reason,
   and a clamp() floor to protect them would have re-broken the email.

   The 0.7rem floor is a guard, not a design size. A container-relative length
   collapses with its container: in a context laid out at zero width the value
   computed to 3.3px. That does not happen in a real viewport, but the floor costs
   nothing because it sits below every real breakpoint — the smallest genuine size
   is 12.2px (0.76rem) at a 320px phone — so it can only ever engage in the
   degenerate case it exists for. */
.channel[href^="mailto:"]{container-type:inline-size}
.channel[href^="mailto:"] .channel__v{font-size:clamp(0.7rem,5.9cqi,1.15rem)}

/* Phone links inside a channel card's value.
   The Call The Shop card holds two numbers, so each is its own <a> rather than
   the whole card being one link. The global rule is a{color:var(--red)}, which
   would render them brand red where the single number was navy — so inherit the
   card's colour and keep the underline on hover only, exactly as .ftr__cv a does
   in the footer. The whole card still lifts on hover; these only change how the
   numbers themselves read. */
.channel__v a{color:inherit;text-decoration:none}
.channel__v a:hover{text-decoration:underline;text-underline-offset:2px}

/* Phone links inside a footer contact cell.
   The footer's Call Us cell became a div holding two tel: links (see shell.mjs),
   and the global rule is a{color:var(--red)} — so without this the two numbers
   would render brand red on navy where they had been white, which reads as a
   styling bug rather than a link. Inherit the cell's colour so they look exactly
   as the single number did, and carry the underline on hover only, matching how
   every other footer link behaves.
   Vertical padding on an INLINE element grows the hit area without moving the
   line boxes, so the tap target improves and the cell's height does not change. */
.ftr__cv a{color:inherit;text-decoration:none;padding-block:4px}
.ftr__cv a:hover{text-decoration:underline;text-underline-offset:2px}
.channel__d{display:block;font-size:.875rem;color:var(--steel-ink);margin:0;
  overflow-wrap:break-word}
@media(max-width:480px){
  .channel__v{font-size:1.0625rem}
}
@media(max-width:900px){.channels{grid-template-columns:1fr}}

/* === FORM PAGE LAYOUT — form left, sticky aside right ================ */
/* Column split set by the operator, 2026-08-28: 1.1fr .9fr, was 1.4fr .6fr.
   The form column gives up width so the aside gets close to an even share — at
   1280px that moves the form from ~790px to ~620px and the aside from ~338px to
   ~508px. Only /contact/ uses .formpage, so nothing else is affected, and the
   single-column stack at max-width:1080px (below) is untouched. */
.formpage{display:grid;grid-template-columns:1.1fr .9fr;gap:var(--sp-16);align-items:start}
.formpage__aside{position:sticky;top:calc(var(--sp-24));display:grid;gap:var(--sp-5)}
/* 1080, not 1000: at 1024 the aside was 251px, leaving the CTA 117px of content
   for a 127px label. .btn-row's flex:0 1 auto lets a button shrink below its own
   text, so a narrow column silently breaks the label. */
@media(max-width:1080px){
  .formpage{grid-template-columns:1fr;gap:var(--sp-12)}
  .formpage__aside{position:static}
}
.asidecard{background:var(--navy);color:var(--text-muted-dark);padding:var(--sp-8);
  border-top:var(--bw-accent) solid var(--red)}
.asidecard h3{color:var(--white);font-size:1.125rem;margin-bottom:var(--sp-5)}
.asidecard p{font-size:.9375rem;margin-bottom:0;color:var(--text-muted-dark)}
/* single CTA in a narrow card: full width, and tighter inline padding so the
   label can never be squeezed by the card's own 32px gutters */
.asidecard .btn{width:100%;padding-inline:var(--sp-4);flex:1 0 auto}
.asidecard--light{background:var(--white);color:var(--text-body);
  border-inline:var(--bw-thin) solid var(--hair);border-bottom:var(--bw-thin) solid var(--hair)}
.asidecard--light h3{color:var(--navy)}
.asidecard--light p{color:var(--text-body)}

/* "what happens next" — numbered, compact */
.nextlist{list-style:none;margin:0;padding:0;counter-reset:n}
.nextlist li{
  counter-increment:n;position:relative;padding-left:var(--sp-10);
  margin-bottom:var(--sp-5);font-size:.9375rem;line-height:1.5;
}
.nextlist li:last-child{margin-bottom:0}
.nextlist li::before{
  content:counter(n);position:absolute;left:0;top:0;width:26px;height:26px;
  display:grid;place-items:center;border-radius:50%;
  background:var(--red);color:var(--white);font-size:.75rem;font-weight:700;
}
.nextlist strong{color:var(--white);display:block}
.asidecard--light .nextlist strong{color:var(--navy)}

/* === TRUST PANEL — the About page's own treatment ====================
   The homepage runs the inline .trust row; this is a divided 4-up panel so the
   two pages do not open with the same device. Stacked icon → value → label. */
.trustpanel{
  display:grid;grid-template-columns:repeat(4,1fr);gap:var(--bw-thin);
  background:var(--hair);border-top:var(--bw-accent) solid var(--red);
  border-inline:var(--bw-thin) solid var(--hair);border-bottom:var(--bw-thin) solid var(--hair);
}
.trustpanel__i{background:var(--white);padding:var(--sp-8) var(--sp-6);text-align:center}
.trustpanel__ico{
  width:48px;height:48px;margin:0 auto var(--sp-4);display:grid;place-items:center;
  border:var(--bw-thin) solid var(--hair);border-radius:50%;
}
.trustpanel__ico svg{width:21px;height:21px;stroke:var(--red);fill:none;stroke-width:1.8}
.trustpanel__v{
  display:block;font-family:var(--font-display);font-size:2.1rem;line-height:1;
  color:var(--navy);margin-bottom:var(--sp-2);
}
.trustpanel__v .stars{font-family:var(--font-body);font-size:1rem;letter-spacing:1px}
.trustpanel__l{
  display:block;font-size:.75rem;font-weight:600;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--steel-ink);line-height:1.4;
}
@media(max-width:900px){.trustpanel{grid-template-columns:repeat(2,1fr)}}
@media(max-width:480px){.trustpanel{grid-template-columns:1fr}}

/* === HORIZONTAL TIMELINE ============================================
   Distinct from .procrow (circles + dashed connector) on purpose: an oversized
   numeral sitting ON a solid rule, with a marker dot. */
/* Everything is IN FLOW. The numeral was absolutely positioned at top:-64px,
   which lifted it out of the column and straight through the section heading
   above — no amount of heading margin could have cleared it reliably. */
.htl{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--sp-8)}
.htl__i{display:flex;flex-direction:column}
.htl__n{
  font-family:var(--font-display);font-size:3.25rem;line-height:1;
  color:var(--red);margin-bottom:var(--sp-4);
}
.htl__rule{
  position:relative;height:var(--bw-default);background:var(--hair);
  margin-bottom:var(--sp-6);
}
.htl__rule::before{
  content:"";position:absolute;left:0;top:-5px;width:12px;height:12px;
  background:var(--red);border-radius:50%;
}
.htl h3{font-size:1.0625rem;margin-bottom:var(--sp-2)}
.htl p{font-size:.875rem;margin:0;max-width:34ch}
.section--navy .htl__i::before{background:var(--hair-dark)}
.section--navy .htl__n{color:var(--red-on-dark)}
@media(max-width:1000px){.htl{grid-template-columns:repeat(2,1fr);gap:var(--sp-12) var(--sp-8)}}
@media(max-width:560px){.htl{grid-template-columns:1fr}}

/* === EDITORIAL PROSE (About / Resource pages) ======================== */
/* NO DROP CAP. Removed at the operator's request 2026-08-11 — the lead paragraph
   opens in normal body text. Do not reintroduce it on the resource-page template
   (T12), which was the other place it was planned. */
.prose{max-width:68ch}
.prose p{font-size:1.0625rem}
/* Legal pages: the policy text fills the container edge to edge.
   Operator call, 2026-08-24. Note this sets aside R_legal_page_max_width_px
   (720) and the 68ch measure — a full-width column is a longer line than the
   design system's readability cap intends. Recorded here rather than silently
   overridden, because the token still says 720 and the next person to read it
   will wonder which won. */
.container--legal{padding-left:60px;padding-right:60px}
.container--legal .prose{max-width:none}
/* The paragraph cap has to come off too. Releasing it on .prose alone left the
   h2s and lists spanning the full 1240px container while every <p> stayed at
   the global p{max-width:var(--measure-body)} — 765px, left-aligned, with 807px
   of dead space down its right. Same release .article p already uses. */
.container--legal .prose p{max-width:none}
.prose__ul{margin:0 0 var(--sp-5);padding-left:var(--sp-6)}
.prose__ul li{font-size:1.0625rem;line-height:1.65;margin-bottom:var(--sp-2)}

/* Links inside ARTICLE BODY COPY get an underline at rest.
   The global rule is a{color:var(--red);text-decoration:none} with an underline
   on hover, which is right for the places links appeared until now — buttons,
   cards, nav rows, breadcrumbs — because each of those is already a distinct
   object on the page. A link sitting mid-sentence is not: with the underline
   removed it is distinguished from the text around it by COLOR ALONE, which
   fails WCAG 2.2 SC 1.4.1 (Use of Color) for anyone who cannot separate the red
   from the near-black. The blog posts are the first pages on this site to carry
   in-prose links, so there is no approved styling to inherit and this is new
   rather than a change to a signed-off pattern.
   Scoped to .article body copy so no existing component is touched: buttons and
   the "Read More" affordances inside .article keep their own no-underline rules,
   which win on specificity. */
.article p a:not(.btn):not(.card__link),
.article li a:not(.btn):not(.card__link){
  text-decoration:underline;
  text-decoration-thickness:1px;
  text-underline-offset:2px;
}
.article p a:not(.btn):not(.card__link):hover,
.article li a:not(.btn):not(.card__link):hover{
  text-decoration-thickness:2px;
  color:var(--red-deep);
}

/* numbered editorial list — values stated as prose, deliberately NOT cards */
.vlist{list-style:none;margin:0;padding:0;counter-reset:v}
.vlist li{
  counter-increment:v;position:relative;padding-left:var(--sp-16);
  padding-bottom:var(--sp-8);margin:0;
}
.vlist li:last-child{padding-bottom:0}
.vlist li::before{
  content:counter(v,decimal-leading-zero);position:absolute;left:0;top:-6px;
  font-family:var(--font-display);font-size:2.5rem;line-height:1;color:var(--red);
}
.vlist h3{margin-bottom:var(--sp-2)}
.vlist p{margin-bottom:0;max-width:60ch}
.section--navy .vlist li::before{color:var(--red-on-dark)}

/* === SPEC PANEL — equipment / capability figures ==================== */
.spec{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-1);
  background:var(--hair-dark)}
.spec__i{background:var(--navy-2);padding:var(--sp-8)}
.spec__k{
  display:block;font-size:var(--fs-caption);font-weight:700;text-transform:uppercase;
  letter-spacing:var(--tr-nav);color:var(--label-on-dark);margin-bottom:var(--sp-3);
}
.spec__v{
  display:block;font-family:var(--font-display);font-size:2.75rem;line-height:1;
  color:var(--white);margin-bottom:var(--sp-2);
}
.spec__d{font-size:.875rem;color:var(--text-muted-dark);margin:0;max-width:30ch}
@media(max-width:820px){.spec{grid-template-columns:1fr}}

/* === FOUNDER CARD =================================================== */
.founder{display:grid;grid-template-columns:300px 1fr;gap:var(--sp-10);
  background:var(--white);border-top:var(--bw-accent) solid var(--red);
  border-inline:var(--bw-thin) solid var(--hair);border-bottom:var(--bw-thin) solid var(--hair);
  padding:var(--sp-10)}
.founder__ph{position:relative;background:var(--wash);aspect-ratio:4/5;
  border:var(--bw-thin) dashed var(--steel);display:grid;place-items:center;
  text-align:center;padding:var(--sp-6)}
/* Once a real image fills the slot the dashed outline and inner padding are
   placeholder furniture — they would show as a gap and a stray rule around the
   photo. The slot stays 4:5; the supplied 3:2 holding image was padded to that
   ratio rather than cropped, because object-fit:cover would have kept only the
   middle 53% and clipped the wording. A portrait shot at 4:5 needs no padding. */
.founder__ph--filled{border:0;padding:0;background:var(--navy-2)}
.founder__ph img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.founder__phk{font-size:var(--fs-caption);color:var(--steel-ink);line-height:1.5}
.founder__role{
  display:inline-block;font-size:var(--fs-caption);font-weight:700;
  text-transform:uppercase;letter-spacing:var(--tr-nav);color:var(--red);
  margin-bottom:var(--sp-3);padding-bottom:var(--sp-1);
  border-bottom:var(--bw-default) solid var(--red);
}
/* The full-page stat numeral is ~96px, which breaks "7-DAY" across two lines in
   this column (~193px per cell). Scale it to the container it actually sits in. */
.founder .stat__n{font-size:2.5rem}
.founder .stat__l{font-size:.6875rem}
.founder .stats{gap:var(--sp-6)}
@media(max-width:760px){.founder{grid-template-columns:1fr;gap:var(--sp-8)}
  .founder__ph{max-width:260px}}

/* === NAVY FEATURE STRIP (sits directly under the hero) ============== */
.featstrip{background:var(--navy-2);position:relative;z-index:2}
.featstrip__grid{display:grid;grid-template-columns:repeat(4,1fr)}
.featstrip__i{
  padding:var(--sp-10) var(--sp-8);position:relative;overflow:hidden;
  border-right:var(--bw-thin) solid var(--hair-dark);
}
.featstrip__i:last-child{border-right:0}
/* the red corner flag — reference device, redrawn with APC red */
.featstrip__i::after{
  content:"";position:absolute;right:0;bottom:0;width:34px;height:34px;
  background:var(--red);clip-path:polygon(100% 0,100% 100%,0 100%);
  transition:width var(--dur) var(--ease),height var(--dur) var(--ease);
}
.featstrip__i:hover::after{width:48px;height:48px}
.featstrip__i svg{width:30px;height:30px;stroke:var(--red-on-raised);fill:none;stroke-width:1.6;margin-bottom:var(--sp-4)}
.featstrip__t{font-size:1.0625rem;font-weight:700;color:var(--white);margin-bottom:var(--sp-2)}
.featstrip__d{font-size:.875rem;color:var(--text-muted-dark);margin:0;max-width:26ch}
@media(max-width:900px){
  .featstrip__grid{grid-template-columns:repeat(2,1fr)}
  .featstrip__i:nth-child(2){border-right:0}
  .featstrip__i:nth-child(-n+2){border-bottom:var(--bw-thin) solid var(--hair-dark)}
}
@media(max-width:560px){
  .featstrip__grid{grid-template-columns:1fr}
  .featstrip__i{border-right:0;border-bottom:var(--bw-thin) solid var(--hair-dark)}
}
/* --- inset variant: the strip sits on the page's content width instead of
   running full-bleed. The plain strip uses .band (1600, no gutter) so its first
   item starts ~32px from the viewport edge, while every other section starts at
   the .container content edge. Here the grid takes the container width and the
   OUTER cells drop their outer padding, so item one lines up with the copy above
   and below it. Only where the strip is a single row — at two columns the inner
   cells would end up unevenly padded against their row-mates. */
.featstrip--inset .featstrip__grid{
  max-width:var(--container-max);margin-inline:auto;padding-inline:var(--gutter);
}
@media(min-width:901px){
  .featstrip--inset .featstrip__i:first-child{padding-left:0}
  .featstrip--inset .featstrip__i:last-child{padding-right:0}
}
@media(max-width:560px){
  /* single column: every cell is both first and last on its row */
  .featstrip--inset .featstrip__i{padding-inline:0}
}
/* A cell whose text carries no description is title-only — the star row on the
   rating item takes the icon slot, so it sits where every other cell's icon is
   rather than running inline with the words. --navy-2 is not .section--navy,
   so the on-dark red has to be restated here. */
.featstrip__i .stars{
  display:block;margin-bottom:var(--sp-4);line-height:1;
  font-size:1.35rem;color:var(--red-on-dark);
}
.featstrip__i .featstrip__t small{
  font-weight:400;color:var(--text-muted-dark);margin-left:var(--sp-1);
}

/* === IMAGE COLLAGE + VERTICAL BADGE TAB ============================= */
.collage{position:relative;padding-bottom:var(--sp-16);padding-left:var(--sp-16)}
.collage__a{position:relative;z-index:1}
.collage__a img{width:100%;aspect-ratio:4/3.4;object-fit:cover}
.collage__b{
  position:absolute;left:0;bottom:0;z-index:2;width:52%;
  border:8px solid var(--wash);background:var(--wash);
}
.collage__b img{width:100%;aspect-ratio:4/3;object-fit:cover}
/* vertical red badge — reference device */
.badgetab{
  position:absolute;right:0;top:0;z-index:3;background:var(--red);color:var(--white);
  padding:var(--sp-6) var(--sp-4);writing-mode:vertical-rl;text-align:center;
  display:flex;align-items:center;gap:var(--sp-3);
}
.badgetab__n{font-family:var(--font-display);font-size:2.25rem;line-height:1}
.badgetab__l{font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.14em}
@media(max-width:900px){
  .collage{padding-left:0;padding-bottom:var(--sp-12)}
  .collage__b{width:44%}
}

/* checkmark proof list — two columns */
/* Two columns ONLY while both can hold a full line. The widest item needs 283px;
   auto-fit drops to a single column the moment the container cannot give two
   cells that much, so the text never wraps mid-phrase at any width. A fixed
   1fr/1fr grid wrapped everywhere between 901px and 1140px. */
.ticklist{list-style:none;padding:0;margin:0 0 var(--sp-8);display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,283px),1fr));
  gap:var(--sp-3) var(--sp-5)}
.ticklist li{display:flex;gap:var(--sp-3);align-items:flex-start;font-size:.9375rem;
  font-weight:600;color:var(--navy);margin:0}
.ticklist svg{width:19px;height:19px;flex:0 0 19px;margin-top:2px;
  stroke:var(--red);fill:none;stroke-width:2}
.section--navy .ticklist li{color:var(--white)}
.section--navy .ticklist svg{stroke:var(--red-on-dark)}

/* === VERTICAL TAB LIST + DETAIL PANEL =============================== */
.vtabs{display:grid;grid-template-columns:340px 1fr;gap:var(--sp-8);align-items:start}
/* the rail owns the tab list AND the "all services" link, so the link reads as
   the last item of the column rather than floating beside the section heading */
.vtabs__side{display:flex;flex-direction:column;gap:var(--sp-3)}
.vtabs__all{width:100%;margin-top:var(--sp-2)}
.vtabs__list{display:flex;flex-direction:column;gap:var(--sp-3);margin:0;padding:0;list-style:none}
.vtabs__btn{
  width:100%;display:flex;align-items:center;gap:var(--sp-4);
  padding:var(--sp-5) var(--sp-6);background:var(--white);border:0;cursor:pointer;
  font-family:var(--font-body);font-size:1.0625rem;font-weight:700;color:var(--navy);
  text-align:left;position:relative;min-height:var(--tap);
  transition:background var(--dur) var(--ease),color var(--dur) var(--ease);
}
.vtabs__btn svg{width:24px;height:24px;flex:0 0 24px;stroke:currentColor;fill:none;stroke-width:1.6}
.vtabs__btn[aria-selected="true"]{background:var(--red);color:var(--white)}
/* the arrow pointer on the active tab — reference device */
.vtabs__btn[aria-selected="true"]::after{
  content:"";position:absolute;right:-13px;top:50%;transform:translateY(-50%);
  border:13px solid transparent;border-left-color:var(--red);
}
.vtabs__btn:hover:not([aria-selected="true"]){background:var(--navy);color:var(--white)}
.vtabs__panel{background:var(--white);padding:var(--sp-6);display:none}
.vtabs__panel[data-on="1"]{display:grid;grid-template-columns:.9fr 1.1fr;gap:var(--sp-8)}
.vtabs__panel img{width:100%;height:100%;object-fit:cover;aspect-ratio:4/3.6}
.vtabs__panel h3{margin-bottom:var(--sp-3)}
.vtabs__body p{font-size:.9375rem}
.section--navy .vtabs__btn{background:var(--navy-2);color:var(--white)}
.section--navy .vtabs__panel{background:var(--navy-2)}
.section--navy .vtabs__panel h3{color:var(--white)}
.section--navy .vtabs__panel p{color:var(--text-muted-dark)}
@media(max-width:900px){
  .vtabs{grid-template-columns:1fr}
  /* Stacked, the pointer moves INSIDE the tab, at the right edge, pointing
     down at the panel that now sits below the list.
     It used to hang outside the bottom edge at -13px, which put it in the
     list's 12px gap — so it overlapped the NEXT tab by a pixel and read as
     pointing at that tab rather than at the panel.
     Note it cannot simply be hidden here: .section--navy .vtabs__btn below
     overrides the aria-selected red fill at equal specificity, so on this
     navy section the pointer is the ONLY thing marking the active tab. */
  .vtabs__btn[aria-selected="true"]::after{
    left:auto;right:var(--sp-5);top:50%;bottom:auto;
    transform:translateY(-50%);
    border-width:8px 8px 0;
    border-color:var(--red) transparent transparent;
  }
  .vtabs__panel[data-on="1"]{grid-template-columns:1fr}
  .vtabs__panel img{aspect-ratio:16/9}
}

/* === NUMBERED PROCESS ROW WITH DASHED CONNECTOR ===================== */
.procrow{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--sp-6);position:relative}
.procrow::before{
  content:"";position:absolute;top:44px;left:12.5%;right:12.5%;height:2px;
  background:repeating-linear-gradient(to right,var(--steel) 0 8px,transparent 8px 16px);
}
.proc{text-align:center;position:relative;z-index:1}
.proc__n{
  width:88px;height:88px;margin:0 auto var(--sp-5);border-radius:50%;
  background:var(--wash);border:var(--bw-default) solid var(--hair);
  display:grid;place-items:center;font-family:var(--font-display);
  font-size:2.25rem;color:var(--red);position:relative;
}
.proc__n::after{
  content:"";position:absolute;inset:-9px;border-radius:50%;
  border:var(--bw-thin) dashed var(--hair);
}
.proc h3{font-size:1.125rem;margin-bottom:var(--sp-2)}
.proc p{font-size:.875rem;margin:0 auto;max-width:30ch}
.section--navy .proc__n{background:var(--navy-2);border-color:var(--hair-dark);color:var(--red-on-dark)}
.section--navy .proc__n::after{border-color:var(--hair-dark)}
.section--navy .procrow::before{background:repeating-linear-gradient(to right,var(--hair-dark) 0 8px,transparent 8px 16px)}
.procrow--3{grid-template-columns:repeat(3,1fr)}
@media(max-width:900px){
  .procrow,.procrow--3{grid-template-columns:1fr 1fr;gap:var(--sp-10) var(--sp-6)}
  .procrow::before{display:none}
}
@media(max-width:520px){.procrow,.procrow--3{grid-template-columns:1fr}}

/* === OUTLINED MARQUEE BAND ========================================== */
.marquee{
  overflow:hidden;background:var(--wash);padding-block:var(--sp-10);
  border-block:var(--bw-thin) solid var(--hair);
}
.marquee__t{
  display:flex;gap:var(--sp-8);align-items:center;width:max-content;
  animation:marq 34s linear infinite;
}
.marquee span{
  font-family:var(--font-display);font-size:clamp(3rem,2rem + 4vw,5.25rem);
  line-height:1;text-transform:uppercase;letter-spacing:.02em;white-space:nowrap;
  color:transparent;-webkit-text-stroke:2px var(--steel);
}
.marquee .is-solid{color:var(--red);-webkit-text-stroke:0}
.marquee i{width:22px;height:22px;flex:0 0 22px;background:var(--red);
  clip-path:polygon(50% 0,61% 39%,100% 50%,61% 61%,50% 100%,39% 61%,0 50%,39% 39%)}
@keyframes marq{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media(prefers-reduced-motion:reduce){.marquee__t{animation:none}}

/* === GALLERY (edge-to-edge) ========================================= */
.gallery{display:grid;grid-template-columns:repeat(4,1fr);gap:2px}
.gallery a{position:relative;display:block;overflow:hidden}
.gallery img{width:100%;aspect-ratio:1/1;object-fit:cover;
  transition:transform .5s var(--ease)}
.gallery a:hover img{transform:scale(1.06)}
.gallery a::after{
  content:attr(data-cap);position:absolute;inset:auto 0 0 0;padding:var(--sp-4);
  background:linear-gradient(transparent,rgba(13,27,42,.9));color:var(--white);
  font-size:.8125rem;font-weight:600;
}
@media(max-width:900px){.gallery{grid-template-columns:repeat(2,1fr)}}

/* === RED FORM PANEL + DIAGONAL CUT ================================== */
.bookrow{display:grid;grid-template-columns:.9fr 1.1fr;gap:var(--sp-16);align-items:center;position:relative}
.formpanel{background:var(--red);color:var(--white);padding:var(--sp-10)}
/* Height reservation for the embedded GHL lead form.
   form_embed.js measures the form and writes an explicit px height onto the
   iframe, so the SCRIPT owns the final size and this rule must not fight it.

   The reservation exists only for the moment before that happens: the supplied
   iframe asks for height:100%, which has no definite parent height to resolve
   against, so without a floor the panel paints collapsed.

   380px, NOT the embed's data-height of 581. min-height beats height in the
   cascade, so a floor at or above the real height becomes dead space under the
   Submit button, with the iframe's rounded edge reading as a stray line across
   the column — that is what a 581px floor did on /contact/.
   The height is NOT constant, so the floor cannot be tuned close to it: the same
   form reported 570px then 462px on /contact/ and 638px then 530px on the
   homepage, because the Cloudflare Turnstile widget collapses once it resolves
   and the narrower column wraps fields onto more lines. 380px sits below
   anything five fields, a captcha, a consent line and a submit button can
   occupy, so it reserves most of the space against layout shift while never
   being able to add any.
   No rule on the iframe itself: its height is the script's to set. */
.formembed{min-height:380px;display:block}

/* === LEADCONNECTOR CHAT WIDGET — clear the sticky mobile bar ==========
   THE TWO STATES NEED DIFFERENT BOXES, and the widget hands us the hook: it
   toggles data-active on the host — "false" closed, "true" open.

   CLOSED, the only thing on screen is the 58px bubble at bottom:20px, so a
   zero-height strip one bar-height up is exactly right, and a zero-height host
   covers nothing and intercepts no clicks.

   OPEN, the widget switches — on TOUCH devices only — to a full-screen layout
   where .lc_text-widget is top:0;bottom:0 and .lc_text-widget--box is
   max-height:100%. Against a zero-height containing block those resolved to 1px:
   the panel collapsed to a sliver at the foot of the screen with the form
   entirely off-screen. So when open the host must BE the box the panel should
   fill — the viewport above the bar.

   Desktop Chrome at a narrow width never reproduces this. The mobile layout is
   keyed to touch, so it appears only on a real phone; the closed-bubble fix
   verified fine at 430px wide and still shipped this bug.

   pointer-events:none is NOT usable. pointer-events inherits, this widget does
   not set it on its own layers, and a none host made the bubble unclickable — the
   chat could not be opened at all. The closed host needs no escape hatch because
   it has no area; the open host is meant to capture clicks, as a chat overlay
   should. */
@media(max-width:768px){
  chat-widget[data-active="false"]{
    display:block;
    position:fixed;
    left:0;
    right:0;
    /* visible gap = (this offset + the widget's own 20px) - the 64px bar, so -10
       leaves 10px. Do not subtract more than 20px, or the bubble touches and then
       covers the Specials tab again. */
    bottom:calc(var(--mobilebar-h) - 10px);
    height:0;
    z-index:10000;               /* one above .mobilebar's 9999 */
    transform:translateZ(0);     /* establishes the containing block — see above */
  }
  /* OPEN: the full viewport, minus the bar. This is the fallback for engines
     without :has() — the panel clears the bar but is 56px shorter than the
     widget wants, which pushes Send into the form's own scroller. */
  chat-widget[data-active="true"]{
    display:block;
    position:fixed;
    top:0;
    left:0;
    right:0;
    bottom:var(--mobilebar-h);
    z-index:10000;
    transform:translateZ(0);
  }
  /* An open chat is a full-screen takeover, so the bar stands down and the panel
     takes the height back — enough for the Send button to sit on screen instead
     of below the fold of the widget's form scroller. Nothing is lost: the bar
     returns the moment the chat closes, and body keeps its padding either way,
     so hiding it shifts no layout.
     Order forces :has() here — .mobilebar is emitted BEFORE chat-widget, so no
     sibling combinator can reach back to it. */
  body:has(chat-widget[data-active="true"]) .mobilebar{display:none}
  body:has(chat-widget[data-active="true"]) chat-widget[data-active="true"]{bottom:0}
}
/* 40px of panel padding costs 80px of a 312px viewport, which squeezed the
   submit label onto two lines. The form needs that width more than the panel does. */
@media(max-width:520px){.formpanel{padding:var(--sp-6)}}
.formpanel h2{color:var(--white);margin-bottom:var(--sp-6)}
.formpanel .field label{color:var(--white)}
/* --red on the red panel is 1:1 — literally invisible. The required marker
   inverts to white here and leans on weight, not hue, to read as "required". */
.formpanel .req{color:var(--white);font-weight:700}
.formpanel .field input,.formpanel .field select,.formpanel .field textarea{
  border-color:transparent;background:var(--white)
}
.formpanel .consent{color:rgba(255,255,255,.95)}
.formpanel .consent a{color:var(--white);text-decoration:underline}
.formpanel .field__hint{color:rgba(255,255,255,.85)}
@media(max-width:900px){.bookrow{grid-template-columns:1fr;gap:var(--sp-10)}}

/* review cards — proof device borrowed from the clean competitor reference */
.reviews{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--sp-5)}
.review{background:var(--white);border:var(--bw-thin) solid var(--hair);
  border-top:var(--bw-accent) solid var(--blue);padding:var(--sp-6)}
.review__stars{color:var(--red);letter-spacing:2px;margin-bottom:var(--sp-3)}
.review p{font-size:.9375rem;margin-bottom:var(--sp-4)}
.review__who{display:flex;align-items:center;gap:var(--sp-3);
  font-size:.8125rem;font-weight:700;color:var(--navy)}
.review__av{width:34px;height:34px;border-radius:50%;background:var(--navy);
  color:var(--white);display:grid;place-items:center;font-size:.875rem;flex:0 0 34px}
@media(max-width:640px){.reviews{grid-template-columns:1fr}}

/* STAT VARIANT — the Google rating is a number, not a testimonial, so it gets
   the oversized-numeral treatment the garnet 90% block uses rather than sitting
   next to a real quote pretending to be one. Navy ground + grain, so it reads
   as a panel; the sibling quote card keeps the white one and the pair still
   shares the blue top rule. Numeral is clamped smaller than .bignum__n because
   this sits in a half-width card, not a half-page column. */
.review--stat{
  position:relative;overflow:hidden;
  background:var(--navy);border-color:var(--navy);
  display:flex;flex-direction:column;justify-content:center;text-align:center;
}
.review--stat .review__n{
  font-family:var(--font-display);font-size:clamp(3.25rem,1.5rem + 6vw,5rem);
  line-height:.8;color:var(--red-on-dark);letter-spacing:.01em;
}
.review--stat .review__u{font-size:.45em;vertical-align:super;margin-left:.04em}
.review--stat p{color:var(--text-muted-dark);margin-top:var(--sp-5);max-width:26ch;
  margin-inline:auto}
/* the attribution chip was removed from this card, so its two overrides went
   with it — the caption still names the Google Business Profile in the text */

/* === CERT / TRUST LOGO STRIP ======================================== */
.certstrip{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-6) var(--sp-10);
  justify-content:center}
.cert{
  display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-4) var(--sp-6);
  border:var(--bw-thin) solid var(--hair);background:var(--white);
  font-size:.8125rem;font-weight:700;color:var(--navy);text-transform:uppercase;
  letter-spacing:.05em;
}
.cert svg{width:22px;height:22px;stroke:var(--blue);fill:none;stroke-width:1.8;flex:0 0 22px}
.cert small{display:block;font-size:.625rem;font-weight:600;color:var(--steel-ink);
  text-transform:none;letter-spacing:0;margin-top:2px}
.cert>span{display:block}

.reviews--4{grid-template-columns:repeat(4,1fr)}
@media(max-width:1100px){.reviews--4{grid-template-columns:repeat(2,1fr)}}
@media(max-width:640px){.reviews--4{grid-template-columns:1fr}}

/* === MULTI-STEP QUOTE FORM ========================================== */
.stepper{max-width:940px;margin-inline:auto}
.stepper__nav{
  list-style:none;margin:0 0 var(--sp-8);padding:0;
  display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-1);
}
.stepper__s{
  display:flex;align-items:center;gap:var(--sp-3);margin:0;
  padding:var(--sp-4) var(--sp-5);background:var(--white);
  border-top:var(--bw-accent) solid var(--hair);
  font-size:.875rem;font-weight:700;color:var(--steel-ink);
}
/* --hair on white is 1.52:1 — a future step must still be READABLE, not just
   visibly inactive. State is carried by the accent border and weight instead. */
.stepper__s span{
  font-family:var(--font-display);font-size:1.5rem;line-height:1;color:var(--steel-ink);
}
.stepper__s.is-on{border-top-color:var(--red);color:var(--navy)}
.stepper__s.is-on span{color:var(--red)}
@media(max-width:700px){
  .stepper__nav{grid-template-columns:1fr}
  .stepper__s{border-top-width:var(--bw-thin);border-left:var(--bw-accent) solid var(--hair)}
  .stepper__s.is-on{border-left-color:var(--red)}
}
.stepper__panel{background:var(--white);border:var(--bw-thin) solid var(--hair);
  padding:var(--sp-10)}
@media(max-width:520px){.stepper__panel{padding:var(--sp-6)}}
.stepper__step{border:0;padding:0;margin:0}
/* Progressive enhancement: with no JS every step is visible and the form still
   submits as one long form. js-on hides all but the current step. */
.js-on .stepper__step[hidden]{display:none}
.stepper__step+.stepper__step{margin-top:var(--sp-10);padding-top:var(--sp-10);
  border-top:var(--bw-thin) solid var(--hair)}
.js-on .stepper__step+.stepper__step{margin-top:0;padding-top:0;border-top:0}
.stepper__s{cursor:default}
.stepper__s.is-done{color:var(--navy)}
.stepper__s.is-done span{color:var(--red)}
.stepper__s.is-done::after{
  content:"✓";margin-left:auto;color:var(--red);font-weight:700;font-size:1rem;
}
.stepper__step legend{
  font-family:var(--font-body);font-weight:700;font-size:var(--fs-h3);
  color:var(--navy);margin-bottom:var(--sp-6);padding:0;
}
.choices{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--sp-4)}
@media(max-width:640px){.choices{grid-template-columns:1fr}}
/* The grid stretches the LABEL, but the styled box inside it kept its own
   content height — so a one-line description rendered a visibly shorter card
   than a two-line one. The box must fill the cell it was given. */
.choice{display:flex;cursor:pointer;margin:0}
.choice input{position:absolute;opacity:0;width:0;height:0}
.choice__b{
  flex:1;display:flex;flex-direction:column;padding:var(--sp-6);background:var(--wash);
  border:var(--bw-default) solid var(--hair);
  transition:border-color var(--dur) var(--ease),background var(--dur) var(--ease);
}
.choice__b svg{width:26px;height:26px;stroke:var(--red);fill:none;stroke-width:1.7;
  margin-bottom:var(--sp-3)}
.choice__b strong{display:block;color:var(--navy);font-size:1.0625rem;margin-bottom:2px}
.choice__b small{display:block;color:var(--steel-ink);font-size:.8125rem;line-height:1.4}
.choice input:checked + .choice__b{border-color:var(--red);background:var(--white)}
.choice input:focus-visible + .choice__b{outline:var(--bw-default) solid var(--red);outline-offset:2px}
.choice:hover .choice__b{border-color:var(--steel)}
