/* === Red Barn Media Group — Climatech of Professional Air === */

/* ----------------------------------------------------------------------------
   FILE SHAPE — read this before deleting anything.

   Everything above "=== Home Hero ===" is the PUBLISHED DESIGN SYSTEM: tokens,
   type scale, buttons, colour/surface utilities, links, icon primitives, form
   controls. Everything below it is home-page section CSS.

   The vocabulary is DELIBERATELY BROADER THAN THE HOME PAGE CONSUMES. The 6b
   sweep (2026-08-05) confirmed ~44 of these classes have no reference in any
   PHP/JS/HTML file yet — the .bg- .text- .link- .border- colour tokens,
   .icon-stacked and its four variants, .icon-context, .btn-252459, .btn-lg,
   .btn-light-panel, .surface-light, .form-select, the .card-img family,
   .card-body, .fs-body-lg, .label-md, .black, .regular. Step 8 builds
   page_template.php out of exactly this vocabulary, and Step 8 treats a NEW
   class as an alarm — so deleting these now guarantees reinventing them then.

   UNREFERENCED HERE DOES NOT MEAN DEAD. Only delete from this half if a class is
   unreachable by construction (e.g. .navbar-expand-xl, removed at 6b because
   menu.php expands at xxl and the selector could never match).
   ---------------------------------------------------------------------------- */

/* === Self-hosted webfonts =====================================================
   Replaces the Adobe Fonts (Typekit) kit that used to load from use.typekit.net in
   doc-header.php. Three families, six files, all same-origin — see /fonts.

     Figma / Typekit        Self-hosted        Weights actually consumed
     ------------------     ---------------    -------------------------------
     inter-variable         Inter (variable)   400, 600, 700 + 400 italic
     kyrial-sans-pro        Khula              600, 700, 800
     kepler-std-extended    Taviraj            900 italic ONLY (.cpa-overline)

   woff2 only, and every one is SUBSET TO LATIN (960 KB -> 257 KB, 73% off). All three
   families shipped scripts this site will never set — Inter carried Cyrillic, Greek and
   Vietnamese; Khula is a Devanagari family; Taviraj is a Thai one.

   /fonts holds NOTHING BUT these six subset files. The unsubset .ttf originals were
   removed from the repo and are .gitignore'd — all three families are OFL and freely
   re-downloadable. fonts/README.md carries the exact versions, the regeneration
   command, and the width checks that prove a re-cut is faithful. Read it before
   touching a font file.

   ⚠️ KHULA HAS NO 900. Its family stops at 800 (ExtraBold), but --font-weight-black
      is 900 and is the single most-used weight in this build (.btn, .label-lg, h6,
      every nav link, the footer eyebrow). Rather than rewrite 400+ consumers or
      leave the match implicit, THE 800 FACE DECLARES `font-weight: 800 900` — a
      range descriptor on a static file, which tells the matcher this one file
      answers both. A 900 request lands on Khula ExtraBold exactly, with no
      synthesis. Do not "tidy" that back to a single 800.

      The 900 in --font-weight-black is therefore still literal for Taviraj, which
      does have a true 900. That is why the token was left at 900.

   font-display: swap everywhere — text paints in the fallback immediately and
   swaps on arrival. Safe for the same reason the old async Typekit swap was safe:
   the headings carry explicit line-heights, so the swap repaints without reflowing
   (see the LCP note in doc-header.php). Re-verify if a heading ever lands in an
   auto-height box. */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-variable.woff2') format('woff2');
  font-weight: 100 900;   /* variable wght axis; opsz 14-32 is left on auto */
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-variable-italic.woff2') format('woff2');
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'Khula';
  src: url('/fonts/khula-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Khula';
  src: url('/fonts/khula-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Khula';
  src: url('/fonts/khula-800.woff2') format('woff2');
  font-weight: 800 900;   /* see the KHULA HAS NO 900 note above — not a typo */
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Taviraj';
  src: url('/fonts/taviraj-italic-900.woff2') format('woff2');
  font-weight: 900;
  font-style: italic;
  font-display: swap;
}

/* === CSS Custom Properties ===
   Values are the literal Figma token values. Do not approximate or snap to a neighbour.
   Hover tokens are TINTS (base mixed with white — 20% for brand, 10% for neutrals),
   never shades and never a pointer to another palette colour. */
:root {
  /* Brand Colors */
  --color-red: #C02021;            /* Figma Brand/Heating */
  --color-red-hover: #CD4D4D;      /* red + 20% white */
  --color-slate: #2E414C;          /* Figma Brand/Slate */
  --color-slate-hover: #586770;    /* slate + 20% white */
  --color-dark-stone: #0F222E;     /* Figma Brand/Dark Stone */
  --color-dark-stone-hover: #3F4E58;
  --color-brand-light: #EEFFEE;    /* Figma Brand/Light */

  /* Neutrals */
  --color-neutral-50: #EEF0F5;
  --color-neutral-200: #DCDFE7;
  --color-neutral-300: #CCCED6;
  --color-neutral-500: #A9ABB4;
  --color-neutral-600: #8D919A;
  --color-neutral-700: #5E6B78;
  --color-neutral-700-hover: #6E7A86; /* neutral + 10% white */

  /* Dark Mode Surfaces */
  --color-dark-00: #121212;        /* Figma DarkMode/00dp */
  --color-dark-01: #191B1F;        /* Figma Dark Mode/01dp */
  --color-dark-02: #23292E;        /* Figma Dark Mode/02dp */

  /* Generic */
  --color-black: #000000;
  --color-white: #FFFFFF;

  /* Gradients
     Hero: the video ships CLEAN — this overlay is required, not optional.
     (Inverse of the usual baked-in-hero rule. See .project-brief.md.)

     These stops are DERIVED, not eyeballed. Every 2nd frame of the 34s hero clip was
     composited under candidate gradients and measured for white-text contrast across
     the text column (x 8.75%-62.25%). The original softer overlay failed on 15 of 17
     frames, worst 2.70:1. This is the LEANEST profile that clears AA on every frame —
     worst case 5.20:1 — so the footage stays as visible as legibility allows.
     The alpha at the 62% stop is load-bearing: dropping it to 0.55 fails at 3.80:1. */
  --gradient-hero: linear-gradient(90deg, rgba(15, 34, 46, 0.94) 0%, rgba(15, 34, 46, 0.82) 30%, rgba(85, 24, 30, 0.70) 46%, rgba(15, 34, 46, 0.65) 62%, rgba(15, 34, 46, 0.23) 82%, rgba(15, 34, 46, 0) 100%);
  /* Below lg the hero copy spans the FULL width, so a left-to-right scrim leaves the
     end of every line unprotected — measured 1.32:1 on the poster's bright sky.
     The mobile scrim runs vertically and covers the whole frame instead. */
  --gradient-hero-mobile: linear-gradient(180deg, rgba(15, 34, 46, 0.90) 0%, rgba(15, 34, 46, 0.84) 45%, rgba(85, 24, 30, 0.80) 72%, rgba(15, 34, 46, 0.90) 100%);
  --gradient-steel: linear-gradient(135deg, #0F222E 0%, #2E414C 50%, #0F222E 100%);
  /* Navigation runs a straight left-to-right dark->slate, NOT the symmetric steel ramp.
     Sampled off the Figma nav: #112633 at 2% through #2C3F4A at 98%. */
  --gradient-nav: linear-gradient(90deg, #0F222E 0%, #1B303C 45%, #2E414C 100%);
  --gradient-section-dark: linear-gradient(160deg, #0F222E 0%, #191B1F 55%, #0F222E 100%);
  --gradient-brands: linear-gradient(120deg, #0F222E 0%, #23292E 38%, #7A1A1D 72%, #0F222E 100%);
  --gradient-our-story: linear-gradient(100deg, #0F222E 0%, #23292E 60%, #6E1A1C 100%);
  --gradient-maint-plans: linear-gradient(180deg, #0F222E 0%, #23292E 45%, #A81C1D 100%);
  --gradient-footer: linear-gradient(115deg, #0F222E 0%, #16303F 45%, #0F222E 100%);
  /* Opener: navy on the left blooming to brand red off the right edge.
     Sampled from the Figma frame — #122835 far left, #A81F23 at mid-right. */
  --gradient-opener: radial-gradient(ellipse 95% 130% at 98% 50%, #A81F23 0%, #6A1D20 24%, #3C2029 48%, #162C3A 74%, #122835 100%);
  --gradient-red-fade: linear-gradient(179deg, #C02021 2.88%, #6E1A1C 99.5%);
  /* Services panel: Figma names this "Gradient/Steel" but its literal value is an
     88deg left-to-right ramp, NOT the symmetric --gradient-steel above. Kept separate
     rather than snapping to the neighbour. */
  --gradient-service-panel: linear-gradient(88deg, #122734 1.25%, #2E414C 98.75%);

  /* Typography — families. Self-hosted; see the @font-face block at the top of this file. */
  --font-heading: 'Khula', sans-serif;
  --font-body: 'Inter', sans-serif;
  --font-overline: 'Taviraj', serif;

  /* Typography — weights.
     NOTE: Figma's style NAMES disagree with their encoded weights
     (its "Semibold" paragraph token is weight 700, its "Medium" is 600).
     These map to the ENCODED weights, which is what actually renders.

     --font-weight-black stays 900 even though Khula tops out at 800: Taviraj has a
     true 900, and the Khula 800 face claims the 800-900 range so headings resolve
     to ExtraBold without synthesis. See the @font-face block. */
  --font-weight-black: 900;
  --font-weight-bold: 700;
  --font-weight-semibold: 600;
  --font-weight-regular: 400;

  /* Layout.
     --menu-height is the TOTAL nav height. Below xxl that is a single row (logo +
     hamburger). At xxl the logo sits beside two stacked rows, so it grows to Figma's
     134px — see the xxl override below. Anything offsetting the fixed nav (the hero)
     should reference this one value rather than adding the utility bar separately. */
  --menu-height: 5.75rem;
  --menu-height-shrink: 4rem;
  --utility-bar-height: 2.5rem;
  --transition-standard: 0.25s;
  --border-radius-standard: 1.25rem;  /* 20px — Figma large panel radius */
  --border-radius-card: 0.75rem;      /* 12px — Figma card radius */
  --border-radius-panel: 1rem;        /* 16px — Figma tile/panel radius */
  --border-radius-pill: 30px;         /* Figma button radius */
  --border-radius-input: 0.5rem;
  --box-shadow-dropdown: 0 3rem 3rem rgba(0, 0, 0, 0.175);

  /* Tap target floor (WCAG 2.5.5) */
  --tap-target-min: 40px;
}

/* === Base Styles === */
html,
body {
  width: 100%;
  height: 100%;
}

body {
  color: var(--color-black);
  font-family: var(--font-body);
  font-weight: var(--font-weight-regular);
  background-color: var(--color-white);
  position: relative;
}

/* === Typography ===
   Sizes/line-heights are the literal Figma type-scale values. */
h1, .h1,
h2, .h2,
h3, .h3,
h4, .h4,
h5, .h5 {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
}
h1, .h1 { font-size: 4rem;    line-height: 4.5rem;  } /* Display/Large  64/72 */
h2, .h2 { font-size: 2.5rem;  line-height: 3rem;    } /* Heading/H2     40/48 */
h3, .h3 { font-size: 2rem;    line-height: 2.5rem;  } /* Heading/H3     32/40 */
h4, .h4 { font-size: 1.5rem;  line-height: 2rem;    } /* Heading/H4     24/32 */
h5, .h5 { font-size: 1.25rem; line-height: 1.75rem; } /* Heading/H5     20/28 */
h6, .h6 {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-black);
  text-transform: uppercase;
  font-size: 1rem;
  line-height: 1.125rem;
} /* Label/Large 16/18 */

/* Fluid headline sizes — the 64px display would overflow a phone at fixed size. */
@media (max-width: 991.98px) {
  h1, .h1 { font-size: clamp(2rem, 7vw, 4rem);      line-height: 1.12; }
  h2, .h2 { font-size: clamp(1.75rem, 5vw, 2.5rem); line-height: 1.2;  }
  h3, .h3 { font-size: clamp(1.5rem, 4vw, 2rem);    line-height: 1.25; }
}

/* Semibold heading variant — Figma pairs H3/H4/H5 Bold and SemiBold. */
.heading-semibold {
  font-weight: var(--font-weight-semibold);
}

/* Label tokens — Khula ExtraBold (the 900 "black" role), uppercase.
   Used for button labels and eyebrow chips. */
.label-lg { font-family: var(--font-heading); font-weight: var(--font-weight-black); text-transform: uppercase; font-size: 1rem;     line-height: 1.125rem; }
.label-md { font-family: var(--font-heading); font-weight: var(--font-weight-black); text-transform: uppercase; font-size: 0.875rem; line-height: 1rem; }
.label-sm { font-family: var(--font-heading); font-weight: var(--font-weight-black); text-transform: uppercase; font-size: 0.75rem;  line-height: 0.875rem; }

/* Overline — Taviraj Black Italic, ALWAYS uppercase. The uppercase transform is part
   of the token, not a per-use decision.
   Taviraj is a normal-width face standing in for Kepler Std EXTENDED, so it reads
   narrower than the Figma comp — the letter-spacing below is the compensation. */
.cpa-overline {
  display: block;
  font-family: var(--font-overline);
  font-weight: var(--font-weight-black);
  font-style: italic;
  font-size: 1rem;
  line-height: 1.25rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: 0.5rem;
}

/* Body copy — Figma Paragraph scale */
p,
ul li,
ol li,
table {
  font-size: 1rem;
  line-height: 1.5rem;
  font-weight: var(--font-weight-regular);
}
.fs-body-lg { font-size: 1.125rem; line-height: 1.625rem; }
.fs-body-sm { font-size: 0.875rem; line-height: 1.375rem; }
.fs-body-xs { font-size: 0.75rem;  line-height: 1.25rem;  }

.bold      { font-weight: var(--font-weight-bold); }
.semibold  { font-weight: var(--font-weight-semibold); }
.regular   { font-weight: var(--font-weight-regular); }
.black     { font-weight: var(--font-weight-black); }

strong {
  font-weight: var(--font-weight-bold);
}

/* === Tap-target floor (WCAG 2.5.5 — 40x40 minimum) ===
   Apply to interactive elements whose own content won't fill 40px:
   icon-only buttons, nav toggles, social icons, carousel arrows,
   accordion/tab toggles, close buttons.
   Do NOT stack this on an element whose own padding already clears 40px. */
.tap-target {
  min-height: var(--tap-target-min);
  min-width: var(--tap-target-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* === Buttons ===
   Figma buttons are 40px tall pills: 12px vertical + 16px horizontal padding
   around a 16px line-height label. That already clears the tap-target floor,
   so .btn does NOT need .tap-target stacked on it.
   Base .btn is extended (not redefined) — Bootstrap's own sizing stays intact. */
.btn {
  padding: 0.75rem 1rem;
  font-family: var(--font-heading);
  font-weight: var(--font-weight-black);
  text-transform: uppercase;
  border-radius: var(--border-radius-pill);
  font-size: 0.875rem;
  line-height: 1rem;
  min-height: var(--tap-target-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

.btn.btn-sm {
  padding: 0.625rem 0.875rem;
  font-size: 0.75rem;
  min-height: var(--tap-target-min); /* keeps small buttons on the floor */
}
.btn.btn-lg {
  padding: 0.875rem 1.5rem;
  font-size: 1rem;
}

/* Brand buttons drive Bootstrap 5.3's own --bs-btn-* variables rather than
   overriding its declarations with !important. This is the documented BS 5.3
   button API: it needs no !important, and active/focus/disabled states inherit
   correctly instead of having to be re-specified.
   Hover values are TINTS (base + 20% white) per the hover-token rule. */

/* Primary — brand red. .btn-252459 is the dealer-ID alias of the same button. */
.btn-red,
.btn-252459 {
  --bs-btn-color: var(--color-white);
  --bs-btn-bg: var(--color-red);
  --bs-btn-border-color: var(--color-red);
  --bs-btn-hover-color: var(--color-white);
  --bs-btn-hover-bg: var(--color-red-hover);
  --bs-btn-hover-border-color: var(--color-red-hover);
  --bs-btn-active-color: var(--color-white);
  --bs-btn-active-bg: var(--color-red-hover);
  --bs-btn-active-border-color: var(--color-red-hover);
  --bs-btn-disabled-color: var(--color-white);
  --bs-btn-disabled-bg: var(--color-red);
  --bs-btn-disabled-border-color: var(--color-red);
  --bs-btn-focus-shadow-rgb: 192, 32, 33;
}

/* Secondary — Figma "Button 2" fills with Neutral/700 */
.btn-neutral {
  --bs-btn-color: var(--color-white);
  --bs-btn-bg: var(--color-neutral-700);
  --bs-btn-border-color: var(--color-neutral-700);
  --bs-btn-hover-color: var(--color-white);
  --bs-btn-hover-bg: var(--color-neutral-700-hover);
  --bs-btn-hover-border-color: var(--color-neutral-700-hover);
  --bs-btn-active-color: var(--color-white);
  --bs-btn-active-bg: var(--color-neutral-700-hover);
  --bs-btn-active-border-color: var(--color-neutral-700-hover);
  --bs-btn-focus-shadow-rgb: 94, 107, 120;
}

.btn-slate {
  --bs-btn-color: var(--color-white);
  --bs-btn-bg: var(--color-slate);
  --bs-btn-border-color: var(--color-slate);
  --bs-btn-hover-color: var(--color-white);
  --bs-btn-hover-bg: var(--color-slate-hover);
  --bs-btn-hover-border-color: var(--color-slate-hover);
  --bs-btn-active-color: var(--color-white);
  --bs-btn-active-bg: var(--color-slate-hover);
  --bs-btn-active-border-color: var(--color-slate-hover);
  --bs-btn-focus-shadow-rgb: 46, 65, 76;
}

.btn-outline-white {
  --bs-btn-color: var(--color-white);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: var(--color-white);
  --bs-btn-hover-color: var(--color-dark-stone);
  --bs-btn-hover-bg: var(--color-white);
  --bs-btn-hover-border-color: var(--color-white);
  --bs-btn-active-color: var(--color-dark-stone);
  --bs-btn-active-bg: var(--color-white);
  --bs-btn-active-border-color: var(--color-white);
  --bs-btn-focus-shadow-rgb: 255, 255, 255;
}

.btn-outline-red {
  --bs-btn-color: var(--color-red);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: var(--color-red);
  --bs-btn-hover-color: var(--color-white);
  --bs-btn-hover-bg: var(--color-red);
  --bs-btn-hover-border-color: var(--color-red);
  --bs-btn-active-color: var(--color-white);
  --bs-btn-active-bg: var(--color-red);
  --bs-btn-active-border-color: var(--color-red);
  --bs-btn-focus-shadow-rgb: 192, 32, 33;
}

.btn-light-panel {
  --bs-btn-color: var(--color-dark-stone);
  --bs-btn-bg: var(--color-neutral-200);
  --bs-btn-border-color: var(--color-neutral-200);
  --bs-btn-hover-color: var(--color-dark-stone);
  --bs-btn-hover-bg: var(--color-neutral-300);
  --bs-btn-hover-border-color: var(--color-neutral-300);
  --bs-btn-active-color: var(--color-dark-stone);
  --bs-btn-active-bg: var(--color-neutral-300);
  --bs-btn-active-border-color: var(--color-neutral-300);
  --bs-btn-focus-shadow-rgb: 220, 223, 231;
}

/* Light fill with a RED label — Figma's inverse-primary for red bands, where a solid
   red button would disappear. Distinct from .btn-light-panel, which is the same fill
   with a dark-stone label and exists so a CTA inside a light card does NOT compete with
   that card's red one. Red on neutral-200 measures 4.54:1 — passing, but with little
   margin, so do not darken this fill. */
.btn-light-red {
  --bs-btn-color: var(--color-red);
  --bs-btn-bg: var(--color-neutral-200);
  --bs-btn-border-color: var(--color-neutral-200);
  --bs-btn-hover-color: var(--color-red);
  --bs-btn-hover-bg: var(--color-white);
  --bs-btn-hover-border-color: var(--color-white);
  --bs-btn-active-color: var(--color-red);
  --bs-btn-active-bg: var(--color-white);
  --bs-btn-active-border-color: var(--color-white);
  --bs-btn-focus-shadow-rgb: 220, 223, 231;
}

/* === Color Utility Classes === */
.text-red    { color: var(--color-red) !important; }
.bg-red      { background-color: var(--color-red) !important; }
.border-red  { border-color: var(--color-red) !important; }

.text-slate   { color: var(--color-slate) !important; }
.bg-slate     { background-color: var(--color-slate) !important; }
.border-slate { border-color: var(--color-slate) !important; }

.text-dark-stone   { color: var(--color-dark-stone) !important; }
.bg-dark-stone     { background-color: var(--color-dark-stone) !important; }
.border-dark-stone { border-color: var(--color-dark-stone) !important; }

.text-neutral-200 { color: var(--color-neutral-200) !important; }
.bg-neutral-200   { background-color: var(--color-neutral-200) !important; }
.text-neutral-300 { color: var(--color-neutral-300) !important; }
.text-neutral-500 { color: var(--color-neutral-500) !important; }
.text-neutral-600 { color: var(--color-neutral-600) !important; }
.text-neutral-700 { color: var(--color-neutral-700) !important; }
.bg-neutral-50    { background-color: var(--color-neutral-50) !important; }
.bg-neutral-700   { background-color: var(--color-neutral-700) !important; }

.bg-dark-00 { background-color: var(--color-dark-00) !important; }
.bg-dark-01 { background-color: var(--color-dark-01) !important; }
.bg-dark-02 { background-color: var(--color-dark-02) !important; }

.bg-gradient-hero        { background: var(--gradient-hero) !important; }
.bg-gradient-steel       { background: var(--gradient-steel) !important; }
.bg-gradient-section-dark{ background: var(--gradient-section-dark) !important; }
.bg-gradient-brands      { background: var(--gradient-brands) !important; }
.bg-gradient-our-story   { background: var(--gradient-our-story) !important; }
.bg-gradient-maint-plans { background: var(--gradient-maint-plans) !important; }
.bg-gradient-footer      { background: var(--gradient-footer) !important; }
.bg-gradient-red-fade    { background: var(--gradient-red-fade) !important; }

.shadow-light  { box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.05) !important; }
.shadow-medium { box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.20) !important; }

/* ============================================================================
   WCAG SAFETY NET — colour by parent surface
   ----------------------------------------------------------------------------
   Every surface PUBLISHES inherited custom properties; components CONSUME them.
   The NEAREST surface wins, so a red icon inside a white card that sits on a
   dark band correctly stays red — which is why the light component cards are
   registered in BOTH systems.

   Selectors use :where() to keep specificity at (0,0,0), so any explicit colour
   on the element still beats the default. Every consumer carries a fallback so
   an element outside every registered surface still renders on-brand.

   Ratios below are measured, not assumed (palette-contrast, WCAG 2.1 AA):
     white on red #C02021 .................. 6.05:1  PASS
     red #C02021 on white .................. 6.05:1  PASS  (no darkening needed)
     red #C02021 on any dark surface ....... 2.43-3.10:1  FAIL -> icons go white
     neutral-500 on dark-stone ............. 7.12:1  PASS
     neutral-500 on the maint-plans red .... 3.22:1  FAIL -> muted goes neutral-200
     neutral-600 on white .................. 3.16:1  FAIL -> never body text on light
   ============================================================================ */

/* --- LIGHT surfaces: brand red for links + accent icons ------------------- */
:where(
  body,
  .bg-white,
  .bg-neutral-50,
  .bg-neutral-200,
  .surface-light,
  .dropdown-menu,
  .cpa-mega-dropdown-group,
  .cpa-card-light,
  /* Paints its own neutral-50 fill and sets its own text colour, so it is safe on
     any band — but it must publish the LIGHT pairing too, or a link or .icon-context
     inside it inherits white from the dark section it now sits on (/maintenance-plans
     renders these tiers directly on a dark gradient band since 2026-08-10). */
  .cpa-maint-tier
) {
  --cpa-link: var(--color-red);
  --cpa-link-hover: var(--color-dark-stone);
  --cpa-icon: var(--color-red);
  --cpa-body: var(--color-dark-stone);
  --cpa-muted: var(--color-neutral-700);
}

/* --- DARK surfaces: white links + white accent icons ---------------------- */
:where(
  .bg-dark-00,
  .bg-dark-01,
  .bg-dark-02,
  .bg-dark-stone,
  .bg-slate,
  .bg-neutral-700,
  .cpa-panel,
  .cpa-about-card,
  .surface-dark,
  .menu-container,
  .offcanvas-body,
  .cpa-footer,
  .bg-gradient-steel,
  .bg-gradient-section-dark,
  .bg-gradient-brands,
  .bg-gradient-our-story,
  .bg-gradient-footer,
  .bg-gradient-hero
) {
  --cpa-link: var(--color-white);
  --cpa-link-hover: var(--color-neutral-300);
  --cpa-icon: var(--color-white);
  --cpa-body: var(--color-neutral-200);
  --cpa-muted: var(--color-neutral-500);
}

/* --- RED-DOMINANT surfaces --------------------------------------------------
   Brand red on brand red is invisible, and neutral-500 body copy drops to
   3.22:1 at the maint-plans red stop and 2.64:1 on flat red. Muted lifts to
   neutral-200 (5.53:1 at the worst stop) on these surfaces only. */
:where(
  .bg-red,
  .bg-gradient-red-fade,
  .bg-gradient-maint-plans,
  .surface-red
) {
  --cpa-link: var(--color-white);
  --cpa-link-hover: var(--color-neutral-200);
  --cpa-icon: var(--color-white);
  --cpa-body: var(--color-white);
  --cpa-muted: var(--color-neutral-200);
}

/* --- CONSUMERS -------------------------------------------------------------
   Links pick this up automatically — no class required. Buttons opt out; they
   carry their own foreground/background pair. */
a:where(:not(.btn)) {
  color: var(--cpa-link, var(--color-red));
}
a:where(:not(.btn)):where(:hover, :focus-visible, :active) {
  color: var(--cpa-link-hover, var(--color-dark-stone));
}

/* Accent icons consume the icon context. Apply .icon-context to any decorative
   or accent glyph that is reused across light and dark bands. */
.icon-context {
  color: var(--cpa-icon, var(--color-red));
}
.text-context       { color: var(--cpa-body, var(--color-dark-stone)); }
.text-context-muted { color: var(--cpa-muted, var(--color-neutral-700)); }

/* Light component card — paints its own light background and can nest inside a
   dark or red band (brand cards, financing cards). Registered as a light
   surface above so its contents flip back to the light pairing. */
.cpa-card-light {
  background-color: var(--color-neutral-200);
  color: var(--color-dark-stone);
  border-radius: var(--border-radius-standard);
}

/* === Links === */
a {
  text-decoration: none;
}

a.link-white  { color: var(--color-white); }
a.link-white:is(:hover, :focus, :active) { color: var(--color-red); }

a.link-red    { color: var(--color-red); }
a.link-red:is(:hover, :focus, :active) { color: var(--color-red-hover); }

a.link-dark-stone { color: var(--color-dark-stone); }
a.link-dark-stone:is(:hover, :focus, :active) { color: var(--color-red); }

a.link-neutral { color: var(--color-neutral-700); }
a.link-neutral:is(:hover, :focus, :active) { color: var(--color-dark-stone); }

/* === Icon stacked primitive ===
   Static icon-on-shape widget. Compose with a bg utility for the backing shape.
   Custom CSS only when the icon CHANGES on hover/active — otherwise use this. */
.icon-stacked {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  background: var(--color-red);
  color: var(--color-white);
  font-size: 1.25rem;
  flex-shrink: 0;
}
.icon-stacked.icon-stacked-lg { width: 4rem; height: 4rem; font-size: 1.5rem; }
.icon-stacked.icon-stacked-sm { width: 2.5rem; height: 2.5rem; font-size: 1rem; }
.icon-stacked.icon-stacked-slate { background: var(--color-slate); }
.icon-stacked.icon-stacked-square { border-radius: var(--border-radius-input); }

/* === Component Styles === */
.card {
  border-radius: var(--border-radius-standard);
  border: 0;
  background: transparent;
  --bs-card-border-radius: var(--border-radius-standard);
  --bs-card-inner-border-radius: var(--border-radius-standard);
}
.card-img,
.card-img-top,
.card-img-bottom {
  width: 100%;
  height: auto;
}
.card .card-body {
  padding: 1.5rem;
}
.card.h-100 {
  margin-bottom: 0;
}

/* Panel — the rounded container that groups a set of cards on a dark band.
   REPURPOSED 2026-08-05 (values, not name): Figma fills this SOLID Brand/Slate #2E414C.
   The original translucent white guess was written at 4d before any consumer existed and
   never matched the design; nothing had used it, so nothing breaks. White on slate
   measures 10.62:1.
   CONSOLIDATED at 6b with .cpa-opener-callout (the opener's Free-Second-Opinion box),
   which is the identical slate treatment and differed only in padding — Figma draws it
   at 28px against this one's 20px. One treatment, one override. */
.cpa-panel,
.cpa-opener-callout {
  background-color: var(--color-slate);
  border-radius: var(--border-radius-standard);
  padding: 1.25rem;
}
.cpa-opener-callout {
  padding: 1.75rem;   /* Figma 28px */
}

/* === Forms ===
   Every control carries a real <label> (visible or .visually-hidden) AND a
   format-hint placeholder. Placeholder contrast comes from the token below —
   browser-default placeholder grey fails AA. */
.form-control,
.form-select {
  min-height: var(--tap-target-min);
  border-radius: var(--border-radius-input);
  border: 1px solid var(--color-neutral-700);
  background-color: var(--color-neutral-700);
  color: var(--color-white);
  font-family: var(--font-body);
  font-size: 1rem;
  padding: 0.5rem 0.875rem;
}
.form-control:focus,
.form-select:focus {
  background-color: var(--color-neutral-700);
  border-color: var(--color-white);
  color: var(--color-white);
  box-shadow: 0 0 0 0.25rem rgba(192, 32, 33, 0.35);
}
/* Placeholder contrast comes from a token, never a browser default.
   Measured on the neutral-700 field background: neutral-50 = 4.78:1 PASS.
   neutral-200 (4.09:1) and neutral-300 (3.47:1) both FAIL — do not use them here. */
.form-control::placeholder {
  color: var(--color-neutral-50);
  opacity: 1;
}
.form-label {
  font-family: var(--font-body);
  font-weight: var(--font-weight-semibold);
  font-size: 0.875rem;
  margin-bottom: 0.25rem;
}
.form-check-input {
  width: 1.5rem;
  height: 1.5rem;
  margin-top: 0.125rem;
  background-color: transparent;
  border: 2px solid var(--color-white);
}
.form-check-input:checked {
  background-color: var(--color-red);
  border-color: var(--color-red);
}
/* Checkbox/radio labels are tap targets too. Column flex (not inline-flex + centre)
   so a long multi-line disclosure stacks normally while a short label still fills 40px. */
.form-check-label {
  min-height: var(--tap-target-min);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-left: 0.5rem;
}

/* SMS consent block on the shared short form */
.sf-sms-consent {
  display: flex;
  align-items: flex-start;
  gap: 0.25rem;
}
.sf-sms-consent .form-check-input {
  flex-shrink: 0;
  margin-top: 0.5rem;
}
.sf-sms-consent-lead {
  font-size: 0.875rem;
  line-height: 1.375rem;
}
/* Figma Paragraph/XSmall/Regular — 12/20 */
.sf-sms-consent-legal {
  font-size: 0.75rem;
  line-height: 1.25rem;
  color: var(--cpa-muted, var(--color-neutral-500));
  margin-top: 0.25rem;
}
/* Required marker — explained in text, never colour alone */
.required-marker {
  color: var(--color-red);
  font-weight: var(--font-weight-bold);
}

/* === Platform contact form (contact.php only) ============================
   /contact renders the DEALER-PLATFORM form, contact_bootstrap($dealer_id),
   not the shared short form. That is not a style choice: small_contact()
   POSTs to /contact.php, and contact_bootstrap() is what receives and
   processes that submission. Swapping it for the short form breaks every
   other page's form silently — the post lands on a page that no longer
   handles it.

   That include ships its own markup: a .form-row grid predating Bootstrap 5
   (which dropped .form-row entirely, so the row collapses without this) plus
   its own <label> per control alongside a placeholder.

   Scoped to .cpa-contact-form so none of it can leak onto the short form,
   which carries Figma's own column structure and VISIBLE labels.

   These rules live here rather than in a <style> block on contact.php because
   the project forbids inline CSS. Same treatment as the jay-gill, central-air
   and VCS builds.

   ⚠️ contact_bootstrap() lives in /var/www/www_includes and CANNOT be rendered
      on a local php -S server, so this block is ported from the husk's own
      inline <style> rather than measured. VERIFY ON THE SERVER.            */
.cpa-contact-form .form-row {
  --bs-gutter-x: 1.5rem;
  --bs-gutter-y: 0;
  display: flex;
  flex-wrap: wrap;
  margin-top: calc(-1 * var(--bs-gutter-y));
  margin-right: calc(-0.5 * var(--bs-gutter-x));
  margin-left: calc(-0.5 * var(--bs-gutter-x));
}
.cpa-contact-form .form-row > .col {
  padding-right: calc(var(--bs-gutter-x) * 0.5);
  padding-left: calc(var(--bs-gutter-x) * 0.5);
  margin-top: var(--bs-gutter-y);
  /* The card is only ~456px wide at lg, and .col is `flex: 1 0 0%`, so two
     controls in one row would squash to ~200px each. .form-row already wraps —
     a floor on the column width is all that is needed to drop them to one-up
     instead. Field COUNT per row is decided by the platform, not by us, so a
     width floor is the only safe lever here. */
  min-width: 12rem;
}
.cpa-contact-form .form-row:last-of-type {
  margin-left: 0;
}
/* The platform ships a <label> AND a placeholder for every control, which reads
   as duplicated. The husk hid the label with `display: none` — that also strips
   it from the accessibility tree and can leave an input with NO accessible name.
   Clip it instead: identical visual result, label still announced.
   .form-check-label is excluded — a consent checkbox needs its visible text. */
.cpa-contact-form label:not(.form-check-label) {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* === Accordion === */
.accordion {
  background: transparent;
  border: none;
}
.accordion-item,
.accordion-header,
.accordion-button {
  background: transparent;
  border: none;
}
.accordion-button {
  font-family: var(--font-body);
  font-weight: var(--font-weight-bold);
  font-size: 1rem;
  min-height: var(--tap-target-min);
  box-shadow: none !important;
}
.accordion-button:not(.collapsed) {
  box-shadow: none;
  background: transparent;
}
/* Bootstrap's default chevron is hidden — icons are supplied inline in markup */
.accordion-button::after {
  display: none !important;
}

/* === Nav Tabs === */
.nav-tabs .nav-link {
  color: var(--color-neutral-600);
  font-family: var(--font-heading);
  font-weight: var(--font-weight-black);
  text-transform: uppercase;
  min-height: var(--tap-target-min);
}
.nav-tabs .nav-link:is(:hover, :focus) {
  color: var(--color-red);
  border-color: transparent;
}
.nav-tabs .nav-link.active {
  color: var(--color-red);
  background: transparent;
  border-color: transparent transparent var(--color-red);
}

/* === Utility === */
em,
u {
  font-style: inherit !important;
  text-decoration: inherit !important;
}

/* === Menu === */
.menu-container {
  background: var(--gradient-nav);
  box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.25);
  transition: box-shadow var(--transition-standard);
}
/* Lift off the page once scrolled — the bar earns its shadow instead of always wearing it. */
.menu-container.shrink-menu {
  box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.55);
}

/* The logo spans the FULL nav height; the utility row and the nav row stack beside it.
   That is why the divider rule starts to the right of the logo rather than running the
   whole container width — it separates the two stacked rows only. */
.cpa-nav-stack {
  min-width: 0;
}
/* The divider only exists where there are two rows to divide — below xxl the utility
   row is hidden, so the rule would be a stray line above the hamburger. */
@media (min-width: 1400px) {
  :root {
    --menu-height: 8.375rem;        /* 134px — Figma nav height with both rows */
    --menu-height-shrink: 5.5rem;
  }
  /* Nav row is a RULE, not a band — no fill of its own, just the divider. The padding
     keeps that rule off the top of the CTA buttons instead of resting on them. */
  .cpa-nav-row {
    border-top: 1px solid rgba(255, 255, 255, 0.18);
    background: none;
    padding-top: 1rem;
    padding-bottom: 0.5rem;
  }
  .cpa-utility-bar {
    min-height: var(--utility-bar-height);
    padding-bottom: 0.25rem;
  }
}

/* Height only. This class sits on a Bootstrap .row, so it must NOT set display or
   flex-direction — doing so overrides the grid and stacks the columns. Vertical
   centring comes from the row's own .align-items-center. */
.cpa-menu-padding {
  min-height: var(--menu-height);
  transition: min-height var(--transition-standard);
}
.shrink-menu .cpa-menu-padding {
  min-height: var(--menu-height-shrink);
}

.cpa-logo-desktop {
  width: auto;
  height: 80px;
  transition: height var(--transition-standard);
}
.shrink-menu .cpa-logo-desktop {
  height: 56px;
}

/* Utility bar (top nav row) */
.cpa-utility-bar {
  min-height: var(--utility-bar-height);
  font-size: 0.8rem;
}
.cpa-utility-link {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-black);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-white);
  white-space: nowrap;
  min-height: var(--tap-target-min);
  display: inline-flex;
  align-items: center;
}
.cpa-utility-link:is(:hover, :focus) {
  color: var(--color-red);
}

/* Desktop nav links.
   Padding is set through Bootstrap's own --bs-nav-link-padding-x rather than an
   !important override. The tighter value matches Figma's nav spacing AND keeps the
   4 links + 3 CTAs inside the container at the xl breakpoint. */
.cpa-desktop-nav {
  /* Back at Figma's 0.25rem. It was briefly cut to 0.125rem on 2026-08-14 to buy ~16px when
     the client's "Who We Serve" rename left only 2.5px of MEASURED clearance between the logo
     and the first nav label. That compromise was reverted once Financing moved out of this row
     into the utility bar, which freed ~148px — the real fix, so the type spacing did not have
     to pay for it.

     The container is a fixed 1320px at every viewport >= 1400px, so this row renders identically
     at all desktop widths: verify once, correct everywhere. The row is cut for FOUR links plus
     TWO CTAs. Adding a fifth link, a third CTA, or a longer label puts it back into the logo —
     see the note in includes/menu.php. */
  --bs-nav-link-padding-x: 0.25rem;
}
.cpa-desktop-nav .nav-link {
  position: relative; /* anchors the ::before hover rule below */
  font-family: var(--font-heading);
  font-weight: var(--font-weight-black);
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--color-white);
  white-space: nowrap;
  min-height: var(--tap-target-min);
  display: inline-flex;
  align-items: center;
}
/* Hover/open state: a brand-red rule wipes out from the centre and the caret turns with
   it. Colour alone reads as a default Bootstrap navbar; the moving rule is what stops it
   feeling out-of-the-box. Honours prefers-reduced-motion via the global transition reset. */
.cpa-desktop-nav .nav-link::before {
  content: '';
  position: absolute;
  left: 50%;
  right: 50%;
  bottom: 0.35rem;
  height: 2px;
  background: var(--color-red);
  transition: left var(--transition-standard), right var(--transition-standard);
}
.cpa-desktop-nav .nav-link:is(:hover, :focus-visible, :active, .show, [aria-expanded='true'])::before {
  left: 0.25rem;
  right: 0.25rem;
}
.cpa-desktop-nav .nav-link:is(:hover, :active, .show, [aria-expanded='true']) {
  color: var(--color-white);
}
.cpa-desktop-nav .nav-link.has-dropdown::after {
  transition: transform var(--transition-standard), color var(--transition-standard);
}
.cpa-desktop-nav .nav-link.has-dropdown:is(.show, [aria-expanded='true'])::after {
  transform: rotate(180deg);
}

/* Dropdown carets — FA glyph replaces Bootstrap's CSS triangle */
.dropdown-toggle:not(.has-dropdown)::after {
  border: 0 !important;
  margin: 0 !important;
}
.has-dropdown {
  align-items: center;
  gap: 0.25em;
}
.has-dropdown::after {
  content: '\f107';
  font-family: 'Font Awesome 7 Pro';
  font-weight: 400;
  border: 0;
  margin-left: 0.4em;
  font-size: inherit;
  vertical-align: middle;
  color: var(--color-red);
}
/* Tap-target floor applies to EVERY dropdown item, not just those under a
   .nav-item — Bootstrap's default is 32px, and a mega-menu or standalone
   dropdown would otherwise sit under the floor. Verified 40px in the harness.
   The transition and the hover padding-nudge were a SECOND .dropdown-item block
   down in the mega-dropdown section until 6b; merged here so the item has one
   declaration. Note the two hover rules keep DIFFERENT selector lists on purpose:
   :active takes the colour change but not the padding nudge. */
.dropdown-item {
  min-height: var(--tap-target-min);
  display: flex;
  align-items: center;
  border-radius: var(--border-radius-input);
  color: var(--color-dark-stone);
  font-family: var(--font-body);
  font-weight: var(--font-weight-semibold);
  font-size: 1rem;
  transition: background-color var(--transition-standard), color var(--transition-standard),
              padding-left var(--transition-standard);
}
.dropdown-item:is(:hover, :active, :focus) {
  background-color: var(--color-neutral-50);
  color: var(--color-red) !important;
}
/* Dropdown links nudge toward their destination on hover */
.dropdown-item:is(:hover, :focus) {
  padding-left: 1.25rem;
}

/* Mega dropdown — spans the navbar container rather than hanging off the <li>.
   The <li> carries .position-static so the panel escapes it; anchoring is then
   done by making the navbar's own .container the positioned ancestor. Without
   this, `w-100` resolves against .menu-container (viewport width) and the panel
   overruns the right edge. The navbar's .container carries Bootstrap's
   .position-relative utility in menu.php to provide that anchor. */
.dropdown-menu.mega-dropdown {
  left: 0;
  right: 0;
  width: auto;
  max-width: none;
  padding: 1rem;
}

.cpa-mega-dropdown-group {
  position: relative;
  isolation: isolate;
  padding: 1.25rem;
  border-radius: var(--border-radius-standard);
  background-color: var(--color-neutral-50);
  border-left: 3px solid transparent;
  transition: background-color var(--transition-standard), border-color var(--transition-standard),
              transform var(--transition-standard), box-shadow var(--transition-standard);
}
.cpa-mega-dropdown-group:is(:hover, :focus-within) {
  border-left-color: var(--color-red);
  background-color: var(--color-white);
  transform: translateY(-2px);
  box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.2);
}
/* Silo heading + its icon badge. The icon mirrors the glyph the Figma services section
   uses for that same service, so the nav and the page speak the same iconography. */
.cpa-mega-heading {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  color: var(--color-dark-stone);
  transition: color var(--transition-standard);
}
.cpa-mega-dropdown-group:is(:hover, :focus-within) .cpa-mega-heading {
  color: var(--color-red);
}
.cpa-mega-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  flex-shrink: 0;
  border-radius: var(--border-radius-input);
  background: var(--color-neutral-200);
  color: var(--color-red);
  font-size: 0.875rem;
  transition: background-color var(--transition-standard), color var(--transition-standard);
}
.cpa-mega-dropdown-group:is(:hover, :focus-within) .cpa-mega-icon {
  background: var(--color-red);
  color: var(--color-white);
}
/* EVERY dropdown is a mega panel — the destination-only ones (Indoor Air Quality,
   Other Services) use a card whose whole body is the link, stacked icon over label,
   so a 4-card row fills the full-width panel the same way the 6-card silo grid does. */
.cpa-mega-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
  min-height: var(--tap-target-min);
  text-decoration: none;
}
.cpa-mega-heading-text {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  font-size: 0.9375rem;
  line-height: 1.25;
  color: var(--color-dark-stone);
  transition: color var(--transition-standard);
}
.cpa-mega-card:is(:hover, :focus-visible) .cpa-mega-heading-text {
  color: var(--color-red);
}

/* === Home Hero ===
   Full-bleed media behind a scrim, content on top. The hero is the one section
   that carries padding-TOP — it has to clear the fixed navigation. */
.cpa-hero {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 640px;
  padding-top: calc(var(--menu-height) + 3rem);
  padding-bottom: 3rem;
}
/* Media + scrim fill the section. The <video> and the mobile poster share this
   box so they can be swapped by display utility without re-stating geometry. */
.cpa-hero-media,
.cpa-hero-scrim {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.cpa-hero-media {
  object-fit: cover;
  z-index: 0;
}
/* The still behind the hero video, carried by TWO elements rather than the video's
   own poster attribute.

   ⚠️ A display:none <video> STILL FETCHES ITS poster (measured 2026-08-17 with a
      standalone probe). So a poster attribute on the video means every phone pays
      for the 1920 file it never displays — which is exactly what the d-none was
      written to prevent for the mp4. The attribute is therefore GONE from the
      markup, and the still is painted here instead:

        below lg   this div paints the 1280 rendition (the div is d-lg-none)
        at lg+     .cpa-hero paints the 1920 behind the <video>, which has no
                   frames until custom.js attaches its src and so shows it through

   Below lg the hero is a fixed 640px tall, so the still always renders at
   640 x 16/9 = 1138 CSS px wide NO MATTER the viewport width — 390 and 991 need
   the same file. That is why there is one mobile rendition and not a ladder: an
   800w file would be a 1.42x UPSCALE at every width below lg.

   Both files are preloaded in doc-header.php under the SAME media conditions as
   these two rules. Change a breakpoint here and you must change it there, or the
   page downloads both stills. */
.cpa-hero-poster {
  background: url('/img/cpa-hero-poster-1280-720.webp') center center / cover no-repeat;
}
@media (min-width: 992px) {
  .cpa-hero {
    background: url('/img/cpa-hero-poster.webp') center center / cover no-repeat;
  }
}
/* The scrim carries its own background rather than wearing the .bg-gradient-hero
   utility, so the mobile variant can override it in a media query without needing
   to out-!important a utility class. */
.cpa-hero-scrim {
  z-index: 1;
  background: var(--gradient-hero-mobile);
}
@media (min-width: 992px) {
  .cpa-hero-scrim {
    background: var(--gradient-hero);
  }
}
.cpa-hero > .container {
  z-index: 2;
}
/* Live Elfsight reviews badge. The box is reserved so the trust-badge row does not
   reflow when the widget mounts (third-party embeds are a classic CLS source), and so
   the row still reads correctly if the widget is blocked. */
.cpa-hero-reviews {
  display: flex;
  align-items: center;
  min-width: 200px;
  min-height: 60px;
}
/* Figma's hero is 784px tall at the 1600px frame. The nav offset needs no special case
   here — --menu-height already grows at xxl to cover both stacked rows. */
@media (min-width: 1400px) {
  .cpa-hero {
    min-height: 784px;
  }
}
/* Compact hero — /contact only. That page is a single-purpose conversion page, so
   the form has to be reachable without hunting for it: the hero drops the 640px
   (784px at xxl) height floor and tightens its vertical padding.

   ⚠️ THE NAV CLEARANCE IS NOT REDUCED. padding-top still resolves to --menu-height
      plus a gap; trim that term and the H1 slides under the fixed nav.

   Two-class selector on purpose — it has to beat the 1400px min-height rule above
   regardless of where this block sits in the file. */
.cpa-hero.cpa-hero-compact {
  min-height: 0;
  padding-top: calc(var(--menu-height) + 1.5rem);
  padding-bottom: 2.5rem;
}

/* === Home Opener ===
   NOTE: this section deliberately has NO overflow-hidden — the advantage bar straddles
   the boundary with the hero and would be clipped. That also rules out g-5 gutters here
   (a -1.5rem row margin inside a 0.75rem-padded container overflows without clipping),
   so the inner rows use g-4. */
.cpa-opener {
  background: var(--gradient-opener);
}

/* Advantage bar — sits half over the hero, half in the opener. z-index clears the
   hero's own layers (media 0, scrim 1, container 2). */
.cpa-advantage-bar {
  position: relative;
  z-index: 3;
  margin-bottom: 3rem;
}
/* Only straddle once the six cards are a single row. Below xl the bar wraps to two or
   three rows, and pulling that whole block up would cover the hero's copy. */
@media (min-width: 1200px) {
  .cpa-advantage-bar {
    margin-top: -58px; /* half the 116px card height */
  }
}
.cpa-advantage {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 116px;
  padding: 1.25rem;
  border-radius: var(--border-radius-card);
  background-color: var(--color-dark-stone);
  text-align: center;
  transition: background-color var(--transition-standard),
              transform var(--transition-standard),
              box-shadow var(--transition-standard);
}
.cpa-advantage:hover {
  background-color: var(--color-red);
  transform: translateY(-6px);
  box-shadow: 0 0.75rem 1.5rem rgba(0, 0, 0, 0.35);
}
.cpa-advantage-icon {
  font-size: 2.25rem;   /* Figma 36px */
  line-height: 1.5rem;
  color: var(--color-white);
  transition: transform var(--transition-standard);
}
.cpa-advantage:hover .cpa-advantage-icon {
  transform: scale(1.18);
}
.cpa-advantage-label {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-black);
  font-size: 0.75rem;
  line-height: 0.9375rem;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: var(--color-white);
}

/* The opener's callout panel is .cpa-panel's treatment — declared with it above. */
/* Contact card (Figma dark stone #0F222E) */
.cpa-opener-form {
  background-color: var(--color-dark-stone);
  border-radius: var(--border-radius-standard);
  padding: 1.75rem;
}
/* The short-form include now carries Figma's own column structure, so no per-card
   grid overrides are needed here. Only the textarea height is set — Figma's message
   field is 272px tall. */
.sf-message {
  min-height: 11rem;
}
/* Required-field key. The asterisk is explained in text, never colour alone. */
.sf-required-note {
  font-size: 0.75rem;
  line-height: 1.25rem;
  margin-top: 0.375rem;
  color: var(--cpa-muted, var(--color-neutral-500));
}
/* ServiceTitan SMS opt-in embed — responsive shell around a fixed-size iframe */
.cpa-optin-embed {
  width: 100%;
  border: 0;
  display: block;
}

/* === Home Financing ===
   Full-bleed photo band. The red wash is BAKED INTO the asset — across all three
   variants the brightest pixel anywhere measures 5.43:1 against white and the copy
   zone 6.42:1 — so NO CSS overlay is added here. That is the standard baked-in
   guardrail; the home hero is this project's one exception to it, not the rule.

   DEVIATION, padding-bottom-only rule: this band carries padding-TOP too, on the
   same basis as the hero. The rule exists so adjacent section gaps don't double,
   and a band that paints its own full-bleed background cannot double with its
   neighbour — the top padding is an inset INSIDE the paint, not a gap between
   sections. Without it the overline sits flush on the photo's top edge.
   Figma: 100px both sides at the 1600px frame. */
.cpa-financing {
  padding-top: 3.5rem;
  padding-bottom: 3.5rem;
}
@media (min-width: 992px) {
  .cpa-financing {
    padding-top: 6.25rem;
    padding-bottom: 6.25rem;
  }
}

/* Financing partner card. Figma fills these with Neutral/300 — one step darker than
   the Neutral/200 that .cpa-card-light carries for the brand cards (both nodes were
   checked), so this is a separate card, not a variant of that one.
   It deliberately does NOT join the standard light surfaces: measured on #CCCED6,
   brand red drops to 3.85:1 and neutral-700 muted copy to 3.47:1 — both AA failures
   for body text — and inheriting the parent red band would put white at 1.57:1.
   So it publishes its own pairing: dark-stone 10.37:1, slate 6.76:1 for muted.
   Figma's black body copy measures 13.37:1, and the red Wells Fargo wordmark clears
   its 3:1 large-text threshold at 20px Bold. */
.cpa-financing-card {
  --cpa-link: var(--color-dark-stone);
  --cpa-link-hover: var(--color-slate);
  --cpa-icon: var(--color-dark-stone);
  --cpa-body: var(--color-black);
  --cpa-muted: var(--color-slate);
  background-color: var(--color-neutral-300);
  color: var(--color-black);
  border-radius: var(--border-radius-card);
  padding: 1.25rem;
}

/* === Home Services tabs ===
   Paints its own dark-stone band, so it carries its own top padding — the same
   full-bleed exception documented on .cpa-financing and the hero. Figma: 80px. */
.cpa-services {
  padding-top: 5rem;
  padding-bottom: 5rem;
}

/* Tab strip — Figma stacks a 36px glyph over a Label/Small caption, gives every tab an
   equal flex share, and rules them off with hairline dividers. */
.cpa-service-tabs {
  border-bottom: 0;
  /* HORIZONTAL SCROLL ONLY. The markup carries .overflow-x-auto so the strip can scroll
     sideways when eight tabs don't fit; per CSS, an overflow-y of `visible` COMPUTES to
     `auto` as soon as the other axis is not visible, so the strip was also a vertical
     scroller. It had exactly 1px to scroll (see the margin-bottom note below), which macOS
     renders as an overlay scrollbar the moment the pointer enters the strip. Measured
     2026-08-10: clientHeight 75, scrollHeight 76. Both fixes ship — this one guarantees the
     axis can never scroll again whatever a future child does. */
  overflow-y: hidden;
}
.cpa-service-tabs .nav-item {
  flex: 1 0 0;
  min-width: 7rem;
}
.cpa-service-tabs .nav-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 0.5rem;
  width: 100%;
  padding: 0.625rem 1rem 1.25rem;
  border: 0;
  border-right: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 0;
  font-size: 0.75rem;
  line-height: 0.875rem;
  text-align: center;
  /* Bootstrap's .nav-tabs .nav-link sets margin-bottom: -1px so the active tab's border
     laps over the strip's bottom border. This strip has border-bottom: 0, so that pull-up
     had nothing to overlap — it just made every anchor 1px taller than the <ul> that
     scrolls it (76 vs 75) and gave the strip a 1px vertical scroll range. Zeroed at the
     source so nothing is clipped by the overflow-y: hidden above. */
  margin-bottom: 0;
}
.cpa-service-tabs .nav-item:last-child .nav-link {
  border-right: 0;
}
.cpa-service-tabs .nav-link i {
  font-size: 2.25rem;   /* Figma 36px */
  line-height: 1.5rem;
}
/* ACTIVE-STATE OVERRIDE — deliberate, do not "restore" to match the global .nav-tabs.
   That rule turns the active tab brand red, which measures only 2.69:1 on this
   dark-stone band (AA fail). Figma's inactive neutral-600 is 5.16:1 and white is
   16.30:1, so the active tab goes white and the red rule beneath it carries the accent.
   The global red active state stays correct on light surfaces. */
.cpa-service-tabs .nav-link.active {
  color: var(--color-white);
  background: transparent;
  box-shadow: inset 0 -2px 0 0 var(--color-red);
}
.cpa-service-tabs .nav-link:is(:hover, :focus-visible) {
  color: var(--color-white);
}

/* One rounded card split into a photo half and a copy half. The radius lives on the
   pane so both halves are clipped by it and the mobile stack rounds correctly. */
.cpa-service-pane {
  border-radius: var(--border-radius-card);
  overflow: hidden;
}
/* All eight panels share one height so switching tabs doesn't shift the page. Without
   this the card measured 340-436px depending on how long that service's copy runs — a
   96px jump on every tab click. 478px is Figma's own card height and clears the tallest
   panel, and it's a FLOOR not a cap, so longer copy still grows rather than clipping.

   The floor goes on the ROW, not the pane. Put on the pane it holds the PAGE steady
   (which is why CLS measured clean) but leaves the visible card free to swing the full
   340-436px inside it — the jump is still there, just no longer pushing the page down.
   Measured 2026-08-05: pane 478 on all eight, row 340/362/362/362/388/402/412/436. */
@media (min-width: 992px) {
  .cpa-service-pane > .row {
    min-height: 478px;
  }
}
.cpa-service-panel {
  background: var(--gradient-service-panel);
}
/* Fact card over the rollover photo. The slate is 88% opaque, so white clears 8.91:1
   even against the brightest pixel measured across the rollover set. The photos ship
   with their slate scrim already baked in — no CSS overlay. */
.cpa-service-minicard {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  background: rgba(46, 65, 76, 0.88);
  border-radius: var(--border-radius-panel);
  padding: 1.25rem 0.625rem;
}
.cpa-service-minicard i {
  font-size: 2.25rem;   /* Figma 36px */
  line-height: 1.5rem;
}

/* === Home Brands ===
   Paints its own gradient band, so it carries its own top padding — the documented
   full-bleed exception. Figma: 80px. */
.cpa-brands {
  padding-top: 5rem;
  padding-bottom: 5rem;
}
/* Figma gives these cards a 16px radius rather than the 20px .cpa-card-light carries,
   and clips the photo flush to the card edge. .cpa-card-light still supplies the
   neutral-200 fill and — importantly — the light-surface registration, which is what
   flips the black card copy back to dark-on-light inside this dark band. */
.cpa-brand-card {
  border-radius: var(--border-radius-panel);
  overflow: hidden;
}
.cpa-brand-card-img {
  width: 100%;
  height: 200px;      /* Figma card image height */
  object-fit: cover;
}
/* Brand marks share one baseline height so three cards of differently-shaped logos
   still line their body copy up. */
.cpa-brand-logos img {
  max-height: 55px;
  width: auto;
  max-width: 100%;
}

/* === Home Reviews ===
   Paints its own full-bleed photo band, so it carries its own top padding — the
   documented exception. Figma: 80px. */
.cpa-reviews {
  padding-top: 5rem;
  padding-bottom: 5rem;
}
/* Space reserved for the live Elfsight reviews widget. Third-party embeds mount late, so
   without a floor the CTAs below jump when it arrives. 280px is MEASURED, not guessed:
   the mounted widget settles at 277px at both 1600 and 390 — Elfsight's carousel handles
   its own responsiveness and holds one height. A 420px guess left 143px of dead space.
   See .cpa-hero-reviews for the same pattern and the never-class-the-widget-div gotcha. */
.cpa-reviews-widget {
  min-height: 280px;
}

/* === Home Award ===
   Paints its own dark-stone band, so it carries its own top padding. Figma: 80px. */
.cpa-award {
  padding-top: 5rem;
  padding-bottom: 5rem;
}
/* The white American Standard mark is a single-size asset; cap it rather than letting
   the native 900px width blow out the column. */
.cpa-award-logo {
  width: 284px;
  max-width: 100%;
  height: auto;
}
/* Bootstrap's .ratio handles the sizing; these only clip the player to Figma's radius.
   .cpa-video-frame (the local body-video players in cpa-block-video.php) shares the rule
   rather than getting its own, so the YouTube embed and the local players are one component
   with one treatment instead of two near-identical ones that can drift apart. */
.cpa-award-video,
.cpa-video-frame {
  border-radius: var(--border-radius-standard);
  overflow: hidden;
}
/* A poster is object-fit:contain by default, which letterboxes it inside the 16:9 frame and
   leaves black bars against the band. The videos are all 16:9, so cover fills it exactly. */
.cpa-video-frame video {
  object-fit: cover;
  background-color: var(--color-slate);
}

/* === Home About ===
   Paints its own gradient band, so it carries its own top padding. Figma: 80px. */
.cpa-about {
  padding-top: 5rem;
  padding-bottom: 5rem;
}
/* Figma lays a 10% dark-stone wash over this photo. It is the ONE body image in the
   build that is NOT pre-treated (brightest pixel measures pure white), so unlike the
   financing/services/review art this overlay really is missing and has to be added.
   No copy sits on it — the wash is tonal, not a legibility scrim. */
.cpa-about-media {
  position: relative;
  border-radius: var(--border-radius-standard);
  overflow: hidden;
}
.cpa-about-media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(18, 39, 52, 0.1);
}
/* Credential card. Fills brand red on hover, matching .cpa-advantage in the opener so the
   two advantage families read as one system. Radius matches the .cpa-panel it sits in.
   The fill goes on the CARD, not the .col — a .col carries the row's gutter as padding,
   so painting it would bleed the red into the gap and butt adjacent cards together.
   White copy and icon hold at 6.05:1 on red (10.62:1 on the resting slate). */
.cpa-about-card {
  /* PAINTS ITS OWN SLATE 2026-08-10. The card used to be transparent and borrowed the fill
     from the .cpa-panel it sat in, which meant it could not exist outside one. Sub-page §4
     Why Us now drops that wrapper so each advantage is its own card, so the fill moved here.
     A NO-OP everywhere the card still sits in a panel — slate on slate — which is home §8
     About and cpa-block-service-area-detail. Radius deliberately left at the 20px standard
     rather than dropped to the 16px tile radius: that would have been a visible change to
     the home page, which was not part of the request. */
  background-color: var(--color-slate);
  border-radius: var(--border-radius-standard);
  transition: background-color var(--transition-standard);
}
.cpa-about-card:hover {
  background-color: var(--color-red);
}

/* === Home Maintenance ===
   Paints its own gradient band, so it carries its own top padding. Figma: 80px. */
.cpa-maint {
  padding-top: 5rem;
  padding-bottom: 5rem;
}
.cpa-maint-card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-height: 498px;   /* Figma card height */
  border-radius: var(--border-radius-standard);
}
.cpa-maint-media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* ADDITIONAL scrim, DERIVED not eyeballed. These photos already ship darkened, but the
   residential one still measured only 4.39:1 against white in the heading zone — an AA
   FAIL — while the commercial one passed at 5.57:1. Sibling cards must share one
   treatment, so both get 20% dark-stone: worst case across all SIX variants becomes
   5.62:1. 15% would also clear (5.30:1); 20% buys margin at negligible cost on photos
   this dark. Re-measure before softening, and before swapping either photo. */
.cpa-maint-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(18, 39, 52, 0.2);
}
.cpa-maint-body {
  position: relative;
  padding: 2.5rem 1.875rem;   /* Figma py-40 px-30 */
}
.cpa-maint-icon {
  font-size: 1.5rem;          /* Figma 24px */
}
/* Dialog trigger. Carries a visible label as well as the chevron — a bare glyph is a weak
   signal that something opens, and this is now a dialog rather than an inline expand. */
.cpa-maint-toggle {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  align-self: center;
  margin-top: auto;
  min-height: var(--tap-target-min);
  min-width: var(--tap-target-min);
  padding: 0.5rem 1.5rem;
  border: 0;
  background: none;
  color: var(--color-neutral-300);
  font-family: var(--font-heading);
  font-weight: var(--font-weight-black);
  text-transform: uppercase;
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  transition: color var(--transition-standard), transform var(--transition-standard);
}
.cpa-maint-toggle i {
  font-size: 2.25rem;         /* Figma 48px glyph, trimmed to sit with the label */
  line-height: 1;
}
.cpa-maint-card:is(:hover, :focus-within) .cpa-maint-toggle {
  color: var(--color-white);
  transform: translateY(-4px);
}
/* Plan detail dialog.
   REPLACES the in-card reveal (client decision 2026-08-05). Figma prototypes a panel that
   slides up over the photo on hover; that was built and then retired for two measured
   reasons. (1) Below lg it had to expand in flow, which grew the card 498 -> 1213px and
   pushed everything beneath it down the page. (2) Stacking the three tiers into one panel
   — which reads far better than three ~200px columns — makes the panel ~700px, taller than
   the 498px card, so the overlay could no longer fit it at ANY breakpoint.
   A Bootstrap modal is one mechanism everywhere and brings Esc, backdrop dismissal, focus
   trapping and scroll lock with it. The card keeps a hover affordance on the chevron. */
.cpa-maint-modal {
  border: 0;
  border-radius: var(--border-radius-standard);
}
.cpa-maint-modal .modal-header {
  border-bottom: 1px solid var(--color-neutral-200);
}
.cpa-maint-tier {
  display: flex;
  flex-direction: column;
  background-color: var(--color-neutral-50);
  border-radius: var(--border-radius-panel);
  padding: 1.25rem;
  color: var(--color-dark-00);
}
/* Stacked tiers read as one comparison rather than three cards, so they share a rhythm. */
.cpa-maint-tier + .cpa-maint-tier {
  margin-top: 1rem;
}
.cpa-maint-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.cpa-maint-list li {
  display: flex;
  gap: 0.5rem;
  font-size: 0.8125rem;
  line-height: 1.25rem;
  margin-bottom: 0.125rem;
}
.cpa-maint-list li i {
  color: var(--color-slate);
  flex-shrink: 0;
  line-height: 1.25rem;
}
.cpa-maint-list-lead {
  font-style: italic;
}
.cpa-maint-list-lg li {
  font-size: 1rem;
  line-height: 1.5rem;
  font-weight: var(--font-weight-semibold);
}

/* === Home Service Area ===
   The live map is the section background, so this band carries its own top padding.
   Figma: 80px, 893px tall. */
.cpa-service-area {
  padding-bottom: 5rem;
}
/* Below lg the panel is full width, so a map BEHIND it was 94% hidden and the Pensacola
   marker rendered at left:-15px — half off the edge. Stacked instead: the map gets its own
   full-bleed band above the panel, where it is actually usable. At lg+ it returns to being
   the section background with the panel floated over its right side.
   The dark-stone fill is the no-JS / blocked-library fallback: if Leaflet never initialises
   this stays a flat dark band and the location list still carries the information. */
.cpa-service-map {
  position: relative;
  width: 100%;
  height: 320px;
  background-color: var(--color-dark-stone);
  z-index: 0;
}
@media (min-width: 992px) {
  .cpa-service-area {
    padding-top: 5rem;
    min-height: 893px;
    display: flex;
    align-items: center;
  }
  .cpa-service-map {
    position: absolute;
    inset: 0;
    height: auto;
  }
}
/* Leaflet ships 30x30 zoom buttons — under the 40x40 tap floor, and they are real
   interactive controls on our page, not decoration. Library chrome still has to clear the
   guardrail.
   SPECIFICITY NOTE: Leaflet's own `.leaflet-touch .leaflet-bar a` is (0,2,1) and its
   stylesheet loads AFTER globals.css, so a matching (0,2,1) selector here loses the tie
   and silently does nothing. Naming both control classes takes this to (0,3,1) and wins
   without needing !important. */
.cpa-service-map .leaflet-control-zoom a.leaflet-control-zoom-in,
.cpa-service-map .leaflet-control-zoom a.leaflet-control-zoom-out {
  width: var(--tap-target-min);
  height: var(--tap-target-min);
  line-height: var(--tap-target-min);
  font-size: 1.25rem;
}
/* Leaflet's default attribution is black-on-white and fights a dark basemap. Attribution
   is REQUIRED by both OpenStreetMap and CARTO, so it is restyled, never removed. */
.cpa-service-map .leaflet-control-attribution {
  background: rgba(15, 34, 46, 0.85);
  color: var(--color-neutral-200);
  font-size: 0.6875rem;
}
.cpa-service-map .leaflet-control-attribution a {
  color: var(--color-white);
}
/* Brand pin, built from a Font Awesome glyph rather than Leaflet's default marker PNG —
   avoids the library's icon-path resolution entirely and matches the palette. */
.cpa-map-pin {
  color: var(--color-red);
  font-size: 2rem;
  line-height: 1;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.55);
}
/* Was .cpa-service-panel until 6b — the SAME name the services tab set uses for its copy
   half, two unrelated components sharing one class. Renamed to its own section prefix.
   The collision was not cosmetic: this panel's radius leaked onto the tab card's copy
   half (notching the seam against the photo), this panel's red survived only because
   .bg-gradient-red-fade carries !important, and custom.js's querySelector picked up the
   tab panel instead of this one, reserving 588px of map padding instead of 684px. */
.cpa-service-area-panel {
  border-radius: var(--border-radius-standard);
  padding: 2.5rem;   /* Figma 40px */
}
/* Attribution moved off the map at the client's request — see the markup comment. It is a
   licence condition of OpenStreetMap and CARTO, so it is relocated and quietened, never
   deleted. Bottom-LEFT keeps it clear of the panel. */
.cpa-map-credit {
  position: absolute;
  left: 4.75rem;   /* clears the bottom-left zoom control */
  bottom: 0.625rem;
  z-index: 1;
  margin: 0;
  font-size: 0.6875rem;
  line-height: 1.2;
  color: var(--color-neutral-500);
}
.cpa-map-credit a {
  color: var(--color-neutral-300);
  text-decoration: underline;
}
.cpa-service-locations {
  list-style: none;
  padding-left: 0;
  margin-bottom: 0;
}
/* Each location is a tap target in its own right — these are standalone list links, not
   inline-in-a-sentence ones, so they take the full 40px floor. That makes the list taller
   than the comp draws it; the alternative is sub-floor hit areas, which the guardrail
   rules out. nowrap keeps every label on one line, which is what the comp shows. */
.cpa-service-location {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: var(--tap-target-min);
  white-space: nowrap;
  transition: color var(--transition-standard);
}
a.cpa-service-location:is(:hover, :focus-visible) {
  color: var(--color-white);
}

/* === Home FAQs ===
   Pre-tinted photo band, so it carries its own top padding. Figma: 80px. */
.cpa-faq {
  padding-top: 5rem;
  padding-bottom: 5rem;
}
/* One accordion split across two columns — see references/patterns.md. The wrapper sits
   OUTSIDE the .row so a single data-bs-parent reaches items in both columns. */
.cpa-faq-accordion .accordion-item {
  border-bottom: 1px solid var(--color-neutral-200);
  margin-bottom: 0.5rem;
}
.cpa-faq-accordion .accordion-button {
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0;
  color: var(--color-white);
  font-size: 1.125rem;   /* Figma 18/26 Inter Bold */
  line-height: 1.625rem;
  text-align: left;
}
.cpa-faq-accordion .accordion-button i {
  flex-shrink: 0;
  font-size: 1rem;
  transition: transform var(--transition-standard);
}
/* Figma only draws the collapsed (plus) state. Rotating the same glyph 45deg turns it into
   a close mark on open — one icon, no second glyph to keep in sync. */
.cpa-faq-accordion .accordion-button:not(.collapsed) i {
  transform: rotate(45deg);
}
.cpa-faq-accordion .accordion-body {
  padding: 0 0 0.75rem;
  font-size: 0.9375rem;
  line-height: 1.5rem;
  color: var(--color-neutral-200);
}

/* === Home Closer ===
   Paints its own slate band, so it carries its own top padding. Figma: 80px.
   The advantage row reuses .cpa-advantage from the opener verbatim — same Figma component,
   so no closer-specific card styles exist or should be added. */
.cpa-closer {
  padding-top: 5rem;
  padding-bottom: 5rem;
}
.cpa-closer-media {
  border-radius: var(--border-radius-standard);
  overflow: hidden;
}

/* === Footer === */
.cpa-footer {
  padding-top: 3rem;
}
.cpa-footer-logo {
  height: auto;
  max-width: 100%;
}
.cpa-footer-eyebrow {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-black);
  text-transform: uppercase;
  font-size: 1rem;
  line-height: 1.75rem;
  color: var(--color-white);
}
.cpa-footer-contact-value {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-bold);
  font-size: 1.25rem;
  line-height: 1.75rem;
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target-min);
}
.cpa-footer-divider {
  border-color: var(--color-neutral-700);
  opacity: 1;
  margin: 0;
}
.cpa-footer-heading {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  color: var(--color-white);
  margin-bottom: 0.75rem;
}
/* Footer links are NOT inline-in-a-sentence links — they get the full tap floor.
   Full-width hit areas stack unambiguously, so no extra inter-item gap is needed. */
.cpa-footer-link {
  display: flex;
  align-items: center;
  min-height: var(--tap-target-min);
  font-size: 0.9375rem;
  line-height: 1.375rem;
}
.cpa-social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-target-min);
  min-width: var(--tap-target-min);
  font-size: 1.25rem;
}

/* === Media Queries === */

/* At xl+ the offcanvas is not a panel — it is the inline nav row. Bootstrap's
   .offcanvas positioning has to be unwound, and the shell made transparent.
   Show/hide of the nav's own children is handled by d-none / d-xl-flex / d-xl-none
   utilities in menu.php, NOT here. */
@media (min-width: 1400px) {
  .offcanvas {
    position: static;
    transform: none;
    visibility: visible;
    background: transparent;
    border: 0;
    width: auto;
    height: auto;
    z-index: auto;
  }
  .offcanvas-body {
    flex-direction: row;
    background: transparent;
    padding: 0;
    overflow: visible;
  }
}

@media (max-width: 1399.98px) {
  /* The panel background belongs to the OFFCANVAS, not a bg-* utility in the markup:
     a utility carries !important and would keep painting a band behind the desktop nav,
     where the offcanvas is just an inline wrapper. Set here, it applies only below xxl.
     It sits on .offcanvas (not .offcanvas-body) so the header is covered too. */
  .offcanvas {
    background: var(--gradient-steel);
  }
  .cpa-mobile-nav .nav-link[data-bs-toggle='collapse'] .fa-chevron-down {
    transition: transform 0.2s ease;
    flex-shrink: 0;
    color: var(--color-white);
  }
  .cpa-mobile-nav .nav-link[data-bs-toggle='collapse']:not(.collapsed) .fa-chevron-down {
    transform: rotate(180deg);
  }
}

@media (max-width: 991.98px) {
  .cpa-menu-padding {
    padding: 0 0.5rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ============================================================================
   BLOG — added 2026-08-10 for /blog and /blog/{slug}
   ----------------------------------------------------------------------------
   Two additions only. Everything else the blog needs already existed:
   .cpa-services (band padding), .cpa-card-light (the light card on a dark band),
   .card / .card-img-top, .cpa-panel (the mid-article CTA), .cpa-overline,
   .tap-target, and the surface system, which resolves article link colour with
   no class because .bg-neutral-50 is a registered LIGHT surface.

   Clear Water Filtration's blog ships its card grid as an INLINE <style> block
   and a column-count masonry. Neither is reproduced here: this build forbids
   inline CSS, and our cards carry a uniform 16:9 crop, so masonry's ragged
   heights would buy nothing while scrambling reading order.
   ========================================================================= */

/* Card lift matches .cpa-advantage exactly (-6px + shadow) so the two card
   families read as one system. The whole card is clickable via Bootstrap's
   .stretched-link on the HEADING link — never by wrapping the card in an <a>,
   which would fold the date and excerpt into the accessible name. */
.cpa-blog-card {
  transition: transform var(--transition-standard),
              box-shadow var(--transition-standard);
}
.cpa-blog-card:hover,
.cpa-blog-card:focus-within {
  transform: translateY(-6px);
  box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.25);
}
.cpa-blog-card .card-img-top {
  border-radius: 0;
}
/* The card intentionally does NOT carry Bootstrap's .card (see blog.php), so the
   `.card .card-body { padding: 1.5rem }` rule above cannot reach it. Restate the
   site's card padding here so blog cards match every other card on the site. */
.cpa-blog-card .card-body {
  padding: 1.5rem;
}

/* Long-form article rhythm. Scoped to .cpa-article so it cannot leak into the
   marketing pages, whose spacing comes from Bootstrap utilities in markup. */
.cpa-article > *:first-child { margin-top: 0; }
.cpa-article h2 {
  margin-top: 2.5rem;
  margin-bottom: 1rem;
}
.cpa-article h3 {
  margin-top: 2rem;
  margin-bottom: 0.75rem;
}
.cpa-article p,
.cpa-article ul,
.cpa-article ol,
.cpa-article table {
  margin-bottom: 1.25rem;
}
.cpa-article ul,
.cpa-article ol {
  padding-left: 1.5rem;
}
.cpa-article li {
  margin-bottom: 0.5rem;
}
/* A handful of migrated posts carry tables. Give them a readable default and
   let a narrow viewport scroll the wrapper rather than overflow the document. */
.cpa-article table {
  width: 100%;
  border-collapse: collapse;
}
.cpa-article th,
.cpa-article td {
  padding: 0.75rem;
  border: 1px solid var(--color-neutral-300);
  text-align: left;
}
.cpa-table-scroll {
  overflow-x: auto;
}
