/* ==========================================================================
   KOPA site stylesheet
   --------------------------------------------------------------------------
   Everything is scoped to `.kpa` and prefixed `kpa-`. The prefix is fixed and
   generic on purpose: a fix made here ports to every other client site
   verbatim, with no find-replace.

   TOKENS — this block is the entire skin. Change these values from
   03-design/style-guide.md and the site rebrands. Never rename a token:
   roles (--brand, --accent) survive a client change, colours (--navy) don't.

   THE HOUSE LOOK. Geometry, type and rhythm are the KOPA default, taken from
   the GHL/agency references (Poppins, ~12px radius, big type, one idea per
   section, lots of air). COLOUR is not: those references all run the same
   blue-and-orange, and matching it would fail the logo-cover test the whole
   framework hangs on. Palette is per-client, always.
   ========================================================================== */
.kpa {
  /* Brand — warm charcoal. Warm, not the blue-grey default: a cool grey fights
     a red accent and drifts the whole page back toward the sector's blue. */
  --brand:        #262220;
  --brand-deep:   #171514;
  --brand-soft:   #3e3a37;

  /* Accent — the CTA colour. Must clear 4.5:1 both on white and under white. */
  --accent:       #a8202f;
  --accent-dark:  #8f1a28;
  --accent-rgb:   168, 32, 47;
  --accent-tint:  rgba(var(--accent-rgb), .10);

  /* Accent text sitting ON a brand (dark) ground — eyebrows, footer headings,
     trust-strip numbers. This CANNOT be --accent, and not as a style choice:
     an accent dark enough for white text on it (needs luminance <= .183) can
     never also clear 4.5:1 against a charcoal ground (needs >= .24). The two
     requirements have no overlap, so one accent token cannot serve both. The
     template default shipped --accent on --brand at 2.85:1 across six
     components; this token is the fix. Check it against --brand-deep too. */
  --accent-on-brand: #f7f6f4;
  /* The accent lifted just far enough to read as a GRAPHIC on a dark ground.
     --accent itself is 2.52:1 on --brand-deep, so a red icon there is dimmer
     than the muted white it replaces — the opposite of the intent. This is one
     step lighter, 3.35:1, which clears the 3:1 non-text minimum and still reads
     as the brand red rather than a coral. NOT for text: text needs 4.5:1, which
     is what --accent-on-brand is for. */
  --accent-icon:  #c33341;

  /* What sits ON an accent fill. The default accent is dark enough for white
     text; a gold, yellow, lime or pale-blue one is NOT — white on gold is
     about 2.4:1 and fails badly. Making the pairing a token is what stops a
     reskin silently shipping unreadable buttons. Check both directions when
     you change --accent. */
  --on-accent:     #ffffff;
  --on-accent-dim: rgba(255, 255, 255, .88);

  /* Text */
  --ink:          #1c1a18;
  --ink-soft:     #57514c;
  --on-brand:     #f7f6f4;
  --on-brand-dim: rgba(247, 246, 244, .78);

  /* Surfaces */
  --canvas:       #f5f3f0;
  --canvas-warm:  #ebe7e2;
  /* Barely-there accent wash. Used where a band needs to separate from a
     --canvas-warm neighbour without going dark. */
  --canvas-tint:  rgba(168, 32, 47, .045);
  --surface:      #ffffff;
  --line:         rgba(38, 34, 32, .14);

  /* Hero and page-hero scrims, so a brand change carries into the overlays
     instead of leaving the previous client's colour behind. */
  --scrim-rgb:    23, 21, 20;

  /* Form state colours. Tokenised because this build's accent IS red: the
     error red has to stay distinguishable from the brand, and state must
     never rest on hue alone — the field border and the message text carry it. */
  --danger:       #b3261e;
  --danger-tint:  #fdf1ef;
  --danger-line:  #e6b7ae;
  --ok:           #1e6b3a;
  --ok-tint:      #eaf5ee;
  --ok-line:      #b7dbc4;

  /* Type — geometric humanist sans, one family, heavy weights for headings.
     Loaded via site.fonts.googleUrl in the config; the stack degrades to
     system fonts if that request fails, so nothing depends on it. */
  --font-display: Poppins, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-body:    Poppins, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Shape & rhythm — softer and roomier than a default. The references all
     sit at 10–15px and it's a large part of why they read as approachable. */
  --radius:       12px;
  --radius-sm:    8px;
  --radius-pill:  100px;
  --gut:          clamp(1.25rem, 5vw, 3rem);
  --sect:         clamp(4.5rem, 10vw, 7.5rem);
  --max:          1200px;

  /* Soft and wide, not tight and dark — cards should lift off the page. */
  --shadow:    0 1px 2px rgba(var(--scrim-rgb), .04), 0 10px 30px rgba(var(--scrim-rgb), .07);
  --shadow-lg: 0 2px 6px rgba(var(--scrim-rgb), .05), 0 24px 60px rgba(var(--scrim-rgb), .12);
}

/* ==========================================================================
   RESET — scoped
   ========================================================================== */
html, body { margin: 0; padding: 0; background: #f5f3f0; }

.kpa, .kpa *, .kpa *::before, .kpa *::after { box-sizing: border-box; }
.kpa { margin: 0; }
.kpa h1, .kpa h2, .kpa h3, .kpa h4, .kpa p, .kpa ul, .kpa ol, .kpa figure, .kpa blockquote { margin: 0; }
.kpa ul, .kpa ol { padding: 0; list-style: none; }
.kpa img, .kpa svg, .kpa video { display: block; max-width: 100%; height: auto; }
/* NOTE: element-qualified (0,1,1). Any single-class override of button padding
   will lose to this — use a doubled selector, e.g. `.kpa .kpa-thing`. */
.kpa button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
.kpa a { color: inherit; text-decoration: none; }

.kpa {
  font-family: var(--font-body);
  font-size: clamp(1rem, .96rem + .2vw, 1.075rem);
  line-height: 1.6;
  color: var(--ink);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
  /* `clip`, NOT `hidden`. overflow-x:hidden makes this element a scroll
     container, which makes it the sticky ancestor for the header — so the
     header sticks to a box that scrolls with the page, i.e. it does not stick
     at all. `clip` prevents the same horizontal overflow without creating a
     scroll container. The `hidden` line stays first as a fallback for Safari
     below 16. Verified with getBoundingClientRect, not by eye. */
  overflow-x: hidden;
  overflow-x: clip;
}

/* Nothing may push the page wider than the viewport. */
.kpa, .kpa * { min-width: 0; }

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

/* Visually hidden, still read aloud. */
.kpa-sr {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

.kpa-skip {
  position: absolute; left: -9999px; top: 0; z-index: 300;
  background: var(--brand); color: var(--on-brand); padding: .8em 1.4em; border-radius: 0 0 var(--radius) 0;
}
.kpa-skip:focus { left: 0; }

@media (prefers-reduced-motion: reduce) {
  .kpa *, .kpa *::before, .kpa *::after {
    animation-duration: .01ms !important; transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   PRIMITIVES
   ========================================================================== */
.kpa-wrap { width: 100%; max-width: var(--max); margin-inline: auto; padding-inline: var(--gut); }
.kpa-sect { padding-block: var(--sect); }
.kpa-band { background: var(--canvas-warm); }

.kpa-h1, .kpa-h2, .kpa-h3 {
  font-family: var(--font-display); font-weight: 700; line-height: 1.12;
  letter-spacing: -.02em; text-wrap: balance; overflow-wrap: break-word;
}
/* Bigger than a default type scale, on purpose: the references are legible at
   arm's length on a phone, and that legibility is most of what "simple" means. */
.kpa-h1 { font-size: clamp(2.05rem, 1.25rem + 3.3vw, 3.5rem); }
.kpa-h2 { font-size: clamp(1.7rem, 1.25rem + 1.9vw, 2.6rem); }
.kpa-h3 { font-size: clamp(1.2rem, 1.08rem + .5vw, 1.45rem); line-height: 1.25; }

/* Centred section heads get the reference build's pill treatment — same
   letter-spaced accent text, set in a tinted capsule. Left-aligned heads keep
   the plain eyebrow so the type still starts on the grid line. */
/* One eyebrow treatment site-wide. It was a pill on centred section heads and
   bare text everywhere else, which read as two different components. Pill
   everywhere, and heavier than before so it actually registers. */
.kpa-eyebrow, .kpa-guide__body .kpa-eyebrow {
  display: inline-block; padding: .5em 1.15em; border-radius: var(--radius-pill);
  /* Deliberately lighter than --accent-tint, which is shared with the icon
     chips and focus rings. At .10 the pill pulled the eye off the heading it
     is supposed to introduce. */
  background: rgba(var(--accent-rgb), .055); margin-bottom: 1.15rem;
  font-size: .76rem; letter-spacing: .12em;
  /* inline-block still stretches when the parent is a column flex container
     (align-items defaults to stretch), which turned the pill into a full-width
     bar inside the service cards. */
  width: fit-content; align-self: start;
}
.kpa-sect-head--center .kpa-eyebrow { margin-inline: auto; align-self: center; }
/* On a dark ground the light tint disappears — give it its own. */
.kpa-eyebrow--on-brand { background: rgba(247, 246, 244, .1); }
.kpa-eyebrow {
  font-family: var(--font-body);
  font-size: .74rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent-dark); margin-bottom: .9rem;
}
/* A red wash under light text on warm charcoal muddies to purple. A neutral
   wash with warm accent text reads clean on any dark surface. */
.kpa-eyebrow--on-brand { color: #e0555f; background: rgba(247, 246, 244, .1); }
/* The hero and the process band are light grounds now, so their eyebrows take
   the light-ground accent. --accent-on-brand stays for what is genuinely dark:
   the ticker, page heroes, the footer and the mobile menu. */
.kpa-hero .kpa-eyebrow--on-brand, .kpa-proc .kpa-eyebrow--on-brand { color: var(--accent-dark); }
.kpa-hero .kpa-chip { border-color: var(--line); background: var(--surface); color: var(--ink-soft); }

.kpa-lead { font-size: clamp(1rem, .96rem + .2vw, 1.08rem); color: var(--ink-soft); line-height: 1.62; }

/* rem, not ch — `ch` resolves against the container's 1rem, which is far too
   narrow for the display headings sitting inside these blocks. */
.kpa-sect-head { max-width: 46rem; margin-bottom: clamp(2rem, 5vw, 3.25rem); }
.kpa-sect-head .kpa-lead { margin-top: 1rem; max-width: 56ch; }
.kpa-sect-head--center { text-align: center; margin-inline: auto; }
.kpa-sect-head--center .kpa-lead { margin-inline: auto; }

.kpa-note { margin-top: 2rem; padding: 1rem 1.25rem; border-left: 3px solid var(--accent); background: var(--accent-tint); font-size: .9rem; }

/* Buttons ---------------------------------------------------------------- */
.kpa-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .55em;
  padding: .95em 1.9em;
  font-size: .96rem; font-weight: 600; letter-spacing: .01em;
  border-radius: var(--radius-pill);
  transition: background-color .18s ease, color .18s ease, transform .18s ease, box-shadow .18s ease, border-color .18s ease;
  text-align: center;
}
.kpa-btn:active { transform: translateY(1px); }

/* Doubled `.kpa` prefix (0,2,0) deliberately: `.kpa a {color:inherit}` is
   (0,1,1) and would otherwise beat a bare `.kpa-btn--accent` (0,1,0), which
   silently renders dark text on the accent pill. */
.kpa .kpa-btn--accent { background: var(--accent); color: var(--on-accent); box-shadow: var(--shadow); }
.kpa .kpa-btn--accent:hover { background: var(--accent-dark); color: var(--on-accent); }

.kpa .kpa-btn--brand { background: var(--brand); color: var(--on-brand); }
.kpa .kpa-btn--brand:hover { background: var(--brand-soft); color: var(--on-brand); }

.kpa .kpa-btn--ghost { border: 1px solid rgba(255, 255, 255, .42); color: var(--on-brand); }
.kpa .kpa-btn--ghost:hover { border-color: var(--accent); color: var(--accent); }

.kpa .kpa-btn--outline { border: 1px solid var(--brand); color: var(--brand); }
.kpa .kpa-btn--outline:hover { background: var(--brand); color: var(--on-brand); }

/* Chips ------------------------------------------------------------------ */
.kpa .kpa-chips { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 2rem; }
.kpa-chip {
  display: inline-flex; align-items: center; gap: .5em;
  padding: .5em 1em; font-size: .84rem; font-weight: 600;
  border: 1px solid rgba(255, 255, 255, .28); border-radius: var(--radius-pill);
  color: var(--on-brand); background: rgba(255, 255, 255, .07);
}
.kpa-chip svg { flex-shrink: 0; color: var(--accent); }
.kpa .kpa-chips--light { margin-top: .8rem; }
.kpa-chips--light .kpa-chip {
  background: var(--canvas-warm); border-color: var(--line); color: var(--ink-soft); font-size: .76rem; padding: .35em .8em;
}

/* Placeholder blocks -----------------------------------------------------
   A missing image must never ship a broken-image icon. The <img> sits on top
   of this; site.js removes the <img> on error and the labelled block shows. */
/* The placeholder holds its box for layout but paints NOTHING until JS has
   confirmed the image failed. Previously the stripes rendered immediately and
   were removed on load, so every image flashed a "photo coming soon" panel for
   the length of its download. Progressive enhancement is preserved: with JS
   off the image still shows; only the fallback art is withheld. */
.kpa-ph {
  position: relative; display: grid; place-items: center;
  color: var(--brand); overflow: hidden;
}
.kpa-ph--missing {
  background-color: #ddd8d2;
  background-image: repeating-linear-gradient(45deg, rgba(38, 34, 32, .08) 0 12px, transparent 12px 24px);
}
.kpa-ph:not(.kpa-ph--missing)::after { content: none; }
.kpa-ph::after {
  content: attr(data-label);
  font-family: var(--font-body);
  font-size: .66rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  background: rgba(255, 255, 255, .93); border: 1px solid rgba(31, 41, 51, .2);
  padding: .5em 1em; border-radius: 2px; text-align: center; max-width: 85%; line-height: 1.4;
}
.kpa-ph--logo { position: absolute; inset: 0; }
.kpa-ph--logo::after { font-size: .55rem; padding: .3em .6em; }
/* Absolute, like --brand. Left in flow it stacks on top of the <img> and the
   88px result overflows the 44px <li>, pushing logos through the divider. */
.kpa-ph--accred { position: absolute; inset: 0; }
.kpa-ph--accred::after { font-size: .5rem; padding: .25em .5em; letter-spacing: .06em; }

/* ==========================================================================
   HEADER
   ========================================================================== */
.kpa-hdr {
  position: sticky; top: 0; z-index: 60;
  background: rgba(255, 255, 255, .88);
  backdrop-filter: saturate(160%) blur(12px);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid transparent;
  /* Flat on load, lifted once the page moves. JS sets data-stuck past 8px. */
  box-shadow: 0 0 0 rgba(var(--scrim-rgb), 0);
  transition: border-color .25s ease, background-color .25s ease, box-shadow .25s ease;
}
.kpa-hdr[data-stuck="true"] {
  border-bottom-color: var(--line);
  background: rgba(255, 255, 255, .97);
  box-shadow: 0 10px 32px rgba(var(--scrim-rgb), .16), 0 2px 6px rgba(var(--scrim-rgb), .08);
}
/* The ticker sits directly under the header, so the header's shadow would fall
   on it and vanish. Lift the header above it. */
.kpa-hdr { z-index: 60; }
.kpa-ticker { position: relative; z-index: 50; }
.kpa-hdr .kpa-wrap { max-width: 1360px; }
.kpa-hdr__in { display: flex; align-items: center; gap: clamp(.75rem, 3vw, 2.5rem); min-height: 72px; }

.kpa-logo { position: relative; display: flex; align-items: center; color: var(--brand); flex-shrink: 0; height: clamp(32px, 3.5vw, 44px); width: clamp(150px, 19vw, 215px); }
.kpa-logo img { height: 100%; width: auto; object-fit: contain; object-position: left center; position: relative; }

.kpa-nav { display: none; margin-left: auto; }
/* DIRECT CHILDREN ONLY. As a descendant selector this also matched the
   dropdown's own <ul class="kpa-drop__grid"> — and at (0,2,1) it outranked
   .kpa .kpa-drop__grid at (0,2,0), forcing all thirteen menu items into a
   single flex row on top of each other. Same trap as the reset rules in
   CLAUDE.md: the selector reached further than intended. */
.kpa .kpa-nav > ul { display: flex; align-items: center; gap: clamp(1rem, 2vw, 1.9rem); }
.kpa .kpa-nav > ul > li { display: flex; align-items: center; }
/* TOP-LEVEL LINKS ONLY. As descendant selectors these also styled every link
   inside the dropdown — and `white-space: nowrap` there stopped the service
   descriptions wrapping, so they overflowed the panel and the two columns
   printed on top of each other. Third time a .kpa-nav descendant selector has
   reached into .kpa-drop; they are all scoped to `> ul > li >` now. */
.kpa-nav > ul > li > a {
  display: inline-flex; align-items: center;
  font-size: .92rem; font-weight: 500; color: var(--ink-soft);
  padding-block: .4rem; position: relative; transition: color .16s ease; white-space: nowrap;
}
.kpa-nav > ul > li > a::after {
  content: ""; position: absolute; left: 0; right: 100%; bottom: 0;
  height: 2px; background: var(--accent); transition: right .22s ease;
}
.kpa-nav > ul > li > a:hover { color: var(--brand); }
.kpa-nav > ul > li > a:hover::after, .kpa-nav > ul > li > a[aria-current="page"]::after { right: 0; }
.kpa-nav > ul > li > a[aria-current="page"] { color: var(--brand); }

.kpa-hdr__end { display: flex; align-items: center; gap: clamp(.5rem, 2vw, 1.25rem); margin-left: auto; }

/* Always visible, every width. This is a build gate, not a decoration. */
/* On desktop this is a number, not a call to action. Nobody clicks a phone
   number on a laptop — they read it and pick up a phone — so dressing it as a
   button put a second CTA next to the real one and split the attention the
   framework says should sit on one thing (§13.1). It stays a tel: link and
   stays tracked; it just stops competing.

   The pill lives in the mobile block below, where it IS the tappable control. */
.kpa-hdr__tel {
  display: inline-flex; align-items: center; gap: .55em;
  font-weight: 700; color: var(--brand); white-space: nowrap;
  font-size: 1.02rem; letter-spacing: -.01em;
}
.kpa-hdr__tel:hover { color: var(--accent-dark); }
.kpa-hdr__tel svg { flex-shrink: 0; color: var(--accent-dark); }
/* The "CALL" label was doing the work the phone icon already does. */
.kpa-hdr__tel small { display: none; }
.kpa-hdr__cta { display: none; }

/* Below the desktop breakpoint the call control collapses to a round icon
   button. The gate is a tappable call affordance above the fold, not the
   digits themselves — the full number is one tap away in the mobile menu and
   sits in the footer of every page. An accessible label carries it for screen
   readers. Do NOT remove the control itself. */
@media (max-width: 999px) {
  .kpa-hdr__tel {
    width: 46px; height: 46px; padding: 0; justify-content: center;
    border-radius: 50%; flex-shrink: 0;
    /* Border and background belong here now, not on the base rule — this is the
       only width at which the control is meant to look like a button. */
    border: 1px solid var(--line); background: var(--surface);
  }
  .kpa-hdr__tel:hover { border-color: var(--accent); }
  .kpa-hdr__tel-txt { display: none; }
  .kpa-hdr__tel svg { width: 19px; height: 19px; }
}
@media (min-width: 1000px) {
  .kpa-nav { display: block; }
  /* Header buttons sit in a 72px bar — the page-body padding (.95em/1.9em)
     makes them the loudest thing in the header and forces the label to wrap. */
  .kpa .kpa-hdr__cta { display: inline-flex; padding: .68em 1.35em; font-size: .9rem; white-space: nowrap; }
  .kpa-hdr__end { margin-left: 0; }
  .kpa-hdr__in { justify-content: space-between; }
}

/* Burger — in the header flow, not fixed. The reference build floated it and
   then had to fight its own stacking context. */
.kpa-burger {
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  width: 44px; height: 44px; border-radius: var(--radius); flex-shrink: 0;
}
.kpa-burger span { display: block; width: 24px; height: 2px; background: var(--brand); border-radius: 2px; transition: transform .3s ease, opacity .2s ease; }
@media (min-width: 1000px) { .kpa-burger { display: none; } }

/* ==========================================================================
   NAV DROPDOWN — icon, label and a line of description per item.
   Opens on hover AND on focus/click, so it works with a keyboard and on a
   touch screen that reports hover. Closes on Escape and on outside click.
   ========================================================================== */
.kpa-nav__has { position: relative; }
.kpa .kpa-nav__top {
  display: inline-flex; align-items: center; gap: .35em;
  font-size: .92rem; font-weight: 500; color: var(--ink-soft);
  padding-block: .4rem; position: relative; white-space: nowrap; transition: color .16s ease;
}
.kpa-nav__top svg { transition: transform .22s ease; }
.kpa-nav__has:hover .kpa-nav__top, .kpa-nav__top[aria-expanded="true"] { color: var(--brand); }
.kpa-nav__top[aria-expanded="true"] svg { transform: rotate(180deg); }

/* The bridge below is load-bearing: without it the .9rem gap between the
   trigger and the panel breaks the :hover chain and the menu is unusable
   with a mouse. */
.kpa-drop::before { content: ""; position: absolute; left: 0; right: 0; top: -1rem; height: 1rem; }
.kpa-drop {
  position: absolute; top: calc(100% + .9rem); left: 50%; transform: translate(-50%, -6px);
  z-index: 70; min-width: min(760px, 78vw); max-width: min(760px, calc(100vw - 3rem));
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); padding: clamp(1rem, 2vw, 1.5rem);
  opacity: 0; visibility: hidden; transition: opacity .2s ease, transform .2s ease, visibility .2s;
}
/* A gap between the trigger and the panel would close the menu as the pointer
   crosses it; this invisible bridge keeps the hover intent alive. */
.kpa-drop::before { content: ""; position: absolute; left: 0; right: 0; top: -.9rem; height: .9rem; }
.kpa-nav__has:hover .kpa-drop,
.kpa-nav__has:focus-within .kpa-drop,
.kpa-drop[data-open="true"] { opacity: 1; visibility: visible; transform: translate(-50%, 0); }

.kpa .kpa-drop__grid { display: grid; gap: .25rem; }
/* Three columns once the descriptions wrap — thirteen services stacked two-up
   ran the panel off the bottom of the screen. Descriptions clamp to two
   lines so every row is the same height. */
@media (min-width: 700px) { .kpa .kpa-drop__grid { grid-template-columns: repeat(3, 1fr); gap: .15rem .75rem; } }
/* min-width:0 on every level. Grid and flex items default to min-width:auto,
   which refuses to shrink below the content's intrinsic width — so the
   descriptions overflowed their column and the two columns printed on top of
   each other. Every nested flex/grid child in this menu needs it. */
.kpa .kpa-drop__grid > li { min-width: 0; }
.kpa-drop__grid a {
  display: flex; gap: .85rem; align-items: flex-start; min-width: 0;
  padding: .55rem .6rem; border-radius: var(--radius-sm); transition: background-color .16s ease;
}
/* flex-start is right for the services menu, where every item is a bold label
   over a description. The areas menu is one line since the postcodes came off,
   and a top-aligned single line sits high against a 30px icon tile.
   Driven off the DOM with :has() rather than a second class, so it follows the
   content: give an item a description again and it top-aligns again by itself.
   (0,1,2) here, so it beats the rule above at (0,1,1). */
.kpa-drop__grid a:not(:has(small)) { align-items: center; }

/* Reviews-coming placeholder. Deliberately quiet: a big empty panel where the
   proof should be draws attention to the absence. One line, one star, done. */
.kpa-revsoon {
  display: flex; align-items: center; justify-content: center; gap: .7rem;
  padding: 1.6rem 1.4rem; border: 1px dashed var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink-soft); text-align: center;
}
.kpa-revsoon svg { color: var(--accent); flex-shrink: 0; }
.kpa-revsoon p { font-size: .95rem; max-width: 56ch; }
.kpa-reviews-embed { border-radius: var(--radius); overflow: hidden; }
.kpa-drop__txt { min-width: 0; }
.kpa-drop__txt strong, .kpa-drop__txt small { overflow-wrap: anywhere; }
.kpa-drop__grid a:hover { background: var(--canvas); }
/* The tile was inert on hover while every other icon on the site animates.
   Matches .kpa-card__ico so the menu feels like the same site as the page. */
.kpa-drop__ico { transition: background-color .2s ease, color .2s ease, transform .2s ease; }
.kpa-drop__grid a:hover .kpa-drop__ico { background: var(--accent); color: var(--on-accent); transform: scale(1.09); }
.kpa-drop__txt strong { transition: color .2s ease; }
.kpa-drop__grid a:hover .kpa-drop__txt strong { color: var(--accent-dark); }
.kpa-drop__ico {
  flex-shrink: 0; width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; background: var(--accent-tint); color: var(--accent-dark);
}
.kpa-drop__txt strong { display: block; font-size: .88rem; font-weight: 700; color: var(--brand); line-height: 1.25; }
.kpa-drop__txt small { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; margin-top: .15rem; font-size: .78rem; line-height: 1.4; color: var(--ink-soft); }

/* Mobile menu sub-items */
.kpa-menu__sub { display: flex; flex-wrap: wrap; gap: .4rem; padding: .3rem 0 1rem; }
.kpa .kpa-menu__sub a {
  font-family: var(--font-body); font-size: .84rem; font-weight: 600; padding: .35em .85em;
  border: 1px solid rgba(255,255,255,.2); border-radius: var(--radius-pill); color: var(--on-brand-dim);
}
.kpa .kpa-menu__sub a::after { display: none; }
.kpa .kpa-menu__sub a:hover { color: var(--accent-on-brand); padding-left: .85em; }

/* ==========================================================================
   COVERAGE MAP — pills beside a static map.
   A STATIC image, deliberately: an embedded Google map is a third-party
   iframe, costs LCP, and needs consent-gating for a decoration. A screenshot
   plus a real link does the same job for free.
   ========================================================================== */
.kpa-coverage__grid { display: grid; gap: clamp(2rem, 4vw, 3.5rem); align-items: center; }
@media (min-width: 900px) { .kpa-coverage__grid { grid-template-columns: 1.25fr .75fr; align-items: center; } }
.kpa-coverage__pills { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.5rem; }
.kpa-coverage__pills a {
  display: inline-flex; align-items: center; gap: .45em;
  font-size: .88rem; font-weight: 600; padding: .5em 1em;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-pill);
  color: var(--ink-soft); transition: all .16s ease;
}
.kpa-coverage__pills a svg { color: var(--accent-dark); flex-shrink: 0; }
.kpa-coverage__pills a:hover { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
.kpa-coverage__pills a:hover svg { color: var(--accent); }
.kpa-covmap { display: block; width: 100%; max-width: 330px; height: auto; margin-inline: auto; border-radius: var(--radius); }
/* No fixed ratio: the map is an inline SVG with its own aspect, and a
   16/11 crop cut the southern half of the patch off. */
.kpa-coverage__map { position: relative; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
.kpa-coverage__map .kpa-ph { width: 100%; height: 100%; }
.kpa-coverage__map img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.kpa-coverage__link {
  position: absolute; left: 1rem; bottom: 1rem; z-index: 2;
  display: inline-flex; align-items: center; gap: .45em;
  background: var(--surface); color: var(--brand); font-size: .85rem; font-weight: 700;
  padding: .55em 1.1em; border-radius: var(--radius-pill); box-shadow: var(--shadow);
}
.kpa-coverage__link:hover { color: var(--accent-dark); }
.kpa-coverage__badge {
  position: absolute; left: 1rem; bottom: 1rem; z-index: 2;
  background: var(--brand); color: var(--on-brand-dim);
  border-radius: var(--radius-sm); padding: .6rem .9rem; font-size: .78rem; box-shadow: var(--shadow);
}
.kpa-coverage__badge strong { display: block; color: #fff; font-size: .86rem; }

/* ==========================================================================
   TRUST TICKER — under the header
   Claims scrolling past one at a time get read; five stacked in a static row
   do not. The track is duplicated in markup so the loop is seamless; reduced
   motion drops the duplicate and wraps the set instead of animating it.
   ========================================================================== */
.kpa-ticker { background: var(--brand-deep); color: var(--on-brand-dim); overflow: hidden; }
.kpa-ticker__track { display: flex; width: max-content; animation: kpa-marquee 38s linear infinite; }
.kpa-ticker__track:hover, .kpa-ticker__track:focus-within { animation-play-state: paused; }
.kpa .kpa-ticker__set { display: flex; align-items: center; }
.kpa .kpa-ticker__set li { display: inline-flex; align-items: center; gap: .5em; }
/* Icons carry the accent; the separator dots are punctuation and recede.
   It was the other way round, which made the dots louder than the marks. */
.kpa-ticker__set li svg { flex-shrink: 0; color: #e0555f; }
.kpa-ticker__set li {
  display: inline-flex; align-items: center; gap: .55em; white-space: nowrap;
  padding: .7rem clamp(1.25rem, 3vw, 2.25rem);
  font-size: .84rem; font-weight: 600;
}
.kpa-ticker__set li::after {
  content: ""; width: 4px; height: 4px; border-radius: 50%;
  background: rgba(247, 246, 244, .28); margin-left: clamp(1.25rem, 3vw, 2.25rem);
}
@keyframes kpa-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
  .kpa-ticker__track { animation: none; width: 100%; }
  .kpa .kpa-ticker__set:last-child { display: none; }
  .kpa .kpa-ticker__set { flex-wrap: wrap; justify-content: center; }
}

/* ==========================================================================
   MOBILE MENU
   ========================================================================== */
.kpa-menu {
  position: fixed; inset: 0; z-index: 200; background: var(--brand); color: var(--on-brand);
  display: flex; flex-direction: column; padding: clamp(1rem, 5vw, 2rem) var(--gut) clamp(1.5rem, 5vw, 2.5rem);
  opacity: 0; visibility: hidden; transform: translateY(-8px);
  transition: opacity .3s ease, transform .3s ease, visibility .3s;
  overflow-y: auto;
}
.kpa-menu[data-open="true"] { opacity: 1; visibility: visible; transform: none; }
/* Logo left, close right. With only the button in here, space-between put
   the X hard against the left edge, which reads as a mistake. */
.kpa-menu__top { display: flex; align-items: center; justify-content: space-between; min-height: 64px; }
.kpa-menu__logo img { height: 34px; width: auto; display: block; }
.kpa-menu__logo { font-weight: 700; letter-spacing: .12em; text-transform: uppercase; font-size: .9rem; }
.kpa-menu__close { width: 44px; height: 44px; display: grid; place-items: center; color: var(--on-brand); }
.kpa-menu__close:hover { color: var(--accent-on-brand); }
.kpa-menu__links { display: flex; flex-direction: column; justify-content: center; margin-top: clamp(.5rem, 2vw, 1.25rem); flex: 1; }
/* Sized so seven links, the phone block and the CTA all clear a 667px phone
   without scrolling. A menu you have to scroll is a menu that hides options. */
.kpa-menu__links a {
  font-family: var(--font-display); font-weight: 700; font-size: clamp(1.15rem, 4.6vw, 1.5rem);
  padding: .42em 0; border-bottom: 1px solid rgba(255, 255, 255, .12);
  display: flex; align-items: center; justify-content: space-between;
  transition: color .2s ease, padding-left .2s ease;
  /* staggered entrance — set per item below */
  opacity: 0; transform: translateY(8px);
}
.kpa-menu[data-open="true"] .kpa-menu__links a { opacity: 1; transform: none;
  transition: opacity .34s ease, transform .34s ease, color .2s ease, padding-left .2s ease; }
.kpa-menu__links a:nth-child(1){transition-delay:.04s}.kpa-menu__links a:nth-child(2){transition-delay:.08s}
.kpa-menu__links a:nth-child(3){transition-delay:.12s}.kpa-menu__links a:nth-child(4){transition-delay:.16s}
.kpa-menu__links a:nth-child(5){transition-delay:.2s}.kpa-menu__links a:nth-child(6){transition-delay:.24s}
.kpa-menu__links a:nth-child(7){transition-delay:.28s}.kpa-menu__links a:nth-child(8){transition-delay:.32s}
.kpa-menu__links a::after { content: "\2192"; color: #e0555f; opacity: .45; font-size: .62em; }
.kpa-menu__links a:hover { color: var(--accent-on-brand); padding-left: .3rem; }
.kpa-menu__links a:hover::after { opacity: 1; }
.kpa-menu { overflow: hidden; }
.kpa-menu__cta { display: grid; gap: 1rem; padding-top: 1.5rem; }
.kpa-menu__tel { text-align: center; font-size: .78rem; letter-spacing: .1em; text-transform: uppercase; color: var(--on-brand-dim); }
.kpa-menu__tel strong { display: block; font-size: 1.55rem; letter-spacing: 0; text-transform: none; color: var(--on-brand); margin-top: .2rem; }

/* ==========================================================================
   HERO
   ========================================================================== */
/* LIGHT-FIRST (18 Aug). The page ran header→ticker→hero→stat strip→offer band
   as four stacked dark blocks before a word of content. The reference build
   keeps the ground light and spends dark sparingly; that is most of why it
   reads as craft rather than weight. The photo stays — it sits under a white
   wash instead of a black one. */
.kpa-hero { position: relative; background: var(--canvas); color: var(--ink); overflow: hidden; }
.kpa-hero__bg { position: absolute; inset: 0; }
.kpa-hero__bg .kpa-ph { width: 100%; height: 100%; }
.kpa-hero__bg .kpa-ph::after { position: absolute; right: 1rem; bottom: 1rem; font-size: .6rem; max-width: 24ch; }
/* On mobile the hero fills the viewport and this label lands on top of the CTA
   proof line. The stripes already say "no image" — the label can go. */

.kpa-hero__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 70% 50%; }
.kpa-hero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(105deg, rgba(245,243,240,.975) 0%, rgba(245,243,240,.94) 44%, rgba(245,243,240,.55) 74%, rgba(245,243,240,.28) 100%);
}
/* Superseded below 1000px, where the backdrop is hidden entirely and the photo
   becomes a panel. Kept for any build that opts back into the backdrop hero. */
/* Matched to the reference build's hero: clamp(2.75rem, 4.5vw, 3.75rem).
   Ours ran to 5.5rem top and bottom, which is ~56px taller and pushed the
   form's submit button closer to the fold for no gain. */
.kpa-hero__in { position: relative; z-index: 2; padding-block: clamp(2.5rem, 4.5vw, 3.75rem); }
.kpa-hero__grid { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
/* One hero breakpoint at 1000px, matching the nav's. The two-column hero, the
   form card and the mobile photo panel all switch together — a hero that went
   single-column at 940 while the panel waited for 1000 left a 60px band showing
   the panel and the form side by side. */
@media (min-width: 1000px) { .kpa-hero__grid { grid-template-columns: 1.06fr .94fr; } }
.kpa-hero__shot { display: none; }

.kpa-hero__h1 { color: var(--ink); }
.kpa-hero__sub { margin-top: 1.25rem; max-width: 48ch; color: var(--ink-soft); font-size: clamp(1rem, .96rem + .24vw, 1.1rem); }
.kpa-hero__btn { display: none; margin-top: 1.6rem; }
.kpa-hero__proof { display: none; }
/* Quiet secondary link under the primary CTA — five stars and a text link, the
   pattern the reference build uses on mobile. Never a second button: one ask. */
.kpa-hero__revlink { display: none; align-items: center; justify-content: center; gap: .5em;
  margin-top: .95rem; font-size: .9rem; font-weight: 600; color: var(--ink-soft); }
.kpa-hero__revlink::after { content: "\2192"; color: var(--accent-dark); transition: transform .18s ease; }
.kpa-hero__revlink:hover::after { transform: translateX(3px); }
.kpa-hero__stars { display: inline-flex; gap: .08em; color: var(--accent); }
.kpa-hero__revlink:hover { color: var(--accent-dark); }

/* ==========================================================================
   MOBILE HERO — the photo is a panel, not a backdrop.
   Below 1000px the backdrop and its scrim are hidden, the hero goes to the
   page ground, and the photograph is promoted above the copy. Photo leads on
   a phone; copy follows it.
   ========================================================================== */
@media (max-width: 999px) {
  /* One message, one action. The form moves to /get-a-quote; the CTA and the
     header call button carry the mobile hero. */
  .kpa-formcard { display: none; }
  .kpa .kpa-hero__btn { display: inline-flex; width: 100%; }
  .kpa-hero__proof {
    display: flex; align-items: center; justify-content: center; gap: .5em;
    margin-top: .9rem; font-size: .82rem; color: var(--ink-soft); text-align: center;
  }
  .kpa-hero__grid > div { text-align: center; }
  .kpa-hero__sub { margin-inline: auto; }
  .kpa .kpa-chips { justify-content: center; margin-top: 1.4rem; }

  .kpa-hero__bg, .kpa-hero__scrim { display: none; }
  /* Six elements were asking for attention in one viewport: eyebrow, headline,
     subhead, CTA, proof line, two chips. The eyebrow duplicates the ticker and
     the subhead; the proof line duplicates the ticker and the chips. Cutting
     both leaves headline -> subhead -> CTA -> chips, which has a clear order. */
  .kpa-hero .kpa-eyebrow { display: none; }
  .kpa-hero__proof { display: none !important; }
  /* One ask. The chips were a third thing competing under the button; the
     reference build runs a single CTA with one quiet secondary link. */
  .kpa-hero .kpa-chips { display: none; }
  .kpa .kpa-hero__revlink { display: flex; }
  /* Page ground, not brand — the photo's bottom edge has to fade into
     something, and it fades into this. */
  .kpa-hero { background: var(--canvas); color: var(--ink); }
  .kpa-hero__in { padding-block: 1.5rem 2.25rem; }
  .kpa-hero__h1 { color: var(--ink); }
  .kpa-hero__sub { color: var(--ink-soft); }
  .kpa-hero__eyebrow, .kpa-hero .kpa-eyebrow--on-brand { color: var(--accent-dark); }
  /* Chips were styled for a dark ground. */
  .kpa-hero .kpa-chip { border-color: var(--line); background: var(--surface); color: var(--ink-soft); }

  /* order:-1 needs a flex stack, and the copy column is a plain block.
     Order on the phone: photo, eyebrow, headline, subhead, CTA, proof, chips.
     The chips move BELOW the CTA deliberately — left in reading order they
     pushed the button to 724px on a 375x667 phone, i.e. under the fold, which
     fails a build gate. Below it, the CTA lands around 640px and the chips are
     still the first thing under the thumb. */
  .kpa-hero__grid > div:first-child { display: flex; flex-direction: column; }
  .kpa .kpa-hero__btn   { order: 1; }
  .kpa .kpa-hero__proof { order: 2; }
  .kpa .kpa-chips       { order: 3; margin-top: 1.5rem; }
  .kpa .kpa-hero__shot {
    display: block; order: -1; width: 100%; margin: 0 0 1.1rem;
    /* Explicit height. The image inside is position:absolute, so this box had
       no height of its own and was being propped up by the .kpa-ph placeholder.
       The moment a real photograph replaced the placeholder the container
       collapsed and the photo overlapped the headline — a bug that could only
       appear AFTER a client supplied imagery. */
    height: 170px;
    position: relative; border-radius: 0; box-shadow: none; overflow: visible;
  }
  .kpa-hero__shot .kpa-ph { height: 170px; border-radius: 20px; }
  .kpa-hero__shot .kpa-ph::after { font-size: .55rem; }
  /* Rounded AND faded: the top corners stay crisp while the bottom edge
     dissolves into the page. It must be a BOTTOM-ONLY LINEAR mask — a radial
     one softens the corners away and loses the effect entirely.
     170px is chosen so the primary CTA still clears the fold on a 667px
     phone; raising it pushes the CTA under. Measure before changing it. */
  .kpa .kpa-hero__shot img {
    position: absolute; inset: 0;
    width: 100%; height: 170px; object-fit: cover; display: block;
    border-radius: 20px;
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 42%, rgba(0,0,0,.55) 72%, rgba(0,0,0,.18) 90%, transparent 100%);
    mask-image: linear-gradient(to bottom, #000 0%, #000 42%, rgba(0,0,0,.55) 72%, rgba(0,0,0,.18) 90%, transparent 100%);
  }
}

/* Form card -------------------------------------------------------------- */
.kpa-formcard {
  background: var(--surface); border-top: 3px solid var(--accent);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
  padding: clamp(1.15rem, 2.9vw, 1.75rem); color: var(--ink);
}
.kpa-formcard__h { font-family: var(--font-display); font-weight: 700; font-size: 1.32rem; line-height: 1.2; }
.kpa-formcard__p { font-size: .88rem; color: var(--ink-soft); margin-top: .4rem; }
.kpa-formcard__foot {
  margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--line);
  font-size: .79rem; color: var(--ink-soft); display: flex; align-items: center; gap: .5em;
}
.kpa-formcard__foot svg { color: var(--accent-dark); flex-shrink: 0; }

/* ==========================================================================
   PAGE HERO (inner pages)
   ========================================================================== */
.kpa-phero { position: relative; background: var(--brand); color: var(--on-brand); overflow: hidden; }
.kpa-phero__bg { position: absolute; inset: 0; }
/* No hero photograph on this page. Flat brand under the scrim reads as an
   accident, so give it a little depth and a hairline of accent at the base —
   the same signature the header carries — so it looks chosen. */
.kpa-phero__bg--plain {
  background:
    radial-gradient(120% 130% at 78% -10%, rgba(var(--accent-rgb), .30), transparent 58%),
    linear-gradient(160deg, var(--brand-soft), var(--brand-deep) 62%);
}
/* No photograph means no scrim. The scrim exists to hold white text over a
   picture; with nothing underneath it just double-darkens the gradient below
   until the whole band reads as flat black — which is exactly how About, Our
   Work, FAQs and Get in Touch were rendering. */
.kpa-phero:has(.kpa-phero__bg--plain) .kpa-phero__scrim { display: none; }
.kpa-phero__bg--plain::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: var(--accent);
}
.kpa-phero__bg .kpa-ph { width: 100%; height: 100%; }
.kpa-phero__bg img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* ONE gradient, not two. Two stacked scrims composite - 1-(1-a)(1-b) - so a
   .55 layer over a .58 layer is 81% opaque at its lightest point, and the
   photograph underneath 37 service and area heroes never showed at all.

   Angled so the weight sits bottom-left under the H1 and the buttons, which is
   where white text needs the contrast, and lifts towards the top-right where
   there is nothing but photo. */
.kpa-phero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(100deg,
    rgba(var(--scrim-rgb),.93) 0%,
    rgba(var(--scrim-rgb),.86) 34%,
    rgba(var(--scrim-rgb),.60) 66%,
    rgba(var(--scrim-rgb),.42) 100%);
}
.kpa-phero__in { position: relative; z-index: 2; padding-block: clamp(2.5rem, 7vw, 4.5rem); }
.kpa-phero__crumb { font-size: .78rem; color: rgba(247,246,244,.6); margin-bottom: .9rem; }
.kpa-phero__crumb a:hover { color: var(--accent-on-brand); }
.kpa-phero h1 { color: #fff; max-width: 22ch; }
.kpa-phero__sub { margin-top: 1rem; max-width: 58ch; color: var(--on-brand-dim); font-size: clamp(1rem, .96rem + .26vw, 1.1rem); }
.kpa-phero__btns { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 1.75rem; }
.kpa-phero__proof { margin-top: 1rem; font-size: .82rem; color: var(--on-brand-dim); }
@media (max-width: 767px) {
  .kpa-phero__crumb { display: none; }   /* BreadcrumbList schema still ships */
  .kpa-phero__in { text-align: center; }
  .kpa-phero h1, .kpa-phero__sub { margin-inline: auto; }
  .kpa-phero__btns { justify-content: center; }
}

/* ==========================================================================
   TRUST STRIP
   ========================================================================== */
/* Light stat band. The coral --accent-on-brand only ever existed because this
   band was dark; on a light ground the numbers take --accent-dark and the
   coral leaves the design entirely. */
.kpa-strip { background: var(--canvas-tint); color: var(--ink-soft); border-block: 1px solid rgba(var(--accent-rgb), .18); }
.kpa-strip ul { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.6rem 1rem; padding-block: 2.2rem; text-align: center; }
@media (min-width: 780px) { .kpa-strip ul { grid-template-columns: repeat(4, 1fr); } }
.kpa-strip strong { display: block; font-family: var(--font-display); font-size: clamp(1.9rem, 1.4rem + 2vw, 2.5rem); font-weight: 700; color: var(--accent-dark); line-height: 1; font-variant-numeric: tabular-nums; }
/* Direct child only. The count-up span lives INSIDE <strong>, so a bare
   `.kpa-strip span` label rule also caught the number and shrank it to
   label size on its own line. Same family as the specificity trap in
   CLAUDE.md: a loose descendant selector reaching further than intended. */
.kpa-strip li > span { display: block; font-size: .8rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; margin-top: .55rem; color: var(--ink-soft); }
@media (min-width: 780px) { .kpa-strip li { position: relative; } .kpa-strip li + li::before { content: ""; position: absolute; left: 0; top: 12%; bottom: 12%; width: 1px; background: var(--line); } }

/* ==========================================================================
   PROCESS
   ========================================================================== */
.kpa-proc { background: var(--canvas-warm); color: var(--ink-soft); }
.kpa-proc .kpa-h2 { color: var(--brand); }
.kpa-proc .kpa-lead { color: var(--ink-soft); }
.kpa-steps { display: grid; gap: clamp(1.75rem, 3.5vw, 2.25rem); counter-reset: s; }
@media (min-width: 760px) { .kpa-steps { grid-template-columns: repeat(4, 1fr); } }

.kpa-step { position: relative; padding-top: 5.2rem; text-align: center; }
/* A light disc with the number badged on its corner, matching the reference.
   The numbering is truthful here — this really is a sequence the customer
   moves through — so it earns its place rather than decorating. */
.kpa-step__num {
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  display: grid; place-items: center;
  width: 74px; height: 74px; border-radius: 50%;
  background: var(--surface); color: var(--brand);
  border: 1px solid var(--line);
  box-shadow: 0 4px 14px rgba(var(--scrim-rgb), .08);
}
.kpa-step__num::after {
  content: attr(data-n);
  position: absolute; top: -2px; right: -2px;
  width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent); color: var(--on-accent);
  font-family: var(--font-display); font-weight: 700; font-size: .82rem; line-height: 1;
}
.kpa-step__num svg { width: 30px; height: 30px; }
/* Dashed connector between the discs, desktop only. Drawn from the disc's
   centre line so it reads as one run rather than four detached cards. Not
   drawn after the last step, and not once the steps have stacked. */
@media (min-width: 760px) {
  .kpa-step::after {
    content: ""; position: absolute; top: 37px; left: calc(50% + 46px); right: calc(-50% + 46px); height: 0;
    border-top: 2px dashed var(--line);
  }
  .kpa-step:last-child::after { display: none; }
}
.kpa-step h3 { font-family: var(--font-display); font-weight: 700; font-size: 1.18rem; color: var(--brand); line-height: 1.25; }
.kpa-step p { margin-top: .65rem; font-size: .93rem; color: var(--ink-soft); }

.kpa-proc__foot {
  margin-top: clamp(2.25rem, 5vw, 3.25rem); padding-top: clamp(1.75rem, 4vw, 2.5rem);
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.5rem;
}
.kpa-proc__proof { font-size: .86rem; color: var(--ink-soft); }
@media (max-width: 759px) {
  .kpa-proc__foot { flex-direction: column; align-items: stretch; text-align: center; }
  .kpa .kpa-proc__foot .kpa-btn { width: 100%; }
}

/* ==========================================================================
   SERVICES
   ========================================================================== */
/* SYMMETRY — never leave a lone card on its own row.
   Flex, not grid, because the row has to centre its last line. Two rules make
   it survive the client changing the list length:

   1. flex-basis must be a FRACTION, calc(50% - gap), never a pixel value. A
      pixel basis small enough to grow into half a row is also small enough
      for three to fit on a wide screen — that is exactly how a rule written
      for five services produced 3 + 1 when the list dropped to four.
   2. The column count comes off the DOM via :has(), not from an assumption
      about how many services this client has. */
.kpa-svcs { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(1rem, 2.2vw, 1.4rem); }
.kpa-svcs > * { flex: 1 1 100%; }
@media (min-width: 620px) {
  .kpa-svcs > * { flex-basis: calc(50% - .7rem); max-width: calc(50% - .7rem); }
}
@media (min-width: 1040px) {
  /* Three across by default; at four items, two rows of two beats 3 + 1. */
  .kpa-svcs > * { flex-basis: calc(33.333% - .94rem); max-width: calc(33.333% - .94rem); }
  .kpa-svcs:has(> :nth-child(4)):not(:has(> :nth-child(5))) > * { flex-basis: calc(50% - .7rem); max-width: calc(50% - .7rem); }
}

.kpa-svc {
  display: flex; flex-direction: column; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  box-shadow: var(--shadow); transition: transform .25s ease, box-shadow .25s ease;
}
.kpa-svc:hover { transform: translateY(-6px); box-shadow: var(--shadow-lg); border-color: rgba(var(--accent-rgb), .45); }
/* The photograph is the biggest object on the card, so it is what should react.
   A slow scale under `overflow: hidden` reads as the card coming alive rather
   than as an effect; transform and opacity only, so it composites on the GPU
   and never repaints on scroll. */
.kpa-svc__img { aspect-ratio: 16 / 10; position: relative; overflow: hidden; }
.kpa-svc__img img { transition: transform .55s cubic-bezier(.22,.61,.36,1); }
.kpa-svc:hover .kpa-svc__img img { transform: scale(1.06); }
/* A wash of brand colour over the photo, and the heading picks up the accent. */
.kpa-svc__img::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, rgba(var(--accent-rgb), .22), transparent 55%);
  opacity: 0; transition: opacity .35s ease;
}
.kpa-svc:hover .kpa-svc__img::after { opacity: 1; }
.kpa-svc__body h3 { transition: color .22s ease; }
.kpa-svc:hover .kpa-svc__body h3 { color: var(--accent-dark); }
.kpa-svc__img .kpa-ph { width: 100%; height: 100%; }
.kpa-svc__img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.kpa-svc__body { padding: 1.5rem; display: flex; flex-direction: column; flex: 1; }
.kpa-svc__body h3 { font-family: var(--font-display); font-weight: 700; font-size: 1.28rem; color: var(--brand); }
.kpa-svc__body p { margin-top: .6rem; font-size: .93rem; color: var(--ink-soft); flex: 1; }
.kpa-svc__more { margin-top: 1.1rem; font-size: .87rem; font-weight: 700; color: var(--accent-dark); display: inline-flex; align-items: center; gap: .45em; transition: gap .2s ease; }
.kpa-svc:hover .kpa-svc__more { gap: .8em; }

.kpa .kpa-svc--flag { flex-basis: 100%; max-width: 100%; }
@media (min-width: 1040px) {
  .kpa-svc--flag { flex-direction: row; align-items: stretch; }
  .kpa-svc--flag .kpa-svc__img { flex: 1 1 52%; aspect-ratio: auto; }
  .kpa-svc--flag .kpa-svc__body { flex: 1 1 48%; justify-content: center; padding: clamp(2rem, 4vw, 3rem); }
  .kpa-svc--flag .kpa-svc__body h3 { font-size: clamp(1.6rem, 1.3rem + 1vw, 2.1rem); }
  .kpa-svc--flag .kpa-svc__body p { flex: 0; font-size: 1rem; }
}

/* ==========================================================================
   SPLIT (text + media)
   ========================================================================== */
.kpa-split { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
@media (min-width: 860px) { .kpa-split { grid-template-columns: 1.1fr .9fr; } }
.kpa-split__media { position: relative; aspect-ratio: 4 / 5; border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-lg); }
.kpa-split__media .kpa-ph { width: 100%; height: 100%; }
.kpa-split__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.kpa-split__body p { margin-top: 1.1rem; color: var(--ink-soft); }
.kpa-split__lead { font-size: clamp(1.08rem, 1rem + .45vw, 1.24rem); color: var(--ink); line-height: 1.5; }
.kpa-split__body .kpa-btn { margin-top: 1.75rem; }
.kpa-sign { margin-top: 1.6rem; padding-top: 1.2rem; border-top: 1px solid var(--line); font-family: var(--font-display); font-weight: 700; font-size: 1.1rem; color: var(--brand); }
.kpa-sign span { display: block; font-weight: 500; font-size: .78rem; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); margin-top: .25rem; }

/* Doubled prefix (0,2,0) — same reason as .kpa-accreds. */
.kpa .kpa-ticks { display: grid; gap: .8rem; margin-top: 1.5rem; }
.kpa-ticks li { display: flex; gap: .7em; align-items: flex-start; font-size: .96rem; color: var(--ink-soft); }
.kpa-ticks svg { flex-shrink: 0; margin-top: .18em; color: var(--accent-dark); }

.kpa-inline-proof { margin-top: .85rem; font-size: .82rem; color: var(--ink-soft); }

/* ==========================================================================
   WHY US
   ========================================================================== */
.kpa-why { display: grid; gap: clamp(1.6rem, 4vw, 2.75rem); }
@media (min-width: 700px) { .kpa-why { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .kpa-why { grid-template-columns: repeat(3, 1fr); } }
.kpa-why li h3 { font-family: var(--font-display); font-weight: 700; font-size: 1.15rem; color: var(--brand); display: flex; align-items: center; gap: .6em; }
.kpa-why li h3 svg {
  color: var(--accent-dark); flex-shrink: 0;
  /* Tinted chip behind the tick, as the reference does — turns a bare list into
     a set of cards without adding a card. */
  box-sizing: content-box; width: 19px; height: 19px; padding: .5rem;
  background: var(--accent-tint); border-radius: var(--radius-sm);
}
/* One tint. A rotation only reads as a rhythm across six or more; across three
   it reads as the first one being special and the rest being a mistake. */
.kpa-why li p { margin-top: .6rem; font-size: .94rem; color: var(--ink-soft); }

/* ==========================================================================
   WORK GRID — one feature, then even rows.
   The first entry is the feature: full width at 21:9, the thing that stops
   the scroll. Everything after it sits at half width, dropping to thirds once
   there are seven or more so the tail never becomes a long single column.
   Column counts come off the DOM with :has(), so the grid keeps working when
   the client's list of jobs changes length.
   ========================================================================== */
.kpa-work { display: grid; gap: clamp(1.1rem, 2.2vw, 1.6rem); }
.kpa-work__item { margin: 0; transition: transform .3s ease; }
.kpa-work__item:hover { transform: translateY(-3px); }
.kpa-work__shot { position: relative; aspect-ratio: 4 / 3; overflow: hidden; border-radius: var(--radius); box-shadow: var(--shadow); }
.kpa-work__shot .kpa-ph { width: 100%; height: 100%; }
.kpa-work__shot img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.kpa-work figcaption { margin-top: .85rem; display: flex; flex-wrap: wrap; align-items: baseline; gap: .35rem .9rem; }
.kpa-work__town { font-family: var(--font-display); font-weight: 700; font-size: 1.08rem; color: var(--brand); }
.kpa-work__meta { font-size: .85rem; color: var(--ink-soft); }
/* When a job has no town confirmed, the title takes the lead slot rather than
   leaving an empty bold element and a stray separator. */
.kpa-work__lead { font-family: var(--font-display); font-weight: 700; font-size: 1.02rem; color: var(--brand); }

/* Two per row, every tile the same size. The old feature slot made the first
   job full-width at 21:9 and the rest half — which reads as a hierarchy the
   jobs do not actually have, and made a missing photo the largest object on
   the page. Equal tiles compare better, which is the point of before/after. */
@media (min-width: 760px) {
  .kpa-work { grid-template-columns: repeat(2, 1fr); align-items: start; }
  .kpa-work > .kpa-work__item { grid-column: span 1; }
}

/* Filters — hidden until JS reveals them (see site.js). */
.kpa-work__filters { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: clamp(1.25rem, 3vw, 2rem); }
.kpa-work__filters[hidden] { display: none; }
.kpa .kpa-work__filter {
  padding: .45em 1em; font-size: .86rem; font-weight: 600;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--ink-soft); transition: all .16s ease;
}
.kpa .kpa-work__filter:hover { border-color: var(--accent); color: var(--accent-dark); }
.kpa .kpa-work__filter.is-on { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
/* Mobile shows three tiles, desktop the full 2x2 — a fourth on a phone is a
   long scroll before the reader reaches the button. */
@media (max-width: 759px) { .kpa-work > .kpa-work__item:nth-child(4) { display: none; } }
/* Contact cards: one column, icon beside the copy. A long email address has to
   be allowed to break or it overflows the card on a phone. */
.kpa .kpa-contacts { display: grid; gap: 14px; grid-template-columns: 1fr; }

.kpa-hours__area { font-size: .86rem; }
.kpa-highlight__terms { margin-top: .7rem; font-size: .78rem; line-height: 1.55; color: var(--on-brand-dim); max-width: 62ch; }
.kpa-svcs__more { display: flex; justify-content: center; margin-top: clamp(1.75rem, 4vw, 2.5rem); }

/* ==========================================================================
   MICRO-INTERACTIONS
   The reference build lifts a card, fills its icon and pulses one CTA. Small
   moves, but they are most of what makes a page feel built rather than laid
   out. Everything here is decoration only and is switched off for anyone who
   has asked for reduced motion.
   ========================================================================== */
.kpa-card { transition: transform .22s ease, border-color .22s ease, box-shadow .22s ease; }
.kpa-card:hover { transform: translateY(-5px); border-color: rgba(var(--accent-rgb), .5); box-shadow: var(--shadow-lg); }
.kpa-card__ico { transition: background-color .22s ease, color .22s ease, transform .22s ease; }
.kpa-card:hover .kpa-card__ico { background: var(--accent); color: var(--on-accent); transform: scale(1.08); }

.kpa-opt { transition: transform .22s ease, box-shadow .22s ease; }
.kpa-opt:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }

/* The local-angle blocks read as a list; the accent rule thickening on hover is
   enough to show they are links without turning them into cards. */
.kpa-angle { transition: border-color .2s ease; }
.kpa-angle:hover { border-left-color: var(--accent-dark); }

/* One pulsing CTA, on the mid-page interrupt — the reader who is already sold.
   Not the hero: a pulsing button above the fold reads as a pop-up. */
.kpa-band-cta .kpa-btn--accent { position: relative; }
/* The ring is static and only its OPACITY animates. Animating box-shadow
   spread repaints on the main thread every frame — Lighthouse flags it as a
   non-composited animation, and it runs forever on a page the reader is
   scrolling. Opacity is compositor-only, so this costs nothing. */
.kpa-band-cta .kpa-btn--accent::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  box-shadow: 0 0 0 11px rgba(255, 255, 255, .38);
  opacity: 0; will-change: opacity;
  animation: kpa-pulse 2.8s ease-in-out infinite;
}
@keyframes kpa-pulse {
  0%, 100% { opacity: 0; }
  45%      { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .kpa-card, .kpa-card__ico, .kpa-opt, .kpa-angle { transition: none; }
  .kpa-svc, .kpa-svc__img img, .kpa-svc__img::after, .kpa-drop__ico { transition: none; }
  .kpa-svc:hover { transform: none; }
  .kpa-svc:hover .kpa-svc__img img { transform: none; }
  .kpa-drop__grid a:hover .kpa-drop__ico { transform: none; }
  .kpa-card:hover, .kpa-opt:hover { transform: none; }
  .kpa-card:hover .kpa-card__ico { transform: none; }
  .kpa-band-cta .kpa-btn--accent::after { animation: none; }
  /* The knob's attract-pulse too — it was left out when the knob moved to a
     pseudo-element, and an infinite animation is exactly what this query is for. */
  .kpa-slider:not(.is-touched) .kpa-slider__knob::after { animation: none; }
}
.kpa .kpa-work__empty { margin-top: 1.5rem; text-align: center; color: var(--ink-soft); }
.kpa-work__more { margin-top: clamp(2rem, 4vw, 2.75rem); display: flex; justify-content: center; }
.kpa-work__empty a { color: var(--accent-dark); text-decoration: underline; }

/* ==========================================================================
   BEFORE / AFTER
   ========================================================================== */
.kpa-ba__grid { display: grid; gap: clamp(2rem, 4vw, 3rem); align-items: start; }
@media (min-width: 900px) { .kpa-ba__grid { grid-template-columns: repeat(2, 1fr); } }

.kpa-slider {
  position: relative; aspect-ratio: 4 / 3; border-radius: var(--radius); overflow: hidden;
  box-shadow: var(--shadow); user-select: none; touch-action: pan-y; cursor: ew-resize;
}
.kpa-slider__layer { position: absolute; inset: 0; }
.kpa-slider__layer .kpa-ph { width: 100%; height: 100%; }
.kpa-slider__layer img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.kpa-slider__layer--after { clip-path: inset(0 0 0 var(--pos, 50%)); }
.kpa-slider__handle {
  position: absolute; top: 0; bottom: 0; left: var(--pos, 50%);
  width: 2px; background: #fff; transform: translateX(-1px); z-index: 3; box-shadow: 0 0 12px rgba(var(--scrim-rgb), .4);
}
.kpa-slider__knob {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 50px; height: 50px; border-radius: 50%; background: #fff; color: var(--brand);
  display: grid; place-items: center; box-shadow: 0 3px 14px rgba(var(--scrim-rgb), .35); border: 2px solid var(--accent);
}
/* Same fix as the CTA pulse: the ring lives on a pseudo-element with a fixed
   spread and only opacity animates, so the knob's own depth shadow stays put
   and nothing repaints on the main thread. This one matters more — it sits on
   a control the reader is about to drag. --accent-rgb exists because rgba()
   cannot take a hex custom property; it was the previous client's orange once. */
.kpa-slider__knob::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  box-shadow: 0 0 0 10px rgba(var(--accent-rgb), .45);
  opacity: 0;
}
.kpa-slider:not(.is-touched) .kpa-slider__knob::after {
  will-change: opacity;
  animation: kpa-knob 2.4s ease-in-out infinite;
}
@keyframes kpa-knob {
  0%, 100% { opacity: 0; }
  50%      { opacity: 1; }
}
.kpa-slider__tag {
  position: absolute; bottom: .8rem; z-index: 2;
  font-size: .66rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  padding: .4em .9em; border-radius: var(--radius-pill); background: rgba(var(--scrim-rgb), .8); color: #fff;
}
.kpa-slider__tag--b { left: .8rem; }
.kpa-slider__tag--a { right: .8rem; background: var(--accent); color: var(--on-accent); }
.kpa-slider__hint {
  position: absolute; top: .8rem; left: 50%; transform: translateX(-50%); z-index: 4;
  display: inline-flex; align-items: center; gap: .4em;
  font-size: .66rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  padding: .42em 1em; border-radius: var(--radius-pill); background: rgba(255,255,255,.94); color: var(--brand);
  box-shadow: 0 2px 10px rgba(var(--scrim-rgb), .25); transition: opacity .3s ease; pointer-events: none;
}
.kpa-slider.is-touched .kpa-slider__hint { opacity: 0; }
.kpa-slider__hint svg { animation: kpa-nudge 1.6s ease-in-out infinite; }
@keyframes kpa-nudge { 0%, 100% { transform: translateX(-2px); } 50% { transform: translateX(2px); } }

.kpa-ba__cap { margin-top: 1rem; display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem 1rem; }
.kpa-ba__town { font-family: var(--font-display); font-weight: 700; font-size: 1.15rem; color: var(--brand); }
.kpa-ba__detail { font-size: .87rem; color: var(--ink-soft); }

/* ==========================================================================
   TESTIMONIALS
   ========================================================================== */
.kpa-revs { background: var(--surface); }
.kpa-revgrid { display: grid; gap: clamp(1rem, 2.2vw, 1.4rem); }
@media (min-width: 700px) { .kpa-revgrid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .kpa-revgrid { grid-template-columns: repeat(3, 1fr); } }

.kpa-rev {
  background: var(--canvas); border: 1px solid var(--line); border-radius: var(--radius);
  padding: clamp(1.3rem, 2.5vw, 1.7rem); display: flex; flex-direction: column;
}
.kpa-rev blockquote { font-size: .98rem; color: var(--ink); line-height: 1.6; flex: 1; }
.kpa-rev figcaption { margin-top: 1.1rem; padding-top: 1rem; border-top: 1px solid var(--line); font-size: .86rem; }
.kpa-rev figcaption strong { display: block; color: var(--brand); font-weight: 700; }
.kpa-rev figcaption span { color: var(--ink-soft); }

.kpa .kpa-rev__video { position: relative; display: block; width: 100%; aspect-ratio: 16 / 9; border-radius: var(--radius); overflow: hidden; margin-bottom: 1.1rem; }
.kpa-rev__video .kpa-ph { width: 100%; height: 100%; }
.kpa-rev__video img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.kpa-rev__play { position: absolute; inset: 0; display: grid; place-items: center; z-index: 2; background: linear-gradient(to top, rgba(var(--scrim-rgb), .5), rgba(var(--scrim-rgb), 0) 55%); }
.kpa-rev__play i { display: grid; place-items: center; width: 56px; height: 56px; border-radius: 50%; background: rgba(255,255,255,.95); color: var(--brand); box-shadow: 0 4px 18px rgba(var(--scrim-rgb), .3); transition: transform .25s ease, background-color .25s ease; }
.kpa-rev__video:hover .kpa-rev__play i { transform: scale(1.08); background: var(--accent); color: var(--on-accent); }

/* Lightbox */
.kpa-lb {
  position: fixed; inset: 0; z-index: 220; background: rgba(var(--scrim-rgb),.94);
  display: grid; place-items: center; padding: var(--gut);
  opacity: 0; visibility: hidden; transition: opacity .25s ease, visibility .25s ease;
}
.kpa-lb[data-open="true"] { opacity: 1; visibility: visible; }
.kpa-lb__box { width: min(1000px, 100%); }
.kpa-lb__frame { aspect-ratio: 16 / 9; max-height: 78vh; margin-inline: auto; background: #000; border-radius: var(--radius); overflow: hidden; }
.kpa-lb__frame .kpa-ph { width: 100%; height: 100%; }
.kpa-lb__close { display: block; margin: 1rem auto 0; color: #fff; font-size: .82rem; letter-spacing: .1em; text-transform: uppercase; }
.kpa-lb__close:hover { color: var(--accent-on-brand); }

/* ==========================================================================
   GUARANTEE
   ========================================================================== */
.kpa-guar { background: var(--canvas-tint); }
.kpa-guar__box {
  background: var(--brand); color: var(--on-brand-dim); border-radius: var(--radius);
  padding: clamp(2rem, 5vw, 3.5rem); text-align: center; box-shadow: var(--shadow-lg);
}
.kpa-guar__box .kpa-h2 { color: #fff; max-width: 22ch; margin-inline: auto; }
.kpa-guar__box p { margin: 1.1rem auto 0; max-width: 58ch; }
/* The box is a DARK surface sitting on a light section. Anything dropped inside
   it inherits light-ground tokens and disappears — the tick list came through at
   about 1.9:1. Same root cause as --accent-on-brand: light tokens on dark. */
.kpa-guar__box .kpa-ticks { justify-content: center; margin-inline: auto; max-width: 34rem; text-align: left; }
.kpa-guar__box .kpa-ticks li { color: var(--on-brand-dim); }
.kpa-guar__box .kpa-ticks svg { color: var(--accent-on-brand); }

/* ==========================================================================
   MOBILE — everything centres. On a phone the eye has one column to follow,
   and ragged-left blocks under a centred heading read as broken alignment.
   ========================================================================== */
@media (max-width: 767px) {
  .kpa-sect-head,
  .kpa-split__body,
  .kpa-angle,
  .kpa-card,
  .kpa-guar__box,
  .kpa-why li,
  .kpa-step,
  .kpa-formcard__foot,
  .kpa-inline-proof { text-align: center; }

  /* Flex rows need their main axis centred too, not just the text. */
  .kpa .kpa-why li h3,
  .kpa .kpa-ticks li,
  .kpa .kpa-chips,
  .kpa .kpa-svcaside__links,
  .kpa .kpa-formcard__foot,
  .kpa-work__filters { justify-content: center; }
  .kpa .kpa-ticks li { text-align: left; }
  .kpa-guar__box .kpa-ticks { max-width: 22rem; }

  /* Left borders read as a hanging indent once the text is centred. */
  .kpa-angle { border-left: 0; border-top: 3px solid var(--accent); padding-left: 0; padding-top: 1rem; }

  .kpa-sect-head, .kpa-split__body { margin-inline: auto; }
  .kpa-split__body .kpa-lead, .kpa-split__lead { margin-inline: auto; }
  .kpa-proc__foot { justify-content: center; text-align: center; }
}
/* Doubled prefix (0,2,0): margin AND padding here both lose to the resets
   `.kpa ul {margin:0}` and `.kpa ul {padding:0}`, which are (0,1,1). Without
   it the logos render on top of the divider. */
.kpa .kpa-accreds { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 1.5rem 2.5rem; margin-top: 2.25rem; padding-top: 2rem; border-top: 1px solid rgba(255,255,255,.14); }
.kpa-accreds li { position: relative; width: 120px; height: 44px; flex: 0 0 auto; }
.kpa-accreds img { height: 44px; width: auto; object-fit: contain; position: relative; margin-inline: auto; }

/* ==========================================================================
   AREAS
   ========================================================================== */
.kpa-areas { background: var(--surface); }
.kpa-region { border: 1px solid var(--line); border-top: 3px solid var(--accent); border-radius: var(--radius); padding: clamp(1.5rem, 3vw, 2.25rem); background: var(--canvas); }
.kpa-region__towns { display: flex; flex-wrap: wrap; gap: .5rem; }
.kpa-region__towns a, .kpa-ft__pills a {
  font-size: .86rem; padding: .42em .9em; border-radius: var(--radius-pill);
  background: var(--surface); border: 1px solid var(--line); color: var(--ink-soft); transition: all .16s ease;
}
.kpa-region__towns a:hover { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
.kpa-region__tel { margin-top: 1.5rem; padding-top: 1.3rem; border-top: 1px solid var(--line); }
.kpa-region__tel small { display: block; font-size: .72rem; letter-spacing: .09em; text-transform: uppercase; color: var(--ink-soft); }
.kpa-region__tel a { font-family: var(--font-display); font-weight: 700; font-size: 1.55rem; color: var(--brand); }
.kpa-region__tel a:hover { color: var(--accent-dark); }

.kpa-areagrid { display: grid; gap: 1rem; }
@media (min-width: 620px) { .kpa-areagrid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 940px) { .kpa-areagrid { grid-template-columns: repeat(3, 1fr); } }
.kpa-areacard {
  display: block; background: var(--surface); border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: var(--radius); padding: 1.4rem 1.5rem; transition: transform .2s ease, box-shadow .2s ease;
}
.kpa-areacard:hover { transform: translateY(-3px); box-shadow: var(--shadow); }
.kpa-areacard h3 { font-family: var(--font-display); font-weight: 700; font-size: 1.2rem; color: var(--brand); }
.kpa-areacard__pc { font-size: .76rem; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); margin-top: .3rem; }
.kpa-areacard .kpa-svc__more { margin-top: 1rem; }

/* ==========================================================================
   FAQ
   ========================================================================== */
.kpa-faqwrap { display: grid; gap: clamp(2rem, 5vw, 4rem); }
@media (min-width: 920px) {
  .kpa-faqwrap { grid-template-columns: minmax(240px, .8fr) 1.2fr; align-items: start; }
  .kpa-faqwrap__aside { position: sticky; top: 100px; }
}
.kpa-faqwrap__lead { font-size: clamp(1.1rem, 1rem + .6vw, 1.35rem); line-height: 1.45; color: var(--brand); margin-top: 1rem; }
.kpa-faqcta { margin-top: 1.6rem; padding-top: 1.6rem; border-top: 1px solid var(--line); display: flex; flex-direction: column; align-items: flex-start; gap: .85rem; }
.kpa-faqcta small { font-size: .76rem; letter-spacing: .1em; text-transform: uppercase; color: var(--accent-dark); font-weight: 700; }

.kpa-faq { display: grid; gap: .7rem; }
.kpa-faq__item {
  position: relative; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
  transition: border-color .2s ease, box-shadow .25s ease, background-color .2s ease;
}
.kpa-faq__item::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; z-index: 0;
  background: var(--accent); transform: scaleX(0); transform-origin: left; transition: transform .28s ease;
}
.kpa-faq__q, .kpa-faq__a { position: relative; z-index: 1; }
.kpa-faq__item:hover { border-color: rgba(var(--accent-rgb), .45); }
.kpa-faq__item[data-open="true"] { border-color: rgba(var(--accent-rgb), .5); background: var(--accent-tint); box-shadow: 0 8px 26px rgba(var(--scrim-rgb), .08); }
.kpa-faq__item[data-open="true"]::before { transform: scaleX(1); }

/* The question is wrapped in an h3 for the document outline. Doubled prefix
   (0,2,0) so this beats the element-qualified heading reset (0,1,1) and the
   wrapper contributes nothing visually — the button still owns the styling. */
.kpa .kpa-faq__qh { margin: 0; font: inherit; font-weight: inherit; letter-spacing: inherit; }

/* Doubled prefix (0,2,0) so the padding beats the reset `.kpa button` (0,1,1).
   Without it the padding is silently dropped and the accordion collapses. */
.kpa .kpa-faq__q {
  width: 100%; text-align: left; display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
  padding: 1.35rem clamp(1.25rem, 2.5vw, 1.85rem);
  font-family: var(--font-display); font-weight: 700; font-size: clamp(1rem, .96rem + .32vw, 1.15rem); line-height: 1.35;
  color: var(--brand); cursor: pointer; transition: color .18s ease;
}
.kpa-faq__q:hover { color: var(--accent-dark); }

.kpa-faq__icon { flex-shrink: 0; width: 30px; height: 30px; border-radius: 50%; background: var(--accent-tint); position: relative; transition: background-color .25s ease, transform .3s ease; }
.kpa-faq__item[data-open="true"] .kpa-faq__icon { background: var(--accent); transform: rotate(180deg); }
.kpa-faq__icon::before, .kpa-faq__icon::after {
  content: ""; position: absolute; top: 50%; left: 50%; background: var(--accent-dark);
  transform: translate(-50%, -50%); transition: transform .28s ease, background-color .25s ease;
}
.kpa-faq__item[data-open="true"] .kpa-faq__icon::before,
.kpa-faq__item[data-open="true"] .kpa-faq__icon::after { background: #fff; }
.kpa-faq__icon::before { width: 12px; height: 2px; }
.kpa-faq__icon::after  { width: 2px; height: 12px; }
.kpa-faq__item[data-open="true"] .kpa-faq__icon::after { transform: translate(-50%, -50%) scaleY(0); }

.kpa-faq__a { overflow: hidden; max-height: 0; transition: max-height .35s ease; }
.kpa-faq__item[data-open="true"] .kpa-faq__a { max-height: 700px; }
.kpa-faq__a p { padding: 0 clamp(1.25rem, 2.5vw, 1.85rem) 1.5rem; color: var(--ink-soft); max-width: 68ch; line-height: 1.7; }
/* No JS: every answer is open and readable. */
.kpa-nojs .kpa-faq__a { max-height: none; }

/* ==========================================================================
   QUOTE / CONTACT LAYOUT
   ========================================================================== */
.kpa-book { display: grid; gap: clamp(2rem, 4vw, 3.5rem); }
@media (min-width: 900px) { .kpa-book { grid-template-columns: 1fr 1.02fr; } }
.kpa-book__points { margin-top: 1.75rem; display: grid; gap: 1.1rem; }
.kpa-book__point { display: flex; gap: .85rem; }
.kpa-book__point svg { flex-shrink: 0; color: var(--accent-dark); margin-top: 2px; }
.kpa-book__point strong { display: block; color: var(--brand); font-family: var(--font-display); font-size: 1.05rem; }
.kpa-book__point span { font-size: .93rem; color: var(--ink-soft); }
.kpa-book__tel { margin-top: 1.75rem; padding-top: 1.5rem; border-top: 1px solid var(--line); }
.kpa-book__tel small { display: block; font-size: .74rem; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); }
.kpa-book__tel a { font-family: var(--font-display); font-weight: 700; font-size: 1.6rem; color: var(--brand); }
.kpa-book__tel a:hover { color: var(--accent-dark); }
.kpa-quote { padding-top: clamp(2.5rem, 6vw, 4rem); }

.kpa-contacts { display: grid; gap: 1.15rem; }
@media (min-width: 620px) { .kpa-contacts { grid-template-columns: repeat(2, 1fr); } }
/* ONE definition. There were two competing .kpa-contact blocks and the later
   one forced flex-direction:column, so the icon stacked above the heading and
   the column ran far taller than the form beside it. */
.kpa-contact {
  display: flex; gap: 15px; align-items: flex-start;
  border: 1px solid var(--line); border-top: 3px solid var(--accent);
  border-radius: var(--radius); padding: 1.35rem 1.4rem; background: var(--surface);
}
.kpa-contact__ico { flex: 0 0 42px; width: 42px; height: 42px; border-radius: 12px;
  display: grid; place-items: center; background: var(--accent-tint); color: var(--accent-dark); }
.kpa-contact > div { min-width: 0; }
.kpa-contact h2 { font-family: var(--font-display); font-weight: 700; color: var(--brand); font-size: 1.1rem; }
.kpa-contact p { margin-top: .3rem; font-size: .9rem; color: var(--ink-soft); line-height: 1.5; }
.kpa-contact__tel { display: inline-block; margin-top: .5rem; font-family: var(--font-display);
  font-weight: 700; font-size: 1.2rem; color: var(--brand); overflow-wrap: anywhere; }
.kpa-contact__tel:hover { color: var(--accent-dark); }
.kpa-contact__mail { font-size: 1.02rem; }

/* Both columns finish level: the grid stretches them to the taller one, and the
   cards and the form each fill their own column. */
.kpa-book { align-items: stretch; }
.kpa-book > div { display: flex; flex-direction: column; }
.kpa .kpa-contacts { height: 100%; align-content: space-between; }
.kpa-book__form > * { height: 100%; }

.kpa-hours { margin-top: 1.75rem; padding: 1.4rem 1.5rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.kpa-hours h3 { font-family: var(--font-display); font-weight: 700; font-size: 1.05rem; color: var(--brand); margin-bottom: .85rem; }
.kpa-hours li { display: flex; justify-content: space-between; gap: 1rem; font-size: .9rem; color: var(--ink-soft); padding-block: .3rem; }
.kpa-hours li strong { color: var(--ink); }
.kpa-hours__area { margin-top: .9rem; padding-top: .9rem; border-top: 1px solid var(--line); font-size: .86rem; color: var(--ink-soft); }

/* ==========================================================================
   FORM
   ========================================================================== */
/* Slightly tighter than the page default — this card sits in the hero and
   every millimetre it loses is one the headline above it keeps. */
.kpa-form { display: grid; gap: .8rem; }
.kpa-form__row { display: grid; gap: .8rem; }
@media (min-width: 480px) { .kpa-form__row { grid-template-columns: 1fr 1fr; } }

.kpa-field { display: grid; gap: .35rem; }
.kpa-field label { font-size: .82rem; font-weight: 700; color: var(--brand); }
.kpa-field label .req { color: var(--accent-dark); }
.kpa-field input, .kpa-field textarea {
  width: 100%; font: inherit; font-size: .95rem; color: var(--ink);
  padding: .62em .8em; border: 1px solid var(--line); border-radius: 6px; background: var(--surface);
  transition: border-color .16s ease, box-shadow .16s ease;
}
.kpa-field input::placeholder, .kpa-field textarea::placeholder { color: #9b938c; }
.kpa-field input:focus, .kpa-field textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-tint); }
.kpa-field textarea { resize: vertical; min-height: 74px; }
.kpa-field--invalid input, .kpa-field--invalid textarea { border-color: var(--danger); background: var(--danger-tint); }
.kpa-field__err { font-size: .75rem; color: var(--danger); display: none; }
.kpa-field--invalid .kpa-field__err { display: block; }

.kpa-consent { display: flex; gap: .65rem; align-items: flex-start; font-size: .78rem; color: var(--ink-soft); line-height: 1.5; }
.kpa-consent input { margin-top: .18rem; flex-shrink: 0; width: 17px; height: 17px; accent-color: var(--accent); }
.kpa-consent a { color: var(--accent-dark); text-decoration: underline; }
.kpa-consent.kpa-field--invalid { color: var(--danger); }

/* Off-screen, not display:none — hidden fields are ignored by some bots. */
.kpa-hp { position: absolute !important; left: -9999px !important; width: 1px; height: 1px; overflow: hidden; }

/* Doubled prefix (0,2,0): vertical padding must beat the button reset. */
.kpa .kpa-form__submit { width: 100%; margin-top: .25rem; padding-top: 1.15em; padding-bottom: 1.15em; }
.kpa-form__msg { display: none; font-size: .9rem; line-height: 1.5; padding: .85em 1.1em; border-radius: 6px; }
.kpa-form__msg.is-ok  { display: block; background: var(--ok-tint); border: 1px solid var(--ok-line); color: var(--ok); }
.kpa-form__msg.is-err { display: block; background: var(--danger-tint); border: 1px solid var(--danger-line); color: var(--danger); }
.kpa-form[data-sending="true"] .kpa-form__submit { opacity: .6; pointer-events: none; }

/* ==========================================================================
   FINAL CTA
   ========================================================================== */
.kpa-cta { background: var(--brand); color: var(--on-brand-dim); text-align: center; }
.kpa-cta .kpa-h2 { color: #fff; }
.kpa-cta p { margin: 1.1rem auto 0; max-width: 52ch; }
.kpa-cta__btns { display: flex; flex-wrap: wrap; gap: .85rem; justify-content: center; margin-top: 2rem; }
.kpa-cta__proof {
  display: inline-flex; align-items: center; gap: .5em; margin-top: 1.5rem;
  padding: .55em 1.1em; border: 1px solid rgba(255,255,255,.2); border-radius: var(--radius-pill);
  background: rgba(255,255,255,.06); font-size: .84rem;
}
.kpa-cta__proof svg { color: var(--accent); flex-shrink: 0; }

/* ==========================================================================
   PROSE (legal pages)
   ========================================================================== */
.kpa-prose { max-width: 68ch; }
.kpa-prose p { margin-top: 1.05rem; color: var(--ink-soft); }
.kpa-prose h2 { font-family: var(--font-display); font-weight: 700; font-size: 1.4rem; color: var(--brand); margin-top: 2.5rem; }
.kpa-prose a { color: var(--accent-dark); text-decoration: underline; }

/* ==========================================================================
   404
   ========================================================================== */
.kpa-404 { background: var(--brand); color: var(--on-brand-dim); text-align: center; }
.kpa-404__in { max-width: 44rem; }
.kpa-404 h1 { color: #fff; margin-bottom: 1rem; }
.kpa-404 p { max-width: 48ch; margin-inline: auto; }
.kpa-404__links { display: flex; flex-wrap: wrap; justify-content: center; gap: .5rem; margin-top: 2.5rem; }
.kpa-404__links a { font-size: .85rem; padding: .4em .9em; border: 1px solid rgba(255,255,255,.2); border-radius: var(--radius-pill); }
.kpa-404__links a:hover { border-color: var(--accent); color: var(--accent); }

/* ==========================================================================
   FOOTER
   ========================================================================== */
/* Thin accent rule along the top edge — a small thing, and it is the kind of
   detail that makes the reference build feel finished. */
.kpa-ft { border-top: 3px solid var(--accent); background: var(--brand-deep); color: rgba(247,246,244,.62); font-size: .9rem; }
.kpa-ft__grid { display: grid; gap: 2.5rem; padding-block: clamp(3rem, 6vw, 4.5rem); }
@media (min-width: 760px) { .kpa-ft__grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1000px) { .kpa-ft__grid { grid-template-columns: 1.6fr 1fr 1fr 1.15fr; } }
/* Rating badges. The Google mark ships as a bare viewBox SVG — without an
   explicit size it resolves to 100% width and swallows the column. */
/* One rhythm down the brand column so it lines up with the link columns
   beside it. Each block was setting its own top margin. */
/* The logo needs more air under it than the blocks below need between them,
   or the blurb reads as part of the lockup. */
.kpa-ft__brand > * + * { margin-top: 1.15rem; }
.kpa-ft__logo + * { margin-top: 2.4rem; }
.kpa-ft__logo { display: inline-block; }
.kpa-ft__badges { display: flex; flex-wrap: wrap; gap: .5rem; }
.kpa-ft__badge {
  display: inline-flex; align-items: center; gap: .55em;
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.12);
  border-radius: var(--radius-pill); padding: .45rem .9rem;
}
.kpa-ft__badge svg { width: 18px; height: 18px; flex: 0 0 18px; }
.kpa-ft__badge span { display: flex; flex-direction: column; line-height: 1.2; }
.kpa-ft__badge strong { font-size: .8rem; color: #fff; }
.kpa-ft__badge small { font-size: .72rem; color: rgba(247,246,244,.62); }

/* Column heads sit on the same baseline as the logo, and the links below
   share one spacing step. */
.kpa-ft h3 { font-size: .72rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: #e0555f; margin: 0 0 1.1rem; }
.kpa .kpa-ft__links { display: grid; gap: .55rem; }
.kpa-ft__grid > div { align-content: start; }
.kpa-ft a:hover { color: var(--accent-on-brand); }
.kpa-ft li + li { margin-top: .5rem; }
/* Phone, email, hours and location. The icons were inheriting the column's
   muted body colour and disappearing into it. */
.kpa-ft__contact svg { color: var(--accent-icon); flex-shrink: 0; }
/* flex-start, not center: the address runs to three lines and a centred icon
   floats away from the text it labels. The nudge lines it up optically with
   the cap height of the first line. */
.kpa-ft__contact li { display: flex; align-items: flex-start; gap: .6rem; }
.kpa-ft__contact svg { margin-top: .22em; }
.kpa-ft__logo { position: relative; display: block; height: 52px; width: 200px; }
.kpa-ft__logo img { height: 100%; width: auto; object-fit: contain; object-position: left center; position: relative; }
/* DOUBLED PREFIX, and it is load-bearing. The reset `.kpa p { margin: 0 }` is
   (0,1,1) and beat this rule at (0,1,0), so the margin here never applied at
   any value — the gap under the footer logo has been missing the whole time.
   Fourth component to hit this; it is the trap named in CLAUDE.md. */
.kpa .kpa-ft__blurb { margin-top: 2.2rem; max-width: 40ch; }
.kpa-ft__cta { margin-top: 1.5rem; }
.kpa-ft__social { display: flex; gap: .6rem; }
.kpa-ft__social a {
  display: grid; place-items: center; width: 40px; height: 40px;
  border: 1px solid rgba(255,255,255,.2); border-radius: 50%;
  color: var(--on-brand-dim); transition: border-color .18s ease, color .18s ease;
}
.kpa-ft__social a:hover { border-color: var(--accent-on-brand); color: var(--accent-on-brand); }
/* Bare viewBox SVGs again — they collapse without an explicit size. */
.kpa-ft__social a svg { width: 18px; height: 18px; display: block; }
.kpa-ft__area { margin-top: 1rem; color: rgba(247,248,248,.5); }
.kpa-ft__lists { border-top: 1px solid rgba(255,255,255,.1); padding-block: clamp(2rem, 4vw, 2.75rem); display: grid; gap: 2rem; }
@media (min-width: 760px) { .kpa-ft__lists { grid-template-columns: 1fr 1.4fr; } }
.kpa-ft__pills { display: flex; flex-wrap: wrap; gap: .45rem; }
.kpa-ft__pills a { background: transparent; border-color: rgba(255,255,255,.16); color: rgba(247,248,248,.68); }
.kpa-ft__pills a:hover { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
/* No background. This carried `var(--canvas)` — the LIGHT page ground — inside a
   dark footer, and because it sits in a .kpa-wrap it rendered as a pale band
   floating short of both edges. The hairline above it is all this bar needs.
   (The `border-top: 0` that used to sit here was dead: overridden on the very
   next declaration.) */
.kpa-ft__legal { color: rgba(247,246,244,.5);
  border-top: 1px solid rgba(255,255,255,.1); padding-block: 1.5rem;
  display: flex; flex-wrap: wrap; gap: .6rem 1.5rem; justify-content: space-between; font-size: .79rem;
}

/* ==========================================================================
   COOKIE BANNER (injected by site.js on first visit)
   ========================================================================== */
.kpa-cookie {
  position: fixed; left: clamp(1rem, 3vw, 2rem); bottom: clamp(1rem, 3vw, 2rem);
  width: min(440px, calc(100% - 2rem)); z-index: 120;
  background: var(--brand); color: var(--on-brand-dim);
  border: 1px solid rgba(255,255,255,.16); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); padding: 1.2rem 1.35rem;
  transform: translateY(150%); opacity: 0;
  transition: transform .45s cubic-bezier(.22,.61,.36,1), opacity .45s ease;
}
.kpa-cookie.is-in { transform: none; opacity: 1; }
.kpa-cookie p { font-size: .84rem; line-height: 1.55; }
.kpa-cookie a { color: var(--accent-on-brand); text-decoration: underline; }
/* The banner ground is --brand, so --btn--outline (brand border, brand text)
   renders black-on-black and the second button vanishes. Same root cause as
   --accent-on-brand: light-ground tokens reused on a dark surface. */
.kpa .kpa-cookie .kpa-btn--outline { border-color: rgba(255,255,255,.45); color: var(--on-brand); }
.kpa .kpa-cookie .kpa-btn--outline:hover { background: var(--on-brand); color: var(--brand); }
.kpa-cookie__btns { display: flex; gap: .6rem; margin-top: 1rem; flex-wrap: wrap; }
.kpa-cookie__btns .kpa-btn { padding: .55em 1.2em; font-size: .85rem; }
/* The chat bubble also lives bottom-right. Lift the card clear rather than
   shrinking it — the Accept and Decline buttons need the width. */
@media (max-width: 600px) {
  .kpa-cookie { bottom: calc(env(safe-area-inset-bottom, 0px) + 5.75rem); left: 1rem; width: calc(100% - 2rem); }
}
@media (prefers-reduced-motion: reduce) { .kpa-cookie { transition: opacity .3s ease; transform: none; } }

/* ==========================================================================
   GUIDES
   ========================================================================== */
.kpa-guides { display: grid; gap: clamp(1rem, 2.2vw, 1.5rem); }
@media (min-width: 640px) { .kpa-guides { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .kpa-guides { grid-template-columns: repeat(3, 1fr); } }
.kpa-guide {
  display: flex; flex-direction: column; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  box-shadow: var(--shadow); transition: transform .25s ease, box-shadow .25s ease;
}
.kpa-guide:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
.kpa-guide__img { position: relative; aspect-ratio: 16 / 10; }
.kpa-guide__img .kpa-ph { width: 100%; height: 100%; }
.kpa-guide__img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.kpa-guide__body { padding: 1.35rem 1.5rem 1.6rem; display: flex; flex-direction: column; flex: 1; }
.kpa-guide__body .kpa-eyebrow { margin-bottom: .5rem; }
.kpa-guide__body h2 { color: var(--brand); }
.kpa-guide__body p { margin-top: .55rem; font-size: .93rem; color: var(--ink-soft); flex: 1; }

/* ==========================================================================
   ICON CARDS — used by service and area pages for "why us" style blocks
   ========================================================================== */
.kpa-cards { display: grid; gap: clamp(1rem, 2.2vw, 1.4rem); }
@media (min-width: 640px) { .kpa-cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .kpa-cards { grid-template-columns: repeat(4, 1fr); } }
/* Three cards should be a row of three, not 2 + 1. */
@media (min-width: 1040px) {
  .kpa-cards:has(> :nth-child(3)):not(:has(> :nth-child(4))) { grid-template-columns: repeat(3, 1fr); }
  /* Five or six cards on a four-column grid leaves a 4+2 or 4+1 orphan row.
     Read the count and drop to thirds so it lands 3+3 or 3+2. Same class of
     bug as the services-grid symmetry note in CLAUDE.md. */
  .kpa-cards:has(> :nth-child(5)):not(:has(> :nth-child(7))) { grid-template-columns: repeat(3, 1fr); }
}
.kpa-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: clamp(1.35rem, 2.5vw, 1.75rem); box-shadow: var(--shadow);
}
.kpa-card__ico {
  width: 48px; height: 48px; border-radius: 14px; display: grid; place-items: center;
  background: var(--accent-tint); color: var(--accent-dark); margin-bottom: 1.1rem;
}
.kpa-card h3 { font-family: var(--font-display); font-weight: 700; font-size: 1.12rem; color: var(--brand); }
.kpa-card p { margin-top: .55rem; font-size: .93rem; color: var(--ink-soft); }

/* ==========================================================================
   OPTIONS — the materials or finishes INSIDE a service
   ========================================================================== */
.kpa-opts { display: grid; gap: clamp(1rem, 2.2vw, 1.4rem); }
@media (min-width: 620px) { .kpa-opts { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .kpa-opts { grid-template-columns: repeat(3, 1fr); } }
.kpa-opt { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
/* A colour rule between the photograph and the copy. Small thing, and it is most
   of what makes the reference's finish cards read as designed rather than stacked. */
.kpa-opt__img { position: relative; aspect-ratio: 4 / 3; border-bottom: 3px solid var(--accent); }
.kpa-opts > :nth-child(3n+2) .kpa-opt__img { border-bottom-color: var(--brand); }
.kpa-opt__img .kpa-ph { width: 100%; height: 100%; }
.kpa-opt__img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.kpa-opt__body { padding: 1.25rem 1.35rem 1.5rem; }
.kpa-opt__body h3 { font-family: var(--font-display); font-weight: 700; font-size: 1.08rem; color: var(--brand); }
.kpa-opt__body p { margin-top: .5rem; font-size: .92rem; color: var(--ink-soft); }

/* ==========================================================================
   LONGFORM — the deep middle of a service page: prose plus a sticky aside.
   This is where the section count goes from four to fifteen.
   ========================================================================== */
.kpa-longform { display: grid; gap: clamp(2rem, 4vw, 3.5rem); }
@media (min-width: 940px) { .kpa-longform { grid-template-columns: 1.6fr .9fr; align-items: start; } }
.kpa-longform__main > h2 { margin-top: 2.5rem; }
.kpa-longform__main > h2:first-child { margin-top: 0; }
.kpa-longform__main p { margin-top: 1.05rem; color: var(--ink-soft); max-width: 68ch; }
.kpa-longform__side {
  background: var(--surface); border: 1px solid var(--line); border-top: 3px solid var(--accent);
  border-radius: var(--radius); padding: clamp(1.35rem, 2.5vw, 1.85rem);
}
@media (min-width: 940px) { .kpa-longform__side { position: sticky; top: 100px; } }
.kpa-longform__side h3 { font-family: var(--font-display); font-weight: 700; font-size: 1.05rem; color: var(--brand); }
.kpa .kpa-longform__side ul { display: grid; gap: .6rem; margin-top: 1rem; }
.kpa-longform__side li { display: flex; gap: .6em; align-items: flex-start; font-size: .92rem; color: var(--ink-soft); }
.kpa-longform__side li svg { flex-shrink: 0; margin-top: .2em; color: var(--accent-dark); }
.kpa-longform__note { margin-top: 1.1rem; padding-top: 1.1rem; border-top: 1px solid var(--line); font-size: .86rem; color: var(--ink-soft); }
.kpa-longform__side .kpa-btn { margin-top: 1.1rem; width: 100%; }

/* Service-page sidebar quote card. The ask rides down the page with the
   reader rather than waiting at the footer — the single highest-leverage
   thing the reference service pages do that ours did not. */
/* Local service angles on an area page. Left border rather than a card: these
   are an argument in three parts, not three separate offers. */
.kpa-angles { display: grid; gap: clamp(1.5rem, 3vw, 2rem); }
@media (min-width: 860px) { .kpa-angles { grid-template-columns: repeat(3, 1fr); } }
.kpa-angle { border-left: 3px solid var(--accent); padding-left: clamp(1rem, 2vw, 1.35rem); }
.kpa-angle h3 { font-family: var(--font-display); font-weight: 700; font-size: 1.12rem; color: var(--brand); line-height: 1.25; }
.kpa-angle h3 a { color: inherit; }
.kpa-angle h3 a:hover { color: var(--accent-dark); }
.kpa-angle p { margin-top: .6rem; font-size: .93rem; color: var(--ink-soft); line-height: 1.6; }

.kpa-svcaside { padding-bottom: 1.35rem; border-bottom: 1px solid var(--line); }
.kpa-svcaside__h { font-size: 1.3rem !important; line-height: 1.22; }
.kpa-svcaside__p { margin-top: .55rem; font-size: .9rem; color: var(--ink-soft); }
/* The two routes carry equal weight: some people book, some ring. */
.kpa .kpa-svcaside__call { display: inline-flex; align-items: center; justify-content: center; gap: .55em; margin-top: .6rem; }
.kpa-svcaside__proof {
  display: flex; align-items: center; gap: .5em; margin-top: 1rem;
  font-size: .84rem; font-weight: 600; color: var(--ink-soft);
}
.kpa-svcaside__proof svg { color: var(--accent-dark); flex-shrink: 0; }
/* Cross-links out to the rest of the range, as pills rather than a plain list —
   they read as navigation, not as body copy. */
.kpa-longform__side > h3 { margin-top: 1.5rem; }
.kpa-svcaside + h3 { margin-top: 1.5rem; }
.kpa .kpa-svcaside__links { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: .9rem; }
.kpa .kpa-svcaside__links li { display: block; }
.kpa-svcaside__links a {
  display: inline-block; padding: .42em 1em; border: 1px solid var(--line);
  border-radius: var(--radius-pill); font-size: .84rem; font-weight: 600;
  color: var(--brand); background: var(--canvas); transition: border-color .18s ease, color .18s ease;
}
.kpa-svcaside__links a:hover { border-color: var(--accent); color: var(--accent-dark); }

/* ==========================================================================
   HIGHLIGHT BAND — one offer, promotion or standing message.
   Deliberately NOT a discount component. Most clients won't discount, and the
   band works just as well for "free site visit this month", "booking into
   [month]", or a seasonal message. The eyebrow chip is optional.
   ========================================================================== */
.kpa-highlight { padding-block: clamp(1.5rem, 4vw, 2.5rem); }
.kpa-highlight__in {
  background: var(--brand); color: var(--on-brand); border-radius: var(--radius);
  padding: clamp(1.5rem, 3.5vw, 2.25rem) clamp(1.5rem, 4vw, 3rem);
  display: grid; gap: 1.25rem; align-items: center; box-shadow: var(--shadow-lg);
}
@media (min-width: 860px) { .kpa-highlight__in { grid-template-columns: auto 1fr auto; gap: 2rem; } }
.kpa-highlight__chip {
  justify-self: start; align-self: start;
  background: var(--accent); color: var(--on-accent);
  font-size: .7rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  padding: .5em 1.1em; border-radius: var(--radius-pill); white-space: nowrap;
}
.kpa-highlight h2 { font-family: var(--font-display); font-weight: 700; font-size: clamp(1.25rem, 1.1rem + .8vw, 1.6rem); color: #fff; }
.kpa-highlight p { margin-top: .5rem; color: var(--on-brand-dim); font-size: .95rem; max-width: 60ch; }
.kpa-highlight .kpa-btn { white-space: nowrap; }

/* ==========================================================================
   BRANDS / ACCREDITATION WALL
   ========================================================================== */
.kpa-brands { background: var(--canvas-warm); border-block: 1px solid var(--line); padding-block: clamp(2.5rem, 5vw, 3.5rem); }
.kpa-brands__head { text-align: center; margin-bottom: 2rem; }
.kpa-brands__head h2 { color: var(--brand); }
.kpa-brands__head p { margin-top: .6rem; font-size: .92rem; color: var(--ink-soft); max-width: 52ch; margin-inline: auto; }
.kpa-brands__row { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 2rem clamp(2rem, 5vw, 3.5rem); }
.kpa-brands__row li { position: relative; width: 118px; height: 40px; }
.kpa-brands__row img {
  height: 40px; width: auto; max-width: 118px; object-fit: contain; margin-inline: auto; position: relative;
  /* Mixed-quality manufacturer logos read as clutter in full colour. Greyscale
     until hover keeps the row calm and the eye on the copy. */
  filter: grayscale(1); opacity: .62; transition: filter .25s ease, opacity .25s ease;
}
.kpa-brands__row li:hover img { filter: none; opacity: 1; }
/* Some suppliers only publish a white-on-transparent mark, because their own
   header is dark. Invisible on this row without the flag. */
.kpa-brands__row li.is-inverted img { filter: grayscale(1) invert(1); }
.kpa-brands__row li.is-inverted:hover img { filter: invert(1); opacity: 1; }
.kpa-ph--brand { position: absolute; inset: 0; }
.kpa-ph--brand::after { font-size: .5rem; padding: .25em .5em; letter-spacing: .06em; }

/* ==========================================================================
   MID-PAGE CTA BAND
   ========================================================================== */
.kpa-band-cta { background: var(--accent); color: var(--on-accent); padding-block: clamp(2.5rem, 5vw, 3.5rem); }
.kpa-band-cta__in { display: grid; gap: 1.75rem; align-items: center; }
@media (min-width: 860px) { .kpa-band-cta__in { grid-template-columns: 1.5fr auto; gap: 3rem; } }
.kpa-band-cta .kpa-h2 { color: var(--on-accent); }
.kpa-band-cta p { margin-top: .6rem; color: var(--on-accent-dim); max-width: 52ch; }
.kpa-band-cta__act { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.5rem; }
/* The accent button would vanish on the accent band — invert it. */
/* Inverted so it does not vanish into the band. --brand with light text
   survives a light accent; white-with-accent-text does not. */
.kpa .kpa-band-cta__act .kpa-btn--accent { background: var(--brand); color: var(--on-brand); }
.kpa .kpa-band-cta__act .kpa-btn--accent:hover { background: var(--brand-soft); color: var(--on-brand); }
.kpa-band-cta__tel { font-family: var(--font-display); font-weight: 700; font-size: 1.35rem; color: var(--on-accent); white-space: nowrap; }
.kpa-band-cta__tel small { display: block; font-family: var(--font-body); font-weight: 500; font-size: .7rem; letter-spacing: .1em; text-transform: uppercase; color: var(--on-accent-dim); }

/* ==========================================================================
   SCROLL REVEAL
   IMPORTANT: content is hidden only once JS has added `.kpa-anim` AND has
   confirmed it can observe and reveal it. Never hide on `.kpa-reveal` alone —
   if the script fails or the observer is unsupported, the page would render
   blank. Progressive enhancement only.
   ========================================================================== */
.kpa-anim .kpa-reveal { opacity: 0; transform: translateY(18px); }
.kpa-anim .kpa-reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity .7s cubic-bezier(.22,.61,.36,1), transform .7s cubic-bezier(.22,.61,.36,1);
}
@media (prefers-reduced-motion: reduce) {
  .kpa-anim .kpa-reveal, .kpa-anim .kpa-reveal.is-in { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* ==========================================================================
   BARE EMBED PAGES  (/discount, /review-us, /marketing-form)
   A single third-party form and nothing else. The header and footer stay so the
   page still reads as GGH — a naked iframe asking for someone's details looks
   like a phishing page — but the chat bubble, cookie banner and analytics are
   all off via the `bare` flag.
   ========================================================================== */
.kpa-wrap--narrow { max-width: 760px; }
.kpa-embedpage .kpa-sect-head { margin-bottom: 1.6rem; }
.kpa-embed {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: clamp(.5rem, 1.5vw, 1rem); box-shadow: var(--shadow);
}
.kpa-embed iframe { display: block; width: 100%; }

/* --plain: no card around the form. Use this when the GHL form itself is set to
   a transparent background with bordered fields, so it sits directly on the page
   and inherits the look rather than arriving as someone else's panel. The card
   version above is the fallback for a form still carrying its own styling. */
.kpa-embed--plain { background: none; border: 0; box-shadow: none; padding: 0; }

/* MIN-HEIGHT FLOORS, and they are load-bearing.

   GoHighLevel's resizer measures the form at ITS viewport, not at the width it
   ends up rendering in. A long field label that fits on one line in the builder
   wraps to two in a 670px container, and the extra line is height the resizer
   never accounted for — so it sets a height that clips the submit button. That
   is exactly what happened here: it set 388px for a form needing 444px.

   CSS min-height beats a smaller inline height, so this is a floor the resizer
   can grow past but never crop below. Measured per form at 670, 560 and 400px
   wide, then rounded up. Re-measure if a form's fields change. */
.kpa-embed--discount  iframe { min-height: 690px; }
.kpa-embed--review    iframe { min-height: 500px; }  /* multi-step: shortest step measures 442px at 400w */
.kpa-embed--marketing iframe { min-height: 480px; }

/* The persuasion lives on the page, not in the form — that is what lets one
   master form be cloned to every client account without editing. */
.kpa .kpa-embed__points {
  display: grid; gap: .55rem; margin: 0 0 1.9rem; padding: 0; list-style: none;
  max-width: 46ch; margin-inline: auto;
}
.kpa .kpa-embed__points li {
  position: relative; padding-left: 1.75rem; font-size: .95rem; color: var(--ink-soft);
}
.kpa-embed__points li::before {
  content: ""; position: absolute; left: 0; top: .42em; width: 1rem; height: .55rem;
  border-left: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg);
}
.kpa .kpa-embed__foot {
  margin-top: 1.4rem; text-align: center; font-size: .87rem; color: var(--ink-soft);
  max-width: 52ch; margin-inline: auto;
}

/* Numbered steps on the internal follow-up page. Numbered because these genuinely
   are a sequence — one triggers, then the other — not decoration. */
.kpa .kpa-embed__steps {
  counter-reset: s; list-style: none; padding: 0;
  margin: 0 0 1.5rem; display: grid; gap: .9rem; max-width: 54ch; margin-inline: auto;
}
.kpa .kpa-embed__steps li {
  counter-increment: s; position: relative; padding-left: 3rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding-block: 1rem; padding-right: 1.1rem;
}
.kpa-embed__steps li::before {
  content: counter(s); position: absolute; left: 1rem; top: 1rem;
  width: 1.6rem; height: 1.6rem; border-radius: 50%;
  background: var(--accent); color: var(--on-accent);
  font-family: var(--font-display); font-weight: 700; font-size: .82rem;
  display: grid; place-items: center;
}
.kpa .kpa-embed__steps h2 { font-family: var(--font-display); font-size: 1rem; font-weight: 700; color: var(--brand); }
.kpa .kpa-embed__steps p { margin-top: .3rem; font-size: .92rem; color: var(--ink-soft); }
.kpa .kpa-embed__why {
  text-align: center; font-weight: 700; color: var(--brand);
  margin: 0 0 1.8rem; font-size: .97rem;
}
