/*
 * Idiom Child Starter — brand override stylesheet.
 *
 * Loaded by functions.php on the parent's `idiom_after_stylesheets` hook, so it
 * loads AFTER the parent CSS and wins the cascade. Put ALL per-company style
 * overrides here (not in style.css).
 *
 * The base theme is driven by CSS custom properties; override brand tokens here
 * rather than re-declaring component rules where possible.
 */

/*
 * B. Carlson brand palette
 * ----------------------------------------------------------------------------
 * Source of truth: the Elementor global kit on bcarlson.com
 * (wp-content/uploads/elementor/css/post-10.css) plus the logo artwork.
 *
 *   Primary blue ........... #165880  --e-global-color-primary
 *   Bright cyan ............ #00AAE6  (logo light end; decorative only)
 *   Deep orange ............ #AF4C12  --e-global-color-secondary  -> CTA/links
 *   Bright orange .......... #FF7B2C  --e-global-color-accent     -> decorative
 *   Body text .............. #112337
 *   Muted text ............. #5D6169  --e-global-color-text
 *
 * The base theme's :root still ships the previous client's navy/red values, so
 * every brand token is re-declared below rather than edited in the shared parent.
 */
:root {
    /* Blues. Every background gradient on the site is built from this one hue
       so the dark surfaces read as a single family instead of the assorted
       navies the base theme hardcodes. #006697 is unusually useful: 6.27:1
       BOTH ways, so it works as a surface under white text and as text on
       white. --grad-light is the lightest stop any gradient may start on —
       white body copy still clears AA (5.07:1) there, which is the constraint
       that sets it. Going lighter breaks text on the heroes. */
    --brand-primary:        #006697; /* base hue         (white on it 6.27:1)  */
    --brand-primary-dark:   #003c56; /* darkest stop     (white on it 11.79:1) */
    --grad-light:           #00779f; /* lightest stop    (white on it 5.07:1)  */
    --grad-deep:            #004a6b; /* mid-dark stop    (white on it 9.58:1)  */

    /* Oranges. --brand-accent is the kit's bright --e-global-color-accent, the
       orange off the truck wrap, and means "accent FILL" — button, badge or
       full section background.

       For orange as TEXT on a light surface (links, icons, hover states) use
       --brand-accent-text instead. The bright hue is unreadable as type: it is
       2.59:1 on white and 2.27:1 on the #f0f0f0 top bar. */
    --brand-accent:         #ff7b2c; /* accent FILL                            */
    --brand-accent-hover:   #ec6410; /* fill hover                             */
    /* Label on the orange fill. Per direction this is WHITE wherever orange is
       a background — button, badge or full section. Flagged for the record:
       white on #ff7b2c measures 2.59:1 and on the hover shade 3.28:1, both
       under the 4.5:1 AA floor for body text. If AA is wanted back without
       losing the bright look, deepening --brand-accent to #b8531c carries
       white at 4.53:1 and is the only change needed. */
    --brand-accent-ink:     #ffffff; /* text ON the fill                       */
    --brand-accent-text:    #af4c12; /* orange as TEXT on white   (5.43 AA)    */
    --brand-accent-text-hv: #8f3d0e; /* its hover                 (7.39 AAA)   */
    --brand-accent-bright:  #ff7b2c; /* retained: same as --brand-accent now   */

    /* CTA FILL — the colour of a clickable, accent-filled action.
       ------------------------------------------------------------------------
       Was split out of --brand-accent to take buttons brick red while the
       orange stayed the DECORATIVE accent (coupon-card top rules, plan badges,
       perk checks, duotone glyphs). Per direction the fill is back on the brand
       orange, so both roles now resolve to the same value. The two tokens stay
       separate so they can diverge again without another sweep.

       CONTRAST. The label on this fill is WHITE, and on the orange that is a
       real shortfall rather than a rounding error:

         white   on #ff7b2c ..... 2.59:1   fails (4.5:1 small / 3:1 large)
         #112337 on #ff7b2c ..... 6.16:1   AA — the ink that passes on this fill
         white   on #b8531c ..... 4.89:1   AA — the fill that passes with white

       So white and #ff7b2c cannot both hold. Two ways back to AA, whichever the
       brand prefers: point --cta-fill-ink at --brand-ink (6.16:1, keeps the
       bright orange), or deepen --cta-fill to #b8531c (4.89:1, keeps the white
       label). Shipping as-is is a deliberate brand-over-AA call — flagged here
       so it is not mistaken for an oversight.

       HOVER is still the brick red from the previous direction and no longer
       belongs to this ramp. --brand-accent-hover (#ec6410) is the orange's own
       hover; white on it is 3.28:1, ink on it 4.85:1. */
    --cta-fill:             #ff7b2c; /* accent-filled BUTTON / action fill     */
    --cta-fill-hover:       #b5302f; /* STALE: brick red under an orange rest  */
    --cta-fill-ink:         #ffffff; /* label ON the CTA fill  (2.59:1 — fails)*/

    /* Cyan from the logo's light end / the truck wrap's lower panel. The pure
       hue can't carry OR sit on white text (2.66:1 both ways), so it ships as
       a scale: use --brand-cyan for fills and graphics that pair with DARK
       text, and the shaded stops when type is involved. Ratios are vs. the
       surface named in each comment. */
    --brand-cyan:           #00aae6; /* true brand cyan — fills/graphics only  */
    --brand-cyan-ink:       #0079a6; /* cyan as text on white      (4.90 AA)   */
    --brand-cyan-deep:      #026b96; /* cyan surface under white   (5.92 AA)   */
    --brand-cyan-light:     #5ad2f5; /* cyan as text on navy       (6.53 AA)   */
    --brand-cyan-tint:      #e8f7fd; /* soft cyan surface, ink on it (14.5 AAA)*/

    /* Orange that stays legible as TEXT on the navy surfaces. The base theme
       points .eyebrow-on-dark and the on-navy link colors at --brand-accent,
       which is only 2.11:1 on #0f3d59 — unreadable. This is the fix. */
    --brand-accent-on-dark: #ff9a5c; /* orange text on navy        (5.47 AA)   */

    /* Neutrals — kit --e-global-color-text and greys. */
    --brand-ink:            #112337; /* body text        (15.92:1 on white)    */
    --brand-muted:          #5d6169; /* secondary text   (6.21:1 on white)     */
    --brand-bg-soft:        #f2f2f2;
}

/* Links use the TEXT orange, not the fill — the bright hue is 2.59:1 on white. */
a            { color: var(--brand-accent-text); }
a:hover      { color: var(--brand-accent-text-hv); }

/* Primary CTA buttons on the CTA fill, white label — see --cta-fill. */
.btn-primary        { background: var(--cta-fill); color: var(--cta-fill-ink); }
.btn-primary:hover  { background: var(--cta-fill-hover); color: var(--cta-fill-ink); }

/*
 * Dark surfaces below reference --brand-ink for their BACKGROUND. Now that
 * --brand-ink is the #333 text color, re-pin them to deep navy so the footer
 * and Care Club section stay on-brand (white text keeps AAA contrast).
 */
/* #014785 is one value off the CTA banner's navy field (#004785), so the banner
 * and the footer now read as one continuous block of colour and the tricolour
 * rule at the banner's bottom edge is the only thing dividing them. That is why
 * the rule moved to that edge — see section 1. */
.footer                  { background: #014785; }

/* The phone link is the one thing the lighter footer broke. Base paints it
 * --brand-accent (#ff7b2c), which measured 4.56:1 on the old #003c56 — passing
 * by a hair — and 3.62:1 on #014785, which does not. --brand-accent-on-dark
 * (#ff9a5c) is the theme's answer for orange type on navy but it was tuned
 * against the old colour too: 5.63:1 there, 4.47:1 here, still short. #ffa26a
 * is the same hue carried far enough to clear the bar on the new surface.
 * Scoped to the footer so the token keeps its value everywhere else, where it
 * is still sitting on the darker navies it was set for. */
.footer .btn-phone-footer,
.footer .btn-phone-footer:hover,
.footer .btn-phone-footer:focus,
.footer .btn-phone-footer:visited,
.footer .btn-phone-footer i   { color: #ffa26a; }   /* 4.74:1 on #014785 */

/* Footer logo in full color, at its true proportions.
 *
 * filter: the base knocks the mark to pure white with
 * `brightness(0) invert(1)` for single-color SVG marks; B. Carlson's is a
 * full-color transparent WebP, so drop the filter and let it render as-is.
 *
 * Aspect ratio: footer.php hardcodes width="220" height="60" on the <img> —
 * the proportions of the previous client's SVG (3.67:1). The actual mark is
 * 250×117 (2.14:1), and the base only caps max-height, so nothing overrode
 * those attributes and the browser stretched the logo to exactly 220×60 —
 * 72% too wide for its height. Setting both dimensions to auto hands sizing
 * back to the file's intrinsic ratio; width then drives it, keeping roughly
 * the original footprint (200×94) instead of the 128×60 a max-height cap
 * would produce. */
.footer-logo-img {
    filter: none;
    width: 200px;
    height: auto;
    max-height: none;
}
.care-club-section       { background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-dark) 100%); }
.care-club-section.has-bg { background-color: var(--brand-primary-dark); }

/* Brand gradient — Support Blue -> Primary Blue (hero image overlay).
 *
 * MOVED. This scrim is now the bottom layer of the shared hero decoration and
 * is defined once as --hero-photo-scrim; the overlay rule that consumes it is
 * "GROUP B" in the SHARED HERO DECORATION section further down this file. Do not
 * restate a background here — a rule at this spot would be overridden by that
 * one anyway (same specificity, later in the file) and would read as the live
 * value while having no effect. */

/*
 * Logo size + "bridge" overhang.
 *
 * The mark fills its nav column (height auto, so it keeps its natural
 * proportions) and hangs past the bottom of the header so it visually bridges
 * the white nav and whatever section sits below it. (The mobile and footer
 * logo rules are height-driven and unaffected.)
 *
 * Widening --nav-logo-col is what actually enlarges the mark: the base grid
 * sizes the logo column from it AND caps .header-content at
 * calc(--container-max - --nav-logo-col), so overriding the one variable keeps
 * the nav row and the site container aligned.
 *
 * THE CEILING ON THIS NUMBER. The mark is lifted up into the swoosh bar (see
 * .logo-img below), and .nav-wrapper is position:sticky at top:0 — so anything
 * that overflows ABOVE the bar is clipped by the top of the viewport, not just
 * hidden behind something. The room available is the swoosh bar (44px) plus the
 * nav row (78px) = 122px, and the column drives the mark's height through the
 * asset's own 300x138 ratio:
 *
 *     mark height = (--nav-logo-col - 48px of .logo padding) x 138 / 300
 *
 * 320px produced a 125px mark in 122px of room and sheared 3px off the top of
 * the oval. 296px gives 248x114 and leaves an 8px gap above it. If the swoosh
 * bar's height changes, or a taller logo asset is dropped in, re-derive this.
 */
.nav-wrapper { --nav-logo-col: 296px; }

/* The base grid parks the top bar in the menus column ("`. logo top top`"), so
 * it started after the logo column and left a white notch at the screen's left
 * edge. The swoosh artwork is drawn edge to edge, so row 1 is handed all four
 * columns and the logo drops to row 2 only. Column list is untouched. */
.nav-wrapper {
    grid-template-areas:
        "top  top  top  top"
        ".    logo nav  nav";
}

/* The mark no longer hangs below the header — it is lifted UP instead, so its
 * top half crosses the swoosh bar. z-index keeps it above the bar; flex-end
 * anchors it from the row's bottom; overflow stays visible because the lift
 * overflows upward.
 *
 * background is transparent, NOT white. The base theme paints this cell white,
 * which was invisible while the bar started after the logo column — now that
 * the swoosh runs edge to edge it would punch a white notch out of the cyan.
 * The badge carries its own chrome rim, so it reads on cyan unaided.
 *
 * padding-top drops to 0: it is layout height that would push the header taller
 * now that the mark is bottom-anchored rather than free-floating. */
.nav-wrapper .logo {
    position: relative;
    z-index: 3;
    align-items: flex-end;
    padding-top: 0;
    padding-bottom: 0;
    overflow: visible;
    background: transparent;
}

.nav-wrapper .logo-img {
    width: 100%;
    height: auto;
    max-height: none;
    /* THE LIFT, and the gap above and below the mark.
     *
     * The mark is centred in the full header stack — the 44px swoosh bar plus
     * the 78px nav row, 122px of room for a 114px mark — so the 8px left over
     * is split evenly: 4px clear of the top of the screen, 4px clear of the
     * header's bottom border. It used to be 8/0, bottom edge flush, which read
     * as the mark sitting low in its own space.
     *
     * The two margins have to be read together, and they must sum to
     * (mark height - row height) = 114 - 78 = 36. That is what keeps the nav row
     * at 78px: the negative top margin is the only thing stopping an oversized
     * mark from dictating the row, and margin-bottom pushes back down against
     * flex-end by the same amount it is given. -40 + 4 = -36. Break that sum and
     * the whole header changes height.
     *
     * For MORE air, don't just raise these — the mark has to lose height first
     * or it will clip against the top of the sticky header again. Narrow
     * --nav-logo-col by (2 x extra gap x 300/138), then set margin-bottom to the
     * new gap and margin-top to (new mark height - 78 + gap), negated. */
    margin-top: -40px;
    margin-bottom: 4px;
    /* drop-shadow (NOT box-shadow): the asset is a transparent WebP, so
     * box-shadow would trace the rectangular image box and paint a hard edge.
     * drop-shadow follows the alpha channel and hugs the oval. Lighter and
     * tighter than the old two-pass hero shadow — it now only has to separate
     * the badge from the cyan bar behind it, not carry an overhang. */
    filter: drop-shadow(0 1px 2px rgba(0, 28, 41, 0.18))
            drop-shadow(0 4px 8px rgba(0, 28, 41, 0.14));
}

/* The desktop nav appears from 1101px up, and between there and ~1200px the
 * nav row can't spare the 52px the wider logo column takes — the links
 * overflow their track. Step the column back to the base width in that band;
 * the overhang is driven by the margin above, so it survives untouched. */
@media (max-width: 1200px) {
    .nav-wrapper { --nav-logo-col: 268px; }
}

/* ---------------------------------------------------------------------------
 * Main-nav dropdown — break to two columns after the 4th item.
 *
 * idiom-base switches a first-level dropdown to a two-column grid only once it
 * reaches 8 items ("Two-column dropdown" in styles.css), then balances the
 * columns at ceil(N/2) rows. Heating has 7 services, so it fell under that
 * trigger and rendered as one 7-deep column.
 *
 * This drops the trigger to 5 and pins the first column at 4 rows, so a panel
 * of 5-8 items reads as "four services, then the rest".
 *
 * THE LADDER HAS TO BE RESTATED, not just extended. Every step below carries
 * the same specificity as the base rule it shadows, and child.css loads after
 * the base — so a lone :nth-child(5) rule here would out-cascade the base's
 * :nth-child(9)/(11)/(13)/(15) steps and force 9+ item panels (Cooling,
 * Plumbing) into 4 rows, i.e. three columns. Repeating the ladder in ascending
 * order restores "largest match wins" and leaves those panels as they were.
 *
 * Scoped to > li > .sub-menu so the depth-3 flyouts stay single-column.
 * ------------------------------------------------------------------------- */
.nav-links > li > .sub-menu:has(> li:nth-child(5)) {
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: repeat(4, auto);
    column-gap: 4px;
    min-width: 440px;
}
.nav-links > li > .sub-menu:has(> li:nth-child(9))  { grid-template-rows: repeat(5, auto); }
.nav-links > li > .sub-menu:has(> li:nth-child(11)) { grid-template-rows: repeat(6, auto); }
.nav-links > li > .sub-menu:has(> li:nth-child(13)) { grid-template-rows: repeat(7, auto); }
.nav-links > li > .sub-menu:has(> li:nth-child(15)) { grid-template-rows: repeat(8, auto); }

/* ---------------------------------------------------------------------------
 * Hero eyebrow / breadcrumb
 *
 * Two markup patterns exist in the base theme:
 *   1. Breadcrumb heroes:  <nav class="hero-eyebrow"> … <p class="hero-eyebrow-text">
 *   2. Simple heroes:      <p|span class="hero-eyebrow">  (the tagline itself)
 *
 * So: the breadcrumb container (nav.hero-eyebrow) becomes a plain white
 * breadcrumb trail, while the tagline — whether it's .hero-eyebrow-text or a
 * standalone p/span.hero-eyebrow — gets the red pill. Element-scoped so no base
 * markup has to change.
 * ------------------------------------------------------------------------- */

/* Breadcrumb trail (nav.hero-eyebrow). Block-level so it sits on its own line
 * above the tagline pill, with inline items so it follows the hero's
 * text-align — left-aligned heroes keep it left, centered heroes center it. */
.hero-eyebrow {
    display: block;
    margin: 0 0 14px;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    line-height: 1.4;
    text-transform: none;
    color: #ffffff;
}
.hero-eyebrow a { color: rgba(255, 255, 255, 0.85); text-decoration: none; }
.hero-eyebrow a:hover,
.hero-eyebrow a:focus-visible { color: #ffffff; text-decoration: underline; }
.hero-eyebrow a[aria-current="page"] { color: #ffffff; font-weight: 700; }
.hero-eyebrow > i,
.hero-eyebrow a > i { display: inline-block; vertical-align: -1px; }
.hero-eyebrow .hero-eyebrow-sep {
    display: inline;
    margin: 0 7px;
    color: rgba(255, 255, 255, 0.5);
    opacity: 1;
    font-weight: 400;
}

/* Hero button row.
 *
 * idiom-base ships this as `display:flex; flex-wrap:wrap; gap:12px;
 * margin-bottom:24px` with no top margin, so the buttons sit tight against
 * whatever precedes them (the H1 on simple heroes, the subhead on service
 * heroes). Restated in full here — rather than a lone `margin-top` — so the
 * whole rule reads as one block at the site level and a base change to the
 * gap or wrap behaviour can't drift out from under it unnoticed. */
.hero-cta {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 24px;
    margin-top: 24px;
}

/* Tagline pill — the breadcrumb-page tagline AND simple-page eyebrows. */
.hero-eyebrow-text,
p.hero-eyebrow,
span.hero-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: #ffffff;
    margin: 0 0 16px;
    padding: 6px 14px;
    background: rgba(255, 123, 44, 0.16);
    border: 1px solid rgba(255, 123, 44, 0.45);
    border-radius: 999px;
}

/* Home hero eyebrow — paw print.
 *
 * The tagline is "Your Best Friend in Comfort", so the paw is doing real work
 * here: it lands the dog-mascot brand promise in the first element above the H1.
 * Scoped to .hero-home so the interior-page eyebrow pills stay plain.
 *
 * Injected as a ::before rather than an <i> in the markup because the eyebrow is
 * an esc_html() ACF field inside idiom-base/template-parts/section-home-content.php.
 * Adding the element would mean either forking that 198-line base template into
 * this child (killing kit updates for the whole home page) or putting B Carlson
 * branding in the shared base. A pseudo-element keeps both out of it, and the
 * pill is already `display:inline-flex; gap:8px`, so the glyph slots in as a flex
 * item and inherits the existing alignment and spacing with no layout changes.
 *
 * Renders the duotone PRIMARY layer only (the base codepoint, no "ss01"). FA's
 * two-layer machinery needs an absolutely-positioned ::before plus an in-flow
 * ::after, which can't coexist with the pill's own text. For fa-paw specifically
 * the primary layer is the complete outline of all five pads — a whole, legible
 * paw print, not a fragment. Verified against the kit source:
 *   node_modules/@awesome.me/kit-9ee3b9c226/icons/svgs-full/duotone-light/paw.svg
 *
 * fa-paw is NOT in the base theme's 191-icon FontAwesome subset by default — it
 * is listed in idiom-base/scripts/icons-content.txt so `npm run fa:sync --
 * --subset` keeps the glyph in both the CSS and the woff2. Re-run that after any
 * kit update or the paw silently disappears.
 *
 * Decorative only: `content: "\f1b0" / ""` gives the pseudo-element empty alt
 * text so screen readers announce just the tagline, matching how the parent
 * marks its real <i> icons aria-hidden. The @supports block below restores the
 * glyph on engines without the alt-text content syntax, which would otherwise
 * throw out the whole declaration and render no icon at all.
 *
 * No text-shadow rule needed for the photo hero: text-shadow is inherited, and a
 * pseudo-element inherits from its originating element, so the paw already picks
 * up the .hero.has-bg .hero-eyebrow scrim further down this file. */
.hero-home p.hero-eyebrow::before {
    content: "\f1b0" / "";
    flex: 0 0 auto;
    font-family: var(--fa-family-duotone, "Font Awesome 7 Duotone");
    font-weight: 300;
    font-style: normal;
    font-variant: normal;
    font-synthesis: none;
    font-size: 1.05em;
    line-height: 1;
    text-rendering: auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

@supports not (content: "" / "") {
    .hero-home p.hero-eyebrow::before { content: "\f1b0"; }
}

/* Narrow phones — keep the pill on ONE line now that the paw widened it.
 *
 * The tagline is 26 tracked uppercase characters and was already close to the
 * edge: it filled 311px of a 312px content box at 360px wide. The glyph plus its
 * 8px gap adds ~24px, which pushed 360px and 375px (iPhone SE / 12 mini / X-class
 * — very common) onto a second line and put a chunky two-line pill above the H1.
 *
 * Trimming tracking, the gap, the horizontal padding and the glyph a touch buys
 * back the ~24px and lands at 306px in a 312px box — ~6px of headroom, enough to
 * absorb the font-metric differences between engines. 0.16em -> 0.10em is still
 * clearly tracked at this size and only applies below 400px, so the desktop pill
 * is untouched. 320px still wraps, exactly as it did before the paw existed —
 * there is no room to win there and it was never a single line. */
@media (max-width: 400px) {
    .hero-home p.hero-eyebrow {
        letter-spacing: 0.1em;
        gap: 6px;
        padding-left: 12px;
        padding-right: 12px;
    }
    .hero-home p.hero-eyebrow::before { font-size: 1em; }
}

/* PPC hero "deal pill" — drop the parent's brand-red text override so the label
   inherits the hero's white body color (.hero-offer-body { color:#fff }), matching
   the white eyebrow pills above. child.css loads after the parent sheet, so equal
   specificity wins the cascade — no !important needed. */
.hero-offer-eyebrow-pill {
    color: #fff;
}

/* ---------------------------------------------------------------------------
 * PPC / Featured-Offer hero — "Call us 24/7" phone button.
 *
 * These buttons (split, classic --solo, and card variants) shipped with a
 * hardcoded #af4c12 fill, then ran on --brand-accent (#ff7b2c) with a navy
 * --brand-ink label, because white is 2.59:1 on that orange and fails AA.
 *
 * They then moved to --cta-fill, which briefly took them brick red and flipped
 * the ink to WHITE. --cta-fill is back on #ff7b2c, so the fill is the same
 * orange these buttons started on but the ink stayed white behind it:
 *
 *   white   on #ff7b2c (rest) ..... 2.59:1  fails
 *   white   on #b5302f (hover) .... 6.12:1  AA — but a red hover on an orange rest
 *   #112337 on #ff7b2c ............ 6.16:1  AA — the navy label that was removed
 *
 * So the navy ink these buttons originally carried is once again the passing
 * pairing. The white below is inherited from the red direction, not measured
 * for this fill — see the --cta-fill block at the top of the file.
 *
 * child.css loads after the parent sheet, so equal-specificity selectors win —
 * no !important needed.
 * ------------------------------------------------------------------------- */
.hero-split-phone,
.hero-offer-cta-phone.hero-offer-cta-phone--solo,
.hero-card-phone {
    background: var(--cta-fill);
    /* A white 28% edge (the base's) read as a halo around the old dark fill.
       The red fill separates from the navy hero on its own, so the edge stays
       white but drops to 22% — enough to define the button, not enough to ring
       it. */
    border-color: rgba(255, 255, 255, 0.22);
    color: var(--cta-fill-ink);
}
/* Base drops a red glow under the card variant — rgba(216,63,64,.3). That is
   #d83f40, which matched exactly while --cta-fill was red; only the alpha was
   lifted. STALE now that the fill is back on #ff7b2c — this is a red glow under
   an orange button. rgba(255,123,44,.34) is the matching orange, pending the
   same call as --cta-fill-hover. The inset white highlight is the base's. */
.hero-card-phone {
    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5) inset,
                0 8px 18px rgba(216, 63, 64, 0.34);
}
.hero-split-phone:hover,
.hero-split-phone:focus-visible,
.hero-offer-cta-phone.hero-offer-cta-phone--solo:hover,
.hero-offer-cta-phone.hero-offer-cta-phone--solo:focus-visible,
.hero-card-phone:hover,
.hero-card-phone:focus-visible {
    background: var(--cta-fill-hover);
    color: var(--cta-fill-ink);
}
/* Label and number. Two notes:
   - the base paints the label rgba(14,26,48,.72) — navy at 72%, which on the
     red fill is 2.9:1. Pinned to full white with the number.
   - the CARD variant marks its number up as <strong>, not
     .hero-card-phone-number. The old selector list never matched it — it only
     stayed legible by inheriting white on the dark fill. Both spellings are
     covered now so the number cannot drift again. */
.hero-split-phone-label,
.hero-offer-cta-phone--solo .hero-offer-cta-phone-label,
.hero-card-phone-label,
.hero-split-phone-number,
.hero-offer-cta-phone--solo .hero-offer-cta-phone-number,
.hero-card-phone strong {
    color: var(--cta-fill-ink);
}
/* Icon badge: REMOVED per direction — a bare white glyph on the red fill, no
   disc behind it.

   Split and classic wrap their glyph in a <span class="*-phone-icon">, which
   the FA kit leaves alone, so the base's disc actually rendered on those two
   and has to be undone here. Ticket (`.hero-deal-phone i`) and card
   (`.hero-card-phone i`) style the badge on the <i>, and Font Awesome runs in
   SVG-with-JS mode, so the <i> is gone before those rules can land — no disc
   has ever rendered there and both already show the bare glyph. That means
   this one rule brings all four designs to the same treatment.

   White on --cta-fill is 4.46:1 (AA for 1.4.11 non-text). Duotone's secondary
   layer is pinned to full white too, otherwise it drops to 40% currentColor
   and reads as a soft double-image at this size. */
.hero-split-phone-icon,
.hero-offer-cta-phone--solo .hero-offer-cta-phone-icon {
    background: none;
    border-radius: 0;
    width: auto;
    height: auto;
    color: var(--cta-fill-ink);
    --fa-primary-color: var(--cta-fill-ink);
    --fa-secondary-color: var(--cta-fill-ink);
    --fa-primary-opacity: 1;
    --fa-secondary-opacity: 1;
}

/* ---------------------------------------------------------------------------
 * Two-column content grid — restore the container gutter.
 *
 * Markup is <div class="container content-grid">. The base's
 * `.content-grid { padding: 56px 0 }` (styles.css) is a single-class selector
 * declared AFTER `.container { padding: 0 24px }`, so on the shared element it
 * wins and zeroes the horizontal gutter — making every content-grid section
 * (service intro/secondary, care-club, etc.) sit 24px wider on each side than
 * the rest of the page. Restore the 24px side padding on desktop; the base's
 * <=800px rule already sets its own 15px gutter, so scope this above it.
 * ------------------------------------------------------------------------- */
@media (min-width: 801px) {
    .content-grid {
        padding-left: 24px;
        padding-right: 24px;
    }
}

/* ---------------------------------------------------------------------------
 * Split-hero copy — align to the page container edge.
 *
 * The base .hero-split-panel is a full-bleed 50vw column that right-aligns its
 * fixed 600px copy block toward the center seam (align-items: flex-end). That
 * pushes the copy progressively further right as the viewport grows, so it
 * never lines up with the rest of the page (which sits in the max-1580
 * container). Left-align the copy and pad its left edge to the exact container
 * gutter — max(24px, (100vw - container-max)/2 + 24px) — so the hero heading,
 * lede and CTA start on the same vertical line as all other page content. The
 * photo stays full-bleed. Only applies to the two-column layout (>=881px);
 * below that the hero stacks and the base mobile rule governs.
 * ------------------------------------------------------------------------- */
@media (min-width: 881px) {
    .hero-split-panel {
        align-items: flex-start;
        padding-left: max(24px, calc((100vw - var(--container-max, 1580px)) / 2 + 24px));
        padding-right: clamp(24px, 4vw, 64px);
    }
}


/* ---------------------------------------------------------------------------
 * Split-hero review bubble — stay floating on mobile.
 *
 * Markup: template-parts/hero-offer-split.php (child override) now includes the
 * shared hero-google-bubble part instead of the base's bottom-left rating pill.
 *
 * The shared component's mobile rule (styles.css, max-width: 700px) takes the
 * bubble out of absolute positioning and centers it in normal flow above the
 * copy. That rule exists because the heroes it was written for shrink their top
 * padding band to 64px on mobile, leaving nowhere to float it. This hero has no
 * top padding band at ALL — `.hero-offer-split { padding: 0 }` — so in flow the
 * bubble would render flush against the hero's top edge, and, being the
 * section's first child, above a photo that has already stacked to the top.
 *
 * Keep it absolute here instead. Below 880px the base stacks the photo FIRST
 * (`.hero-split-media { order: -1 }`) at a 16/10 — then 4/3 — aspect ratio, so
 * the hero's top-right corner is still photo, at every width, and the bubble
 * lands on the same dark backdrop it does on desktop. The tail stays at its
 * left offset rather than being re-centered, since the bubble is still a
 * corner element pointing back into the photo.
 * ------------------------------------------------------------------------- */
@media (max-width: 700px) {
    .hero-offer-split .hero-bubble-layer {
        position: absolute;
    }
    .hero-offer-split .hero-bubble-inner {
        justify-content: flex-end;
        padding-top: 16px;
        padding-bottom: 0;
    }
    .hero-offer-split .hero-review-bubble::after {
        left: 28px;
        margin-left: 0;
    }
}


/* ---------------------------------------------------------------------------
 * Top utility bar ("top nav") — brand swoosh.
 *
 * Built from the supplied brand banner (Asset 1.svg): cyan open space on the
 * left, a short orange-into-navy crescent, then navy across to the right edge.
 * Nav links + the Google badge sit in the NAVY field in white; the cyan half is
 * intentionally empty.
 *
 * WHY THE ARTWORK IS NOT USED AS ONE STRETCHED IMAGE
 * The source file is 1619.32 x 79.85 (20.3:1). This bar runs ~1600 x 54 (30:1),
 * so painting the whole SVG across it stretches the crescent ~1.5x wider than
 * drawn AND pins the colour break to a fixed 44.76% of the width — which at
 * 1101px (the narrowest desktop layout) lands to the RIGHT of the nav links and
 * drops white text onto cyan. Instead the crescent is cut out as a small tile
 * (assets/svg/nav-swoosh.svg, 160 x 79.85) and the two flat fields are painted
 * by CSS around it:
 *
 *     [ cyan: background-color ][ tile ][ navy: linear-gradient ]
 *
 * The tile is sized `auto 100%`, so it keeps the crescent's true proportions at
 * any bar height, and --tm-navy-w moves the colour break independently of it.
 *
 * --tm-navy-w is the width of the navy field measured from the RIGHT edge. The
 * 55.24% arm reproduces the artwork's own break (100% - 44.76%) on wide
 * screens; the 760px arm is the floor that keeps the whole content cluster
 * (links 448px + gap + badge 199px + 24px padding = ~691px) inside the navy at
 * narrow desktop widths. If you add a top-menu item, raise the 760px.
 * ------------------------------------------------------------------------- */
.top-menu {
    --tm-cyan:  #00b8ec;
    --tm-navy:  #004785;
    --tm-navy-w: max(55.24%, 760px);

    background-color: var(--tm-cyan);
    background-image:
        url("../svg/nav-swoosh.svg"),
        linear-gradient(var(--tm-navy), var(--tm-navy));
    background-repeat: no-repeat, no-repeat;
    /* Tile keeps its aspect (auto), navy is a hard-edged block off the right. */
    background-size: auto 100%, var(--tm-navy-w) 100%;
    /* Tile's right edge butts exactly against the navy block's left edge. */
    background-position: right var(--tm-navy-w) center, right center;
    color: #fff;
    border-bottom: 0;
}
/* The bar now spans the full viewport, so the 40px scoop that softened its old
 * left end has nothing to sit against. */
.nav-wrapper .top-menu {
    border-bottom-left-radius: 0;
}
/* Was capped to the menus column because the bar started after the logo. Now
 * it is full-bleed, so the content re-centres on the site container and its
 * right edge lands where every other section's does. */
.top-menu-content {
    max-width: var(--container-max);
    margin-inline: auto;
}

/* White on #004785 is 9.38:1 (AAA) — the reason the bar could go back to white
 * type after the cyan-era dark-ink workaround. */
.top-menu-links a,
.top-menu-links a > i { color: #fff; opacity: 1; }
.top-menu-links a > .nav-chevron { color: #fff; opacity: 0.7; }
/* Hover shifts to the theme's on-navy cyan (6.53:1) AND underlines, so the
 * state never rests on colour alone. */
.top-menu-links a:hover,
.top-menu-links a:focus-visible {
    color: var(--brand-cyan-light);
    text-decoration: underline;
}
/* The bar is dark, so the global focus ring (dark on light) would vanish here. */
.top-menu-links a:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 3px;
    border-radius: 2px;
}

/* Google review widget keeps its white pill. Its gold stars (#f5b700) and the
 * multicolour G are Google-brand assets that must not be recoloured, and they
 * read cleanly on white — this is the one deliberate non-white element in the
 * navy field. */
.top-menu-badge {
    background: #ffffff;
    border-left: 0;
    padding: 4px 12px;
    border-radius: 999px;
    box-shadow: 0 1px 3px rgba(0, 40, 58, 0.18);
}
.top-menu-badge .idm-google-places-reviews.idm-badge { --idm-badge-text: var(--brand-ink); }
.top-menu-badge .idm-google-places-reviews.idm-badge .idm-read-all-reviews { color: var(--brand-ink) !important; }

/* Dropdown panels stay a light surface — they float over page content, where
 * a second field of saturated blue would fight the page behind it. */
.top-menu-links .sub-menu {
    background: #ffffff;
    border-color: rgba(0, 40, 58, 0.14);
}
.top-menu-links .sub-menu a { color: var(--brand-ink); opacity: 1; }
.top-menu-links .sub-menu a:hover { background: rgba(0, 185, 255, 0.12); color: var(--brand-primary-dark); }
.top-menu-links .sub-menu a i { color: var(--brand-primary-dark); }

/* .care-club-terms-page .cct-hero used to be in this list. It is a hero, so it
 * moved to the shared hero recipe further down the file ("SHARED HERO
 * DECORATION"), which supersedes this band treatment for it. Left out here
 * rather than overridden there, so there is only one rule to read. */
.cta-banner,
.cta-mini,
.coupons-section,
.process-section,
.cc-benefits-section {
    background-image:
        linear-gradient(90deg, rgba(255, 123, 44, 0.03) 0%, transparent 25%, transparent 75%, rgba(255, 123, 44, 0.03) 100%),
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='320' viewBox='0 0 480 320'%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='1'%3E%3Ccircle cx='480' cy='0' r='90' stroke-opacity='0.05'/%3E%3Ccircle cx='480' cy='0' r='165' stroke-opacity='0.055'/%3E%3Ccircle cx='480' cy='0' r='245' stroke-opacity='0.05'/%3E%3Ccircle cx='480' cy='0' r='330' stroke-opacity='0.04'/%3E%3Ccircle cx='480' cy='0' r='420' stroke-opacity='0.03'/%3E%3C/g%3E%3C/svg%3E");
    background-repeat: no-repeat, no-repeat;
    background-position: center, top right;
    background-size: auto, 480px 320px;
}

/* Contact hero backdrop (large fallback overlay) — rings only, top-right. */
.contact-hero-bg {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='320' viewBox='0 0 480 320'%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='1'%3E%3Ccircle cx='480' cy='0' r='90' stroke-opacity='0.05'/%3E%3Ccircle cx='480' cy='0' r='165' stroke-opacity='0.055'/%3E%3Ccircle cx='480' cy='0' r='245' stroke-opacity='0.05'/%3E%3Ccircle cx='480' cy='0' r='330' stroke-opacity='0.04'/%3E%3Ccircle cx='480' cy='0' r='420' stroke-opacity='0.03'/%3E%3C/g%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: top right;
    background-size: 620px 410px;
}

/* Light contact info card — subtle navy rings in the bottom-right corner. */
.contact-page .contact-info-card::before {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='200' viewBox='0 0 300 200'%3E%3Cg fill='none' stroke='%23213a59' stroke-width='1'%3E%3Ccircle cx='300' cy='200' r='55' stroke-opacity='0.07'/%3E%3Ccircle cx='300' cy='200' r='110' stroke-opacity='0.06'/%3E%3Ccircle cx='300' cy='200' r='170' stroke-opacity='0.05'/%3E%3Ccircle cx='300' cy='200' r='235' stroke-opacity='0.04'/%3E%3C/g%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: bottom right;
    background-size: 300px 200px;
}

/* Contact page — "Call us 24/7" emphasis row.
 *
 * The icon chip on that row is `background: var(--brand-accent)` and resolves to
 * the B. Carlson orange, but the row BEHIND it is a hardcoded
 * rgba(216, 63, 64, 0.18) in idiom-base — the previous client's red, which the
 * token table at the top of this file notes the base :root still ships. So the
 * page rendered an orange chip sitting in a pink wash.
 *
 * Restated off --brand-accent at the same alphas the base used, so the row now
 * tints with the same orange as the chip it frames and tracks any future change
 * to the accent token. This is the only surviving colour mismatch found in the
 * 292-page icon audit; every other set on the site resolves to one colour. */
.contact-page .contact-channel-primary {
    background: rgba(255, 123, 44, 0.18);
    border-color: rgba(255, 123, 44, 0.6);
}

/* Google Reviews badge — force the rating number dark (plugin var resolves to
   white in our context). Overrides idiom-google-review frontend CSS, which
   loads before child.css so equal specificity + !important wins. */
.idm-google-places-reviews.idm-google-places-reviews-wap.idm-badge .idm-fr-starsnumb span {
    color: #1a1a1a !important;
}

/* ==========================================================================
 * Financing page (child overrides — pairs with the child's page-financing.php
 * and template-parts/section-financing.php)
 * ========================================================================== */

/* --- Intro: left-aligned copy + photo right --------------------------------- */
.financing-intro--split .financing-intro-layout {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    gap: 56px;
    align-items: center;
}
.financing-intro--split .section-header {
    text-align: left;
    margin: 0 0 18px;
    max-width: none;
}
.financing-intro--split .section-header h2 { margin: 0; }
.financing-intro--split .financing-intro-text {
    max-width: none;
    margin: 0;
    text-align: left;
}
.financing-intro-media {
    margin: 0;
    position: relative;
}
.financing-intro-media img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 16px;
    box-shadow: 0 18px 40px rgba(14, 26, 48, 0.16);
}
@media (max-width: 900px) {
    .financing-intro--split .financing-intro-layout {
        grid-template-columns: 1fr;
        gap: 32px;
    }
}

/* --- Plan cards v2: icon chip, accent top bar, tighter rhythm --------------- */
.financing-plans--v2 .plan-card {
    position: relative;
    /* No overflow:hidden — the "Most Popular" badge hangs above the card edge.
     * Accent bar is a real border-top so it hugs the rounded corners exactly,
     * on both the 1px normal border and the 2px featured border. */
    display: flex;
    flex-direction: column;
    padding: 28px 26px 26px;
    border-top: 4px solid var(--brand-primary);
}
.financing-plans--v2 .plan-card.is-featured { border-top: 4px solid var(--brand-accent); }
/* Header row: icon chip left, title + rate stacked to its right. */
.financing-plans--v2 .plan-card-head {
    display: flex;
    align-items: center;
    gap: 14px;
    margin: 0;
}
.financing-plans--v2 .plan-icon {
    flex-shrink: 0;
    width: 52px;
    height: 52px;
    border-radius: 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.4rem;
    background: rgba(46, 83, 130, 0.09);
    color: var(--brand-primary);
}
.financing-plans--v2 .plan-card.is-featured .plan-icon {
    background: rgba(255, 123, 44, 0.10);
    color: var(--brand-accent);
}
.financing-plans--v2 .plan-card h3 {
    margin: 0 0 2px;
    font-size: 1.25rem;
}
.financing-plans--v2 .plan-rate {
    margin: 0;
    font-weight: 600;
    font-size: 0.95rem;
    color: var(--brand-primary);
}
.financing-plans--v2 .plan-desc {
    margin: 0 0 16px;
    font-size: 0.95rem;
}
.financing-plans--v2 .plan-features {
    margin: 0 0 16px;
    padding-top: 14px;
    border-top: 1px solid var(--brand-border);
}
.financing-plans--v2 .plan-features li {
    font-size: 0.92rem;
    padding: 4px 0;
}
.financing-plans--v2 .plan-disclaimer {
    display: block;
    margin: 0 0 16px;
    font-size: 0.78rem;
    color: var(--brand-muted);
    text-align: center;
}
/* Pin Apply Now to the bottom so all three cards align. */
.financing-plans--v2 .plan-card .btn { margin-top: auto; }

/* ---------------------------------------------------------------------------
   FAQ topic hubs (/faq/ and /faq/topic/<topic>/)
   Chips link the topic hubs from both the FAQ archive and each hub, so every
   topic is one click from any other. See inc/faq-topics.php.
--------------------------------------------------------------------------- */
.faq-topics-section {
    padding: 28px 0 4px;
}
.faq-topics-title {
    font-size: 1.05rem;
    font-weight: 700;
    margin: 0 0 12px;
    color: var(--brand-ink, #0E1A30);
}
.faq-topics-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}
.faq-topic-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    border: 1px solid var(--brand-border, #d8dde8);
    border-radius: 999px;
    background: #fff;
    color: var(--brand-ink, #0E1A30);
    text-decoration: none;
    font-size: 0.94rem;
    line-height: 1.2;
    transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.faq-topic-chip:hover,
.faq-topic-chip:focus-visible {
    background: var(--brand-bg-soft, #f3f6fb);
    border-color: var(--brand-primary, #165880);
    color: var(--brand-primary, #165880);
}
.faq-topic-chip.is-current {
    background: var(--brand-primary, #165880);
    border-color: var(--brand-primary, #165880);
    color: #fff;
}
.faq-topic-count {
    font-size: 0.8rem;
    font-weight: 600;
    opacity: .75;
}
.faq-topic-chip.is-current .faq-topic-count {
    opacity: .9;
}


/* ===========================================================================
 * BRAND ALIGNMENT — truck-wrap palette
 * ---------------------------------------------------------------------------
 * The fleet wrap reads as three bands: vivid orange up top, a white pinstripe,
 * bright cyan below. Before this block the site used neither vivid colour —
 * the cyan appeared nowhere at all and the orange was flattened to the dark
 * brick #af4c12 — so the page ran navy/white/navy/grey/navy and read nothing
 * like the trucks.
 *
 * The rule that unlocks the real palette: #00aae6 and #ff7b2c fail as TEXT
 * colours but are fine as SURFACE colours under dark text. So they go on
 * blocks, bands, fills and graphics; type keeps the shaded stops declared in
 * the :root scale above. Every pairing below is annotated with its ratio.
 * ========================================================================= */

/* --- 1. The band -----------------------------------------------------------
 * A 6px rule at the top of each major colour change — the wrap's banding
 * reduced to a divider. Painted as ::after rather than a background layer
 * because these sections already carry a multi-layer background stack (swoosh
 * SVG + fields) that would have to be re-declared wholesale otherwise.
 * .care-club-section::before is taken by the base theme's corner glow, so
 * ::after is the free slot on all three.
 *
 * The three stops are the service colours off the logo lockup — HEATING red,
 * COOLING blue, PLUMBING green — in that reading order, at exact thirds.
 * Every section that bands gets the identical rule; there is no per-section
 * variant, which is the point of it.
 *
 * The band sits FLUSH on the section — no separator beneath it, per direction.
 *
 * Known consequence, recorded so it is not rediscovered as a bug: the band is
 * opaque and sits directly on each section's own field, and a stop whose colour
 * is close to that field has no edge to define it. Measured against the field
 * each one crosses: blue 1.13:1 on the CTA's navy and 1.42:1 on the coupons
 * navy, green 1.21:1 on the CTA's cyan, red 1.19:1 on Care Club's cyan. In
 * practice the blue third is the visible one — it reads as a gap between the
 * red and the green on the CTA.
 *
 * A 1px white over 1px black hairline pair under the band fixes it completely
 * (one of the two is always in contrast, whichever field it crosses) and is what
 * was here before. It was removed because it renders as 2px of apparent spacing
 * between the colours and the section, which is the thing that was not wanted.
 * Reinstate the box-shadow below if that trade is ever worth revisiting:
 *
 *     box-shadow: 0 1px 0 rgba(255,255,255,.92), 0 2px 0 rgba(0,0,0,.32);
 */
.coupons-section,
.care-club-section,
.cta-banner {
    position: relative;
}
.coupons-section::after,
.care-club-section::after,
.cta-banner::after {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 6px;
    background: linear-gradient(
        90deg,
        #ec1c24 0      33.333%,   /* HEATING  */
        #004f92 33.333% 66.667%,  /* COOLING  */
        #00b16b 66.667% 100%      /* PLUMBING */
    );
    pointer-events: none;
    z-index: 2;
}
/* The banner previously overrode the band to track its own navy/cyan seam. That
 * override is gone: the three service colours are a fixed signature and they do
 * not re-cut themselves per section, which is what makes the rule recognisable
 * across the coupons, Care Club and CTA blocks. The band and the swoosh seam
 * beneath it are now independent — the band divides in thirds, the swoosh hands
 * over at --cta-bright-w — and that is deliberate rather than a mismatch: they
 * are two different marks, not one seam drawn twice.
 *
 * The footer CTA flips the band to its BOTTOM edge, per direction. It is the
 * last band on the page and the only one whose section is followed by another
 * colour block rather than by white, so at the top it was a divider between two
 * dark fields nobody needed; at the bottom it closes the page against the
 * footer. The other two keep theirs on top, where the section they open is what
 * needs marking.
 *
 * `inset` is restated whole rather than setting `top`/`bottom` singly, because
 * the shared rule sets all four sides in one shorthand and a lone `bottom: 0`
 * would leave `top: 0` standing and stretch the band over the entire banner. */
.cta-banner::after {
    inset: auto 0 0 0;
}

/* --- 2. Care Club → cyan ---------------------------------------------------
 * Was the third of four near-identical navy blocks. Now the page's cyan
 * moment. The lightest gradient stop is #02749f so white body copy never
 * drops below 5.25:1.
 */
.care-club-section {
    background: linear-gradient(135deg, var(--grad-light) 0%, var(--brand-primary) 45%, var(--grad-deep) 100%);
}
.care-club-section.has-bg {
    background-color: var(--brand-cyan-deep);
}
/* Base paints this corner glow in the previous client's red (216,63,64).
 * Re-point it at the true brand cyan so it reads as a light source. */
.care-club-section::before {
    background: radial-gradient(circle, rgba(0, 170, 230, 0.28), transparent 70%);
}
/* Perk icons + subtitle in bright cyan-light rather than the brick orange,
 * which sat at 2.1:1 on the old navy. */
.care-club-perks i     { color: var(--brand-cyan-light); }
.care-club-perks       { background: rgba(255, 255, 255, 0.10); border-color: rgba(255, 255, 255, 0.22); }

/* --- 2b. Warm type on the cyan surface -------------------------------------
 * --brand-accent-on-dark is tuned for the navy sections (5.47:1 there) and
 * collapses to 2.51:1 on cyan, so the Care Club's eyebrow and subtitle need
 * their own stop. #ffecdf is the lightest warm tint that still reads as part
 * of the orange family and clears 4.5:1 against #02749f — the LIGHTEST point
 * of the section gradient, which is the worst case. It measures 8.29:1 at the
 * dark end. Both strings are small type, so AA-large would not have covered
 * them.
 */
.care-club-section .eyebrow-on-dark,
.care-club-subtitle {
    color: #ffecdf;
}

/* --- 3. The geometric CTA surface ------------------------------------------
 * The default background for the site's general CTAs: the full-bleed
 * `.cta-banner` and the compact mid-page `.cta-mini` card. Both get the same
 * treatment so the two asks read as one family.
 *
 * STRUCTURE — deliberately the same three-part construction as `.top-menu`
 * above, because the swoosh seam is this brand's pattern and a CTA drawn in
 * some other geometry reads as a different site.
 *
 *   background-color   the deep field, running the full width underneath
 *   layer 2 (bottom)   a hard-edged bright block, --cta-bright-w off the right
 *   layer 1 (top)      cta-swoosh.svg, butted against that block's left edge
 *
 * cta-swoosh.svg carries the same two path definitions as nav-swoosh.svg at
 * identical coordinates, so this is the nav's curve, not a lookalike.
 *
 * COLOUR. `.cta-banner` now takes the nav bar's three fills as well as its
 * geometry, which is what "align the CTA with the top nav" asked for:
 *
 *     nav   cyan field -> orange crescent -> navy field (links live here)
 *     CTA   navy field -> orange crescent -> cyan field (buttons live here)
 *
 * Mirrored, so the two navy fields sit at opposite ends of the page instead of
 * stacking. The band previously ran deep orange -> mid orange -> bright orange
 * to avoid merging into the navy footer below it; the swoosh seam and the hard
 * navy/cyan split now carry that break on their own.
 *
 * `.cta-mini` takes the same three fills in the same order, but tones down the
 * two it lets copy sit on: its bright end is --brand-cyan-deep rather than the
 * vivid --tm-cyan, and its crescent is the deep orange rather than #f58220.
 * Its copy crosses the seam at every width (see the note on that rule), and
 * white over #00b8ec is 2.32:1 while white over #f58220 is 2.59:1 — both
 * straight AA failures. The banner only gets away with the vivid pair because
 * --cta-bright-w guarantees its copy never reaches them.
 *
 * CONTRAST. --cta-bright-w is what keeps this legible: the bright block has to
 * begin to the RIGHT of where the copy ends, so the heading and body always sit
 * on --cta-deep. White on the old flat #ff7b2c measured 2.59:1, under the 4.5:1
 * AA floor and under even the 3:1 large-text floor; white on the navy --cta-deep
 * is 9.38:1, flat at every width because the field is a solid colour rather than
 * a ramp. Per-breakpoint measurements are tabulated at the end of this section —
 * re-measure if --cta-bright-w, the field colours or the container width change.
 */
.cta-banner,
.cta-mini {
    --cta-bright:     #00b8ec;   /* --tm-cyan — the field the crescent opens onto,
                                    and the field the buttons sit on */
    --cta-deep:       #004785;   /* --tm-navy — white on it: 9.38:1 — must match
                                    the rect fill inside cta-swoosh.svg */

    /* Width of the bright field, measured from the RIGHT edge — the same role
       --tm-navy-w plays on the nav bar, and the value the whole section's
       legibility hangs off.
       The two arms are the two layouts. Above the container cap the content is
       centred, so the copy's right edge tracks (100% - 1580px)/2 + ~960px and
       the seam has to clear it: 100% minus that is 50% - 210px. Below the cap
       the container is full-bleed with 24px padding and the copy's right edge
       stops tracking the viewport, so the seam is pinned 1010px off the left
       instead. min() takes whichever is further from the right, i.e. whichever
       keeps more deep field under the text. */
    --cta-bright-w:   min(calc(50% - 210px), calc(100% - 1010px));

    background-color: var(--cta-deep);
    background-image:
        url("../svg/cta-swoosh.svg"),
        linear-gradient(var(--cta-bright), var(--cta-bright));
    background-repeat: no-repeat, no-repeat;
    /* Tile keeps its aspect (auto), bright is a hard-edged block off the right. */
    background-size: auto 100%, var(--cta-bright-w) 100%;
    /* Tile's right edge butts exactly against the bright block's left edge. */
    background-position: right var(--cta-bright-w) center, right center;
    color: #ffffff;                 /* orange section -> white label, as directed */
}

/* The mini card takes the banner's fills verbatim — same navy field, same
 * vivid #f58220 crescent, same vivid #00b8ec bright end, same cta-swoosh.svg —
 * so the two asks read as one element rather than as a bright banner and a
 * muted card.
 *
 * Only --cta-bright-w differs, and it has to: the banner's
 * min(calc(50% - 210px), calc(100% - 1010px)) is written against the full
 * viewport and resolves NEGATIVE inside a 900px card, which invalidates the
 * whole background-size declaration and drops the artwork entirely. 26% is the
 * card-scale equivalent.
 *
 * CONTRAST, read this before touching the copy: the card centres its text in a
 * 900px box and the crescent tile alone is ~480px wide at this card height, so
 * there is no value of --cta-bright-w that clears the seam — white copy lands
 * on all three fills. Against the vivid pair that is 2.59:1 (#f58220) and
 * 2.32:1 (#00b8ec), both below the 3:1 large-text floor. The banner only gets
 * away with the same fills because it left-aligns and --cta-bright-w keeps its
 * copy on the navy. If the AA-safe variant is wanted back, this rule is the
 * only thing to revert: --cta-bright: #026b96 plus cta-swoosh-mini.svg, whose
 * crescent is #a8480f (5.84:1) and bright end #026b96 (5.92:1).
 *
 * Below 1024px the media query further down still swaps BOTH the banner and the
 * card onto the AA-safe mini artwork, so the two stay matched there too and
 * phone-width copy keeps its contrast. */
.cta-mini {
    --cta-bright-w: 26%;
}

/* Below 1024px `.cta-banner-inner` stops being a row and stacks, which turns a
 * 180px band into a ~290-380px one, and the copy then runs the full width of
 * the box — the same problem the mini card has at every width. So the banner
 * borrows the mini's solution here: the AA-safe tonal artwork, which the copy
 * can cross without a contrast cost. Dropping the artwork instead would leave
 * the motif on desktop only, which is where a pattern is least needed.
 *
 * The tile is also sized differently here. At `auto 100%` it would be ~760px
 * wide against a 390px viewport, so only the middle of the crescent lands in
 * frame and the band reads as two arbitrary colour blocks rather than a seam.
 * `100% 100%` squashes the whole motif into the band instead — the SVG carries
 * preserveAspectRatio="none", so it compresses rather than crops, and all three
 * fields stay visible in the right order. The curve steepens, which is the
 * correct trade: a steep seam still reads as the nav's shape, a cropped one
 * reads as nothing. With the tile covering the full width the separate bright
 * block has no work left to do, hence --cta-bright-w: 0.
 *
 * This block has to stay BELOW the `.cta-mini` rule above it. Both are a single
 * class, so they carry identical specificity and source order is what decides —
 * a media query does not win on its own. */
@media (max-width: 1023px) {
    .cta-banner,
    .cta-mini {
        --cta-bright:   #026b96;
        --cta-bright-w: 0px;
        background-image:
            url("../svg/cta-swoosh-mini.svg"),
            linear-gradient(var(--cta-bright), var(--cta-bright));
        background-size: 100% 100%, 0 0;
        background-position: center, right center;
    }
}

/* --- The mascot ------------------------------------------------------------
 * A transparent-backed cutout standing the full height of the band, to the left
 * of the copy. Markup lives in the child's template-parts/section-cta.php.
 *
 * Positioned against the SECTION rather than placed in the flex row, because
 * the band is `padding: 56px 0` and a flex child can only reach the content box
 * — it would come up 112px short of the height that was asked for. Absolute
 * with `height: 100%` against the section resolves to the padding box, i.e. the
 * band edge to edge.
 *
 * `left` tracks the container's inner edge so the dog lines up with the copy's
 * left margin instead of the viewport's: past the 1580px cap the container is
 * centred, below it the container is full-bleed and the 24px gutter is all
 * there is. max() picks whichever applies.
 *
 * `.cta-banner-inner` then reserves the width back as left padding, so the
 * heading starts clear of the dog rather than on top of it. The two values are
 * tied to the same custom property and have to move together.
 */
.cta-banner {
    --cta-photo-w:   clamp(88px, 9.5vw, 150px);
    --cta-photo-gap: clamp(16px, 1.8vw, 30px);
}
.cta-banner-photo {
    position: absolute;
    bottom: 0;
    left: max(24px, calc((100% - var(--container-max)) / 2 + 24px));
    width: var(--cta-photo-w);
    height: 100%;
    /* contain, not cover: the cutout is a whole animal and cropping it at the
       ears to fill a box is worse than leaving air either side of it. */
    object-fit: contain;
    object-position: bottom center;
    pointer-events: none;
    z-index: 1;
}
.cta-banner-inner {
    padding-left: calc(24px + var(--cta-photo-w) + var(--cta-photo-gap));
    position: relative;   /* keeps the copy above the mascot's z-index */
    z-index: 2;
}
/* Base leaves both children at their natural width, which was fine on a full
 * -width row but not once ~180px of it goes to the mascot. At 1280 the copy's
 * natural width plus the actions no longer fit, the actions wrapped to their own
 * line, and the band went from 181px to 264px — a 45% jump at one of the most
 * common laptop widths. Letting the copy column shrink and re-wrap instead keeps
 * the two on one line: it takes whatever is left after the actions, down to
 * 360px, and the actions stop being the thing that gives way.
 *
 * Three separate things are needed and each one was learned the hard way:
 *
 * `nowrap` is what actually holds the row together. Base sets `flex-wrap: wrap`,
 * and a wrap container moves an item to the next line rather than shrinking it,
 * so flex-shrink never got a chance to act — the actions kept dropping to their
 * own line and the band stayed 264px. With wrapping off the copy column is the
 * only thing that can give, which is what should give.
 *
 * `flex: 0 1 auto` on the copy, NOT `1 1 360px`. Allowed to GROW, it expands to
 * fill an ultrawide row and its right edge crosses --cta-bright-w into the cyan
 * field, where white measures 2.32:1 — an outright AA failure at 2560px and up
 * that the natural-width default never had. It only ever needs to shrink.
 *
 * Scoped to the row layout ONLY. Applied unconditionally, `flex: 0 0 auto` also
 * stops the actions shrinking on a phone, where they have to be free to stack —
 * it pushed the button pair to its max-content width and ran the phone number
 * off the right edge of a 390px screen. Below the breakpoint the base rules are
 * correct and are left alone.
 */
@media (min-width: 1024px) {
    .cta-banner-inner   { flex-wrap: nowrap; }
    .cta-banner-text    { flex: 0 1 auto; min-width: 0; }
    .cta-banner-actions { flex: 0 0 auto; }
}

/* Below the stacking breakpoint the band is ~380px tall and the viewport is
 * ~390px wide, so a full-height dog would be about as wide as the phone and
 * would sit under the entire copy block. It comes out, and the padding it was
 * reserving comes out with it — the same call the nav bar's chihuahua and the
 * Care Club mascot already make at their own breakpoints. */
@media (max-width: 1023px) {
    .cta-banner-photo { display: none; }
    .cta-banner-inner { padding-left: 24px; }
}

.cta-banner-text h2   { color: #ffffff; }
.cta-mini-heading     { color: #ffffff; }
/* Solid white, not the inherited opacity (0.92 on the banner, 0.85 from base
 * on the mini). Translucent copy over a gradient composites toward whatever is
 * behind it, so its contrast stops being a fixed number and starts depending on
 * where in the ramp the line happens to sit — the banner's 0.92 body measured
 * 4.56:1 against the background but only 4.11:1 once composited, i.e. it was
 * failing AA while appearing to pass. Full white is deterministic. */
.cta-banner-text p,
.cta-mini-subhead     { color: #ffffff; opacity: 1; }

/* Navy solid + white outline read as deliberate against these fields; the
 * default white-on-brick primary would vanish into the background. `.btn-accent`
 * is the orange fill — on these surfaces it has to be re-paired the same way as
 * `.btn-primary`, or the mini card's main action disappears. */
.cta-banner .btn-primary,
.cta-mini .btn-accent                 { background: var(--brand-primary-dark); color: #fff; border-color: var(--brand-primary-dark); }
.cta-banner .btn-primary:hover,
.cta-mini .btn-accent:hover           { background: var(--brand-ink); color: #fff; border-color: var(--brand-ink); }
.cta-banner .btn-secondary,
.cta-mini .btn-secondary              { background: #fff; color: var(--brand-primary-dark); border-color: #fff; }
.cta-banner .btn-secondary:hover,
.cta-mini .btn-secondary:hover        { background: var(--brand-primary-dark); color: #fff; border-color: var(--brand-primary-dark); }

/* THE BUTTONS NOW STRADDLE TWO FIELDS, so neither fill can delimit itself on
 * its own. --cta-bright-w is calculated to clear the COPY, not the actions
 * column: at 1280px the bright block starts 270px off the right while the
 * actions are ~450px wide, so the pair begins on navy and ends on cyan. Below
 * 1024px the whole band is navy behind them.
 *
 * Against the field each button has to keep 3:1 somewhere on its edge
 * (WCAG 1.4.11). Fill alone cannot do it — navy on the cyan field is 5.09:1 but
 * navy on the NAVY field is 1.26:1, and white is the mirror image at 9.38:1 and
 * 2.32:1. So each button carries a 2px border in the opposite value to its own
 * fill, and whichever field it lands on, one of the two edges holds:
 *
 *                       on navy field   on cyan field
 *   primary   fill navy      1.26          5.09   <- fill holds
 *             border white   9.38          2.32   <- border holds
 *   secondary fill white     9.38          2.32   <- fill holds
 *             border navy    1.26          5.09   <- border holds
 *
 * Base `.btn` sets `border: 0`, so the width has to be restated here — the
 * border-color declarations above render nothing without it. */
.cta-banner .btn-primary   { border: 2px solid #fff; }
.cta-banner .btn-secondary { border: 2px solid var(--brand-primary-dark); }
/* The mini card needs the same treatment, and needs it more: its three fills
 * are navy #004785, the toned crescent #a8480f and cyan-deep #026b96, and the
 * navy `.btn-accent` fill is 1.26:1, 2.02:1 and 1.99:1 against them — invisible
 * as a shape on all three, not just one. Its `.btn-secondary` is white-filled
 * and already clears 5.84:1 everywhere, so it only needs the ring for symmetry
 * with the banner. */
.cta-mini .btn-accent      { border: 2px solid #fff; }
.cta-mini .btn-secondary   { border: 2px solid var(--brand-primary-dark); }
/* Hover keeps a white edge on all four so none loses its outline on the navy
 * half — a navy button with a navy border there is invisible. */
.cta-banner .btn-primary:hover,
.cta-banner .btn-secondary:hover,
.cta-mini .btn-accent:hover,
.cta-mini .btn-secondary:hover { border-color: #fff; }

/* Keyboard focus has to clear both the orange field and the navy button, so
 * it is white with a dark outer ring rather than the sitewide accent colour —
 * an orange focus ring on an orange surface is invisible. */
.cta-banner .btn:focus-visible,
.cta-mini .btn:focus-visible {
    outline: 3px solid #fff;
    outline-offset: 3px;
    box-shadow: 0 0 0 6px rgba(17, 35, 55, 0.45);
}

/* The Care Club terms link ("Care Club Terms", under the fine print on the
 * mini card) is centred — which on this card is exactly where the orange
 * crescent passes. idiom-base paints it var(--brand-accent), so it was orange
 * type on the orange field and the words could not be read at all.
 *
 * White is the one value that holds across all three fills this card can put
 * behind it — navy #004785, crescent #a8480f and cyan-deep #026b96 — clearing
 * 5.84:1 at worst, the same reason `.cta-mini .btn-secondary` above is white.
 *
 * That leaves the base's `:hover { color: #fff }` a no-op, so the hover state
 * moves to the underline: half-strength at rest, solid and thicker on
 * hover/focus. The state change stays visible without colour carrying it. */
.cc-mini-terms-link a {
    color: #ffffff;
    text-decoration-color: rgba(255, 255, 255, 0.55);
}
.cc-mini-terms-link a:hover,
.cc-mini-terms-link a:focus-visible {
    color: #ffffff;
    text-decoration-color: #ffffff;
    text-decoration-thickness: 2px;
}

/* --- Measured contrast, section 3 -------------------------------------------
 * Sampled off the rendered page: for each text run, the LIGHTEST background
 * pixel under its bounding box (the worst case), against solid white type.
 * Both headings are bold and 22px or larger, so WCAG rates them large text at
 * a 3:1 bar; both body runs are 16px at a 4.5:1 bar. Every figure below clears
 * 4.5:1, i.e. even the headings meet the stricter body threshold.
 *
 *   .cta-banner            320    390    768   1023   1024   1600   3440
 *     h2                  5.84   5.84   5.84   5.84   9.38   9.38   9.38
 *     p                   5.84   5.84   5.84   5.84   9.38   9.38   9.38
 *
 *   .cta-mini              320    390    768   1023   1024   1280   3440
 *     .cta-mini-heading   5.84   5.84   5.84   5.84   5.84   5.84   5.84
 *     .cta-mini-subhead   5.84   5.84   5.84   5.84   5.84   5.84   5.84
 *
 * The figures are flat because the copy sits on flat fields rather than on a
 * gradient. 9.38:1 is the navy --cta-deep on its own: from 1024px up the banner
 * is a row and --cta-bright-w guarantees the copy never leaves that field, so
 * the vivid crescent and cyan never get under a letter. 5.84:1 is the worst of
 * the three fills in cta-swoosh-mini.svg (navy 9.38, crescent 5.84, cyan-deep
 * 5.92), which is what the copy can cross on the mini card at any width and on
 * the banner below the 1023/1024 stacking breakpoint.
 *
 * Non-text: the two button treatments and the 6px band are tabulated at their
 * own rules above; all clear the 3:1 bar of WCAG 1.4.11 on both fields.
 *
 * Re-measure if --cta-bright-w, the container max-width, or any fill inside
 * the two SVGs changes. The SVG fills are NOT reachable from CSS, so a change
 * there is invisible to anything that only reads this stylesheet.
 */

/* --- 4. Legibility fixes on the navy surfaces ------------------------------
 * Pre-existing: base points both of these at --brand-accent (#af4c12), which
 * is 2.11:1 on #0f3d59 — below AA by a wide margin. --brand-accent-on-dark is
 * the same hue family at 5.47:1.
 */
.eyebrow-on-dark,
.coupons-section .coupons-cta .btn-link {
    color: var(--brand-accent-on-dark);
}
.coupons-section .coupons-cta .btn-link:hover { color: #fff; }
/* .btn-accent (rest + hover) used to be corrected here, off the orange accent.
 * It now lives in the "CTA FILL SWEEP" block at the end of this file with every
 * other clickable accent fill — one place, one token pair. */

/* --- 5. Cyan as a working accent -------------------------------------------
 * Cyan previously appeared on zero pixels of the page. These are the
 * highest-frequency surfaces where it registers without touching body type.
 */
.trust-strip-grid  { border-top: 3px solid var(--brand-cyan); }

/* Centred layout — the icon stacks above the copy instead of sitting to its
 * left. The item is <i> + <div>, two flex children, so switching the axis to
 * column and centring both is all it takes. The base nudges the icon down 2px
 * to sit on the title's baseline, which is wrong once it is on its own row. */
.trust-strip-item {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 12px;
}
/* The `.svg-inline--fa` half of this selector is load-bearing, not belt-and-
 * braces. The FontAwesome Kit script (kit.fontawesome.com/…js) runs in SVG
 * mode: it deletes every <i class="fa-…"> and substitutes an
 * <svg class="svg-inline--fa">, so a rule written against `i` alone matches
 * nothing once the page is interactive. These icons were falling back to the
 * inherited 16px / #112337 — the reason they rendered small and grey instead of
 * 1.9rem cyan. FA sizes its SVG off `height: 1em`, so font-size still drives it.
 * Same fix already applied to `.intro-features li > i, … > svg` further down. */
.trust-strip-item i,
.trust-strip-item .svg-inline--fa {
    color: var(--brand-cyan-ink);        /* 4.90:1 on white */
    margin-top: 0;
    font-size: 1.9rem;                   /* a touch larger now that it leads */
}
.why-us-card i {
    --fa-secondary-color: var(--brand-cyan);
    --fa-primary-color:   var(--brand-primary-dark);
}
.awards-strip { border-top-color: var(--brand-cyan); border-top-width: 3px; }
.section-header .eyebrow { color: var(--brand-cyan-ink); }
/* ...but NOT on the photo bands. --brand-cyan-ink is tuned as text on white
 * (4.90:1); over the immersive band it lands at ~1.6:1. The rule above and the
 * base's `.section-header--on-dark .eyebrow` are both (0,2,0), so this sheet's
 * later position was silently winning on the dark variant too — undoing the
 * legibility work in section 4.
 *
 * White, not --brand-accent-on-dark. That token is calibrated against the base
 * theme's near-opaque navy (5.47:1), but the gradient rewriter in the child
 * functions.php intentionally drops the top stop to rgba(0,119,159,.641) so the
 * photograph reads through. Against that, #ff9a5c measures 3.65:1 here and
 * cannot exceed 4.51:1 even under a pure-black photo — there is no backdrop
 * that makes small orange text pass AA on this band. White measures 7.64:1 and
 * is photo-independent; the kicker still reads as a kicker off its uppercase /
 * letter-spaced / small-bold treatment.
 * Used by single-ppc_page.php (.repairs-showcase--immersive header). */
.section-header--on-dark .eyebrow { color: #fff; }

/* --- 6. Service-line colour coding — OPT-IN, was global ---------------------
 * The logo colour-codes HEATING / COOLING / PLUMBING, and mirroring that onto
 * the icons is a good idea WHERE EVERY ITEM IN THE SET IS A TRADE. It was
 * previously keyed on the bare FontAwesome glyph class (`.fa-fire { … }`) so it
 * would apply "anywhere an icon is used — nav, cards, perk lists — without
 * per-component rules". That is exactly what went wrong.
 *
 * WHY IT WAS PULLED BACK. Every icon on this site is `fa-duotone`, and FA paints
 * duotone from --fa-primary-color / --fa-secondary-color, NOT from `color`. A
 * container rule like `.repairs-checklist i { color: var(--brand-primary) }`
 * therefore could not win: it sets the wrong property. A bare `.fa-fire`
 * anywhere in the document out-ranked every component in the theme, so any grid
 * that happened to contain one trade glyph rendered it in the trade colour and
 * the rest in the component colour.
 *
 * That is not rare — it was the norm. An audit of all 812 authored icon sets
 * (ACF repeaters, every page + PPC page + service area) found 336 of them
 * mixed. The one that surfaced this: /heating/furnace-repair/, whose "What We
 * Repair" checklist is fire / fan / shield-check / sliders and rendered
 * #ff7b2c orange, #0079a6 cyan, navy, navy. Heating glyphs were the loud case
 * because --brand-accent is the bright fill orange, which the token table above
 * explicitly marks as unreadable as type (2.59:1 on white).
 *
 * WHAT IT BOUGHT. Nothing, as rendered. The Primary Menu's trade icon classes
 * are stored on the menu items but Idiom_Dropdown_Walker never emits them, and
 * the front page ships the photographic .services-image-grid rather than the
 * icon-card grid — so no rendered surface was getting the colour-coding, only
 * the outliers.
 *
 * HOW TO TURN IT BACK ON. It is preserved, not deleted: put `trade-coded` on a
 * container whose items are ALL trades and the three families resolve inside
 * it. The icon-card home services grid (fire / snowflake / faucet / water /
 * wind / building) is the case it was written for:
 *
 *     <div class="services-grid trade-coded"> … </div>
 *
 * Everywhere else icons now inherit `currentColor` from their component, which
 * is what the other ~90% of glyphs already did, so a grid reads as one set.
 */
.trade-coded .fa-fire, .trade-coded .fa-fire-flame-curved, .trade-coded .fa-fire-flame-simple,
.trade-coded .fa-temperature-arrow-up, .trade-coded .fa-temperature-high, .trade-coded .fa-temperature-full {
    --fa-primary-color:   var(--brand-accent);
    --fa-secondary-color: var(--brand-accent-bright);
    color: var(--brand-accent);
}
.trade-coded .fa-snowflake, .trade-coded .fa-air-conditioner, .trade-coded .fa-fan, .trade-coded .fa-wind,
.trade-coded .fa-temperature-arrow-down, .trade-coded .fa-temperature-low, .trade-coded .fa-temperature-empty {
    --fa-primary-color:   var(--brand-cyan-ink);
    --fa-secondary-color: var(--brand-cyan);
    color: var(--brand-cyan-ink);
}
.trade-coded .fa-faucet, .trade-coded .fa-faucet-drip, .trade-coded .fa-droplet, .trade-coded .fa-pipe-valve,
.trade-coded .fa-toilet, .trade-coded .fa-sink, .trade-coded .fa-shower, .trade-coded .fa-water {
    --fa-primary-color:   var(--brand-primary);
    --fa-secondary-color: var(--brand-cyan);
    color: var(--brand-primary);
}
/* On the dark sections the same glyphs need the light stops instead. Kept
 * because a `trade-coded` grid could be dropped into any of them; without the
 * opt-in class none of these selectors have anything to correct. */
.care-club-section .trade-coded .fa-snowflake, .care-club-section .trade-coded .fa-fan, .care-club-section .trade-coded .fa-wind,
.coupons-section .trade-coded .fa-snowflake,   .coupons-section .trade-coded .fa-fan,   .coupons-section .trade-coded .fa-wind,
.footer .trade-coded .fa-faucet, .footer .trade-coded .fa-droplet {
    --fa-primary-color:   var(--brand-cyan-light);
    --fa-secondary-color: var(--brand-cyan);
    color: var(--brand-cyan-light);
}

/* --- 7. Mascot slot --------------------------------------------------------
 * "Your Best Friend in Comfort" is the line on the trucks and the dog is the
 * most recognisable thing on the fleet, but no dog asset exists in the theme
 * or the media library yet.
 *
 * TO ACTIVATE: overwrite
 *   wp-content/themes/bcarlson-child/assets/imgs/mascot-dog.png
 * with a transparent cut-out of the dog and it appears. That path currently
 * holds a 1×1 transparent placeholder — deliberately, so the slot resolves to
 * a real 200 instead of throwing a 404 on every page load. Nothing paints, no
 * gap is reserved, no layout shift when the real photo lands.
 *
 * Sits at z-index 0 in the Care Club section's bottom-right, behind the perks
 * card (which the base gives its own stacking position), and is clipped by the
 * section's existing overflow:hidden so the dog reads as peeking in from the
 * corner. Hidden below 1100px where the two-column grid collapses and there is
 * no spare corner to peek from.
 */
.care-club-inner::after {
    content: "";
    position: absolute;
    right: -16px;
    bottom: -88px;
    width: 260px;
    height: 260px;
    background-image: url("../imgs/mascot-dog.png");
    background-repeat: no-repeat;
    background-position: bottom right;
    background-size: contain;
    pointer-events: none;
    z-index: 0;
}
.care-club-text,
.care-club-perks { position: relative; z-index: 1; }

@media (max-width: 1100px) {
    .care-club-inner::after { display: none; }
}

/* ===========================================================================
 * Financing plan cards — v3
 *
 * The v2 treatment inherited from the previous client read weak: a 1px border
 * over a 0 2px 6px/0.04 shadow is barely visible on white, and `auto-fit`
 * stretched two cards across the full container so neither felt like a
 * deliberate choice. The per-plan legal block also sat between the feature
 * list and the button, pushing the CTA below the fold and burying the offer.
 *
 * v3: fewer, denser, more committed cards. Real elevation, a solid accent
 * header band, the rate promoted to the hero number, and the CTA pinned to
 * the card foot. Legal moves to a single quiet block beneath the grid
 * (.plans-fine-print), which is where a disclosure belongs.
 * ======================================================================== */

/* Two offers should read as a considered pair, not a stretched row. */
.financing-block .financing-plans--v2 .plans-grid {
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    max-width: 900px;
    margin-inline: auto;
    gap: 28px;
    align-items: stretch;
}

/* --- Card shell ---------------------------------------------------------- */
.financing-block .financing-plans--v2 .plan-card {
    padding: 0;                       /* header band goes edge to edge */
    overflow: visible;                /* badge still hangs over the top edge */
    border: 1px solid var(--brand-border);
    border-top: 0;                    /* replaced by the header band below */
    border-radius: 14px;
    background: #fff;
    box-shadow: 0 4px 14px rgba(17, 35, 55, 0.08), 0 1px 3px rgba(17, 35, 55, 0.06);
    transition: transform 180ms ease, box-shadow 180ms ease;
}
.financing-block .financing-plans--v2 .plan-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 14px 32px rgba(17, 35, 55, 0.14), 0 2px 6px rgba(17, 35, 55, 0.08);
}

/* Solid brand band replaces the thin 4px rule — gives the card a real head. */
.financing-block .financing-plans--v2 .plan-card-head {
    background: var(--brand-primary);
    color: #fff;
    padding: 24px 26px;
    border-radius: 13px 13px 0 0;
    gap: 16px;
}
.financing-block .financing-plans--v2 .plan-card.is-featured .plan-card-head {
    background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-dark) 100%);
}
/* The plan name is the heading and now reads like one. It was 1.15rem against
 * a 1.5rem/800 .plan-rate underneath, so the paragraph outweighed the h3 and
 * the hierarchy read backwards.
 *
 * That inversion was a deliberate pricing-card move — promote the rate,
 * because "12.95% APR" is the number people scan for. It stopped being
 * defensible once the rate field started carrying prose: the second card now
 * reads "Promotional financing available", which is a sentence, not a figure,
 * so there is no number to promote and nothing justifying the extra weight.
 *
 * min-height reserves two lines so a one-line plan name still produces the
 * same header height as a two-line one — without it the two bands end at
 * different y positions and the pair reads as misaligned. Set in em against
 * this element's own line-height so it tracks the type instead of being a
 * magic pixel value, and dropped on mobile where the cards stack and the
 * reservation would just be a dead gap. */
.financing-block .financing-plans--v2 .plan-card h3 {
    color: #fff;
    font-size: 1.65rem;
    font-weight: 700;
    line-height: 1.28;
    min-height: 2.56em;   /* 2 × line-height */
}
/* Supporting line under the heading — same white, stepped down in size and
 * weight so it reads as the subhead it is. */
.financing-block .financing-plans--v2 .plan-rate {
    color: rgba(255, 255, 255, 0.94);
    font-size: 1.02rem;
    font-weight: 600;
    letter-spacing: 0;
    margin-top: 6px;
}
.financing-block .financing-plans--v2 .plan-icon,
.financing-block .financing-plans--v2 .plan-card.is-featured .plan-icon {
    background: rgba(255, 255, 255, 0.16);
    color: #fff;
    width: 48px;
    height: 48px;
    border-radius: 12px;
}

/* --- Card body ----------------------------------------------------------- */
.financing-block .financing-plans--v2 .plan-desc,
.financing-block .financing-plans--v2 .plan-features,
.financing-block .financing-plans--v2 .plan-card > .btn {
    margin-inline: 26px;
}
.financing-block .financing-plans--v2 .plan-desc {
    margin-top: 22px;
    margin-bottom: 0;
    color: var(--brand-muted);
    font-size: 0.95rem;
    line-height: 1.6;
}
.financing-block .financing-plans--v2 .plan-features {
    margin-top: 18px;
    border-top: 1px solid var(--brand-border);
    padding-top: 6px;
}
.financing-block .financing-plans--v2 .plan-features li {
    align-items: flex-start;
    gap: 10px;
    padding: 9px 0;
    font-size: 0.94rem;
    line-height: 1.5;
    border-bottom: 1px solid rgba(17, 35, 55, 0.06);
}
.financing-block .financing-plans--v2 .plan-features li:last-child { border-bottom: 0; }
.financing-block .financing-plans--v2 .plan-features i {
    color: var(--brand-accent-text);
    margin-top: 3px;
    flex-shrink: 0;
}

/* CTA pinned to the foot so both cards align regardless of bullet count.
 *
 * width:auto is load-bearing. The base sets width:100% on this button, which
 * resolves against the card's CONTENT box — then the inline margins above sit
 * outside that, so the button measured 434px + 52px of margin inside a 436px
 * card and hung ~25px past the right edge. The card is a column flex container,
 * so auto lets the default stretch size it, and stretch correctly subtracts the
 * margins. */
.financing-block .financing-plans--v2 .plan-card > .btn {
    margin-top: auto;
    margin-bottom: 26px;
    width: auto;
    justify-content: center;
    text-align: center;
}

/* --- Featured emphasis ----------------------------------------------------
 * No outline. The 2px orange ring plus an orange-tinted shadow made the
 * featured card look like a different component sitting next to the other one
 * rather than the same card called out. The shell is now identical to its
 * neighbor and the differentiation is carried entirely by the badge and the
 * gradient header band — which is enough, and reads as one considered pair. */
.financing-block .financing-plans--v2 .plan-card.is-featured {
    border: 1px solid var(--brand-border);
    border-top: 0;
    box-shadow: 0 4px 14px rgba(17, 35, 55, 0.08), 0 1px 3px rgba(17, 35, 55, 0.06);
}
.financing-block .financing-plans--v2 .plan-badge {
    top: -13px;
    right: 22px;
    background: var(--brand-accent);   /* bright fill — dark ink at 6.16:1 */
    color: var(--brand-accent-ink);
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 6px 14px;
    border-radius: 999px;
    box-shadow: 0 3px 8px rgba(175, 76, 18, 0.32);
}

/* --- Legal, moved out of the cards ---------------------------------------
 * The template renders disclosures as their own <section class="financing-
 * disclosures"> below the grid, which is the right place for it. Style that,
 * not the .plans-fine-print / .financing-disclaimer selectors I first guessed. */
.financing-disclosures .disclosures-inner {
    max-width: 900px;
    margin: 36px auto 0;
    padding: 20px 22px;
    background: var(--brand-bg-soft);
    border-radius: 10px;
    border-left: 3px solid var(--brand-border);
    color: var(--brand-muted);
    font-size: 0.8rem;
    line-height: 1.65;
}
.financing-disclosures .disclosures-eyebrow {
    display: block;
    margin-bottom: 8px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--brand-primary);
}
.financing-disclosures .disclosures-text { display: block; }

@media (max-width: 720px) {
    .financing-block .financing-plans--v2 .plans-grid { grid-template-columns: 1fr; gap: 22px; }
    .financing-block .financing-plans--v2 .plan-card:hover { transform: none; }
    .financing-block .financing-plans--v2 .plan-card-head { padding: 20px; }
    /* Cards are single-column here, so there is no neighbor to line the
     * header band up with — the reserved second line would just be a gap. */
    .financing-block .financing-plans--v2 .plan-card h3 { min-height: 0; }
    .financing-block .financing-plans--v2 .plan-desc,
    .financing-block .financing-plans--v2 .plan-features,
    .financing-block .financing-plans--v2 .plan-card > .btn { margin-inline: 20px; }
}

/* Respect reduced-motion (UX priority 7). */
@media (prefers-reduced-motion: reduce) {
    .financing-block .financing-plans--v2 .plan-card { transition: none; }
    .financing-block .financing-plans--v2 .plan-card:hover { transform: none; }
}


/* ---------------------------------------------------------------------------
 * Accent-filled button hovers.
 *
 * The base hardcodes #b5302f as the hover for every accent-filled button. While
 * these buttons were bright orange that was a jarring hue jump, so they were
 * re-pointed at the orange's own deeper shade; when the CTA fill went red,
 * #b5302f became its natural deeper shade and these went back to tracking
 * --cta-fill-hover. The fill is orange again, so the hue jump is BACK — the
 * fix is --cta-fill-hover itself (#ec6410 is the orange's own hover), not these
 * selectors, which are correct either way. Kept as an explicit rule (rather than
 * deleted) so the hover ink is pinned to white alongside it; the base pairs its
 * hover with --brand-accent-ink, and this child sets that token globally.
 * ------------------------------------------------------------------------- */
.header-cta .book-now:hover,
.mobile-menu-cta .book-now:hover,
.mobile-cta .book-now:hover,
.reviews-aggregate-cta .btn:hover,
.project-sidebar-cta .book-now:hover,
.post-sidebar-cta .book-now:hover {
    background: var(--cta-fill-hover);
    color: var(--cta-fill-ink);
}


/* ===========================================================================
 * Background gradients — all re-based on the primary #006697
 * ---------------------------------------------------------------------------
 * The base theme hardcodes the previous client's navy ramp
 * (#2e5382 -> #213a59 -> #0E1A30) into every hero and dark panel, so those
 * surfaces ignored the brand tokens entirely and stayed a different blue from
 * the rest of the site. Re-pointed here at the --grad-* / --brand-primary-*
 * scale, which is one hue stepped for lightness.
 *
 * Stop order is always light -> dark along the gradient axis, and the LIGHTEST
 * stop is --grad-light because white body copy sits on these; it is the last
 * stop where white still clears AA (5.07:1).
 * ========================================================================= */
/* ===========================================================================
 * SHARED HERO DECORATION — one recipe, every hero on the site
 * ---------------------------------------------------------------------------
 * WHAT THIS IS
 *
 * Every dark hero on the site was a flat slab of blue while every dark CONTENT
 * band under it (.cta-banner, .cta-mini, .coupons-section, .process-section,
 * .cc-benefits-section) already carried a concentric-ring motif and an accent
 * edge wash — so the heroes read as a plainer, different system from the page
 * beneath them. Per direction the ring motif is now the shared hero treatment,
 * pushed to where it is actually visible, and applied to every hero family
 * including the PPC offer heroes.
 *
 * Four layers, all borrowed from motifs the site already uses rather than
 * invented here — the point is to make the heroes one family, not to give any
 * one of them a look of its own:
 *
 *   1. accent edge wash   the same 90deg orange bleed the dark bands carry.
 *   2. corner light       a soft accent radial at the rings' own origin, so the
 *                         arcs read as contours around a light source instead of
 *                         as loose geometry. Generalised from the PPC heroes'
 *                         own .hero-offer-decor-glow, which is where the idiom
 *                         already existed.
 *   3. ring set           white concentric arcs anchored off the top-right
 *                         corner. Redrawn at 900x600 with six radii instead of
 *                         the content bands' 480x320/five, because at the band
 *                         size the arcs are lost in a ~620px-tall hero.
 *   4. dot grid           on ::before, so it can be masked. 24px pitch at 0.07 —
 *                         the pitch and alpha .hero-offer-decor-grid uses, so
 *                         the flat heroes and the PPC heroes share a texture.
 *
 * The layers live in custom properties so all five hero families consume the
 * SAME values off one definition. They differ only in which element is the
 * paint surface (see the three groups below), never in the recipe. Retune a
 * value here and every hero moves together — that is the entire reason for the
 * indirection, so resist restating a layer inline in any of the groups.
 *
 * ---------------------------------------------------------------------------
 * WHY THE NUMBERS ARE WHAT THEY ARE
 *
 * The treatment was originally tuned so faint (rings 0.025-0.06 at 1px, wash
 * 0.04, ramp travelling only #00779f -> #006697) that none of it survived
 * normal viewing distance. Three things carry the increase:
 *
 *   - THE ARCS ARE STROKED WITH AN SVG linearGradient rather than six flat
 *     stroke-opacities, and that is the whole trick. The gradient runs from the
 *     top-right origin (0.22 white, 4x the old peak, at 2px instead of 1px)
 *     down toward the bottom-left, fading to nothing. Brightness is therefore
 *     concentrated in the corner the copy never occupies and self-limits on its
 *     way in toward the headline — no mask, no second layer, no extra request.
 *     The url(%23...) fragment is an internal reference inside a self-contained
 *     SVG document, which is why it resolves in a data URI (external refs do
 *     not; keep the <defs> in the same file).
 *   - THE RAMP GAINS A THIRD STOP at --grad-deep, so a hero is modelled
 *     light-at-top to deep-at-bottom instead of flat. This is the largest
 *     perceptual change of the three and it costs nothing: white on #004a6b is
 *     9.58:1, so CTA rows and trust strips sit on MORE contrast, not less.
 *   - THE WARM LAYERS CARRY THE COLOUR, the white ones carry the structure.
 *     rgba(255,123,44,a) over #00779f is luminance-neutral (5.07:1 at a=0,
 *     5.09:1 at a=0.10, 5.07:1 at a=0.16), so orange is the one hue that can be
 *     pushed until it reads without moving the text ratio at all — and, for the
 *     same reason, without washing out the white arcs drawn over it. The wash
 *     is spread across 30% of the band so it stays ambient; the corner light is
 *     concentrated into 400px so it actually blooms. A wide low-alpha orange
 *     wash is invisible on saturated blue no matter what alpha you give it —
 *     concentration, not alpha, is what made this layer read.
 *
 * ---------------------------------------------------------------------------
 * THE CONTRAST CEILING — the one rule to preserve
 *
 * White body copy on --grad-light is 5.07:1, the tightest surface in any hero,
 * and that sets a single ceiling for the whole treatment: 0.07 is the highest
 * WHITE alpha allowed to land anywhere copy can reach. (Measured: 0.06 -> 4.56:1
 * and 0.07 -> 4.48:1 against white, so 0.07 is the last usable step and 0.09 —
 * 4.32:1 — is already out. An earlier revision of this comment put 0.07 at
 * 4.55:1; recomputed, it is 4.48:1.) Both white layers are held to it: the dot
 * grid is 0.07 flat and attenuated further by its mask, and the ring gradient,
 * though it peaks at 0.22, is down to 0.07 by 40% of its axis with only the
 * faded tail able to reach a headline.
 *
 * That is the design intent. The verification has to be empirical, not derived:
 * hand geometry over-predicted the risk here by roughly half a ratio point,
 * because a 2px stroke is antialiased across its width so no pixel ever reaches
 * the nominal gradient alpha.
 *
 * HOW TO RE-CHECK IT. Render a hero, make its text transparent (do NOT hide
 * elements — see below), screenshot, and take the brightest pixel inside the
 * glyph rects the copy occupied. Measured that way across 14 heroes x 5 widths
 * (390/768/1024/1366/1600), every hero on the site clears AA, tightest 4.68:1.
 * The decoration is not free but it is affordable: switching the rings, wash and
 * glow off moves the worst case up by 0.3-0.5 of a point on most heroes (worst
 * observed, the classic offer hero at 1366px: 6.99:1 with, 9.65:1 without).
 *
 * Two ways to get a false reading, both of which produced confident nonsense
 * here before being caught, so they are worth knowing:
 *
 *   - HIDING the copy with visibility:hidden takes the scrim with it on every
 *     photo hero, because the scrim lives on a negative-z-index CHILD
 *     (.hero-bg-overlay at -1, .contact-hero-bg at -2) and visibility inherits.
 *     /commercial-hvac/ scored 1.10:1 that way against 6.20:1 on the real page —
 *     i.e. it was being scored against its raw, unscrimmed photograph. Make the
 *     text transparent instead and leave every painted layer alone.
 *   - Sampling an element's BOUNDING BOX instead of its glyph rects samples that
 *     element's own border and padding. The ghost phone button is a transparent
 *     fill with a white border, so its box reported 2.14:1 on every hero at
 *     once — the tell that a probe is measuring chrome, not backdrop.
 *
 * Re-run the check, do not re-derive by hand, if any alpha, the ring clamp, or a
 * hero's copy width changes — and sanity-check the harness against a plain
 * screenshot of one page before trusting a number it gives you.
 *
 * No motion. A drifting backdrop is the .contact-hero-bg idiom this replaces,
 * but it sits behind an H1, so the intensity went into contrast instead of
 * movement — there is nothing here to guard with prefers-reduced-motion.
 * ========================================================================= */
:root {
    /* Ring artwork. Anchored top-right; six radii; gradient-stroked (see above).
       Sized fluidly rather than at a fixed 900px, and for legibility rather than
       neatness: at a fixed size it sits in the corner on a 1600px viewport but
       slides in behind the headline around 1000px, where a hero is barely wider
       than the artwork. At 62vw it always occupies about the same fraction of the
       band, so the arcs keep the same relationship to the copy at every width.
       `auto` for the height lets the SVG's own 3:2 ratio drive it.

       The 420px floor is a stroke-WEIGHT floor, not a composition one: the layer
       scales its 2px stroke down with itself, and at the old 300px floor on a
       390px phone the arcs rendered at 0.67px — sub-pixel, antialiased to
       nothing, which is why the motif read as absent on mobile specifically.
       420px keeps it near 0.93px. The 1000px ceiling is the widest the ring
       gradient's fade has been measured against. */
    --hero-ring-art: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='900' height='600' viewBox='0 0 900 600'%3E%3Cdefs%3E%3ClinearGradient id='hhr' x1='900' y1='0' x2='120' y2='600' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffffff' stop-opacity='0.22'/%3E%3Cstop offset='0.4' stop-color='%23ffffff' stop-opacity='0.07'/%3E%3Cstop offset='0.9' stop-color='%23ffffff' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cg fill='none' stroke='url(%23hhr)' stroke-width='2'%3E%3Ccircle cx='900' cy='0' r='150'/%3E%3Ccircle cx='900' cy='0' r='270'/%3E%3Ccircle cx='900' cy='0' r='400'/%3E%3Ccircle cx='900' cy='0' r='540'/%3E%3Ccircle cx='900' cy='0' r='690'/%3E%3Ccircle cx='900' cy='0' r='850'/%3E%3C/g%3E%3C/svg%3E");
    --hero-ring-size: clamp(420px, 62vw, 1000px);

    /* Warm layers. Ambient across the band; concentrated at the ring origin. */
    --hero-wash: linear-gradient(90deg, rgba(255, 123, 44, 0.10) 0%, transparent 30%, transparent 70%, rgba(255, 123, 44, 0.10) 100%);
    --hero-glow: radial-gradient(circle 400px at 100% 0%, rgba(255, 123, 44, 0.30) 0%, rgba(255, 123, 44, 0.11) 40%, transparent 72%);

    /* The ramp, and the scrim its photo-backed counterpart uses. The scrim's
       alphas are a legibility budget, not a taste call — see the photo-overlay
       group below. */
    --hero-ramp: linear-gradient(162deg, var(--grad-light) 0%, var(--brand-primary) 45%, var(--grad-deep) 100%);

    /* Photo scrim — CURRENTLY UNUSED ON THIS SITE, kept as insurance. It is
       consumed by the .hero-overlay rule in Group B, and the survey in that group
       found no template that renders .hero-overlay on a photo hero: the element
       exists only on the photo-LESS variant, where it is inert. So nothing on the
       site renders this today. It is defined anyway so that a future template
       which does render that element gets a brand scrim rather than the base
       theme's previous-client navy.

       Runs 115deg — densest at the LEFT, where the breadcrumb, eyebrow and H1 sit
       on these heroes, thinning to 0.44 at the right, which is the half of the
       frame a photograph would show through. That geometry is what the
       "Photo-backed overlays" note further down this file specifies; the rule
       that used to be here ran 162deg from 0.641, lightest at the TOP, which is
       exactly where the copy is. Derived, not measured, since there is nothing to
       measure: 0.88 over a blown-out white pixel is 8.34:1, and the 0.72 mid-stop
       holds 5.19:1 against white, covering the widest the copy column gets. The
       heroes that DO have a bright-photo problem are handled by --hero-top-veil
       and the two scrims below, which are measured. */
    --hero-photo-scrim: linear-gradient(115deg, rgba(0, 60, 86, 0.88) 0%, rgba(0, 60, 86, 0.72) 55%, rgba(0, 119, 159, 0.44) 100%);

    /* Top veil — every photo hero gets this, and only this, by default.
       The first ~22% of any hero carries the breadcrumb trail and the eyebrow,
       the smallest type in the band, and it is also where a photograph's sky
       blows out. Those two worst factors line up there, and the measurements say
       that strip is where photo heroes actually fail: switch this layer off and
       /careers/ drops to 3.48:1, the PPC ticket variant to 3.85:1 and
       /commercial-hvac/ to 4.40:1 at 390px, all on breadcrumb or eyebrow type.

       Deliberately a short top fade rather than a veil over the whole band. The
       heroes that don't need help really don't — /cooling/ac-repair/ measures
       7.09:1 with this and 6.89:1 without — so a full-band veil would have taken
       a third of every hero photograph on the site to fix one strip on three of
       them. It also reads as a vignette, which is what the top of a photo hero
       wants anyway. 0.50 rather than a value that merely clears the bar, because
       this strip's worst case is set by whatever photo an editor uploads next —
       the one place in this treatment worth over-provisioning. */
    --hero-top-veil: linear-gradient(to bottom, rgba(0, 60, 86, 0.50) 0%, rgba(0, 60, 86, 0.12) 22%, transparent 40%);

    /* Extra scrim for the two photo heroes whose picture is bright BEHIND THE
       COPY and not just at the top, so the veil above is not enough on its own.
       Without these two layers the offer card measures 3.79:1 on its lede and the
       care-club hero 3.55:1 on its H1; with them, 4.87:1 and 5.39:1.

       Both are additive over an inline ramp that is already there, which is why
       they open at 0.45/0.34 rather than the 0.88 the standalone scrim uses, and
       why the directional one ends fully transparent: past the copy column it must
       not take any more of the photograph than the inline ramp already does.

       Left-weighted, which is right for the offer card — its copy is a left column
       and never reaches the faded tail. */
    --hero-copy-scrim: linear-gradient(115deg, rgba(0, 60, 86, 0.45) 0%, rgba(0, 60, 86, 0.22) 55%, transparent 100%);

    /* Flat counterpart, for CENTERED copy. The care-club hero was given the
       left-weighted scrim above first, and measuring it across widths is what
       showed that to be wrong: its H1 is centered and runs x=24..876 in a 900px
       band — 95% of the width — so it ends in the tail where a directional scrim
       has faded out. No horizontal gradient can protect copy that spans the whole
       band, at any alpha; flat is the only correct shape. The lesson worth keeping
       is that this only showed up once the narrow widths were measured, so do not
       tune a hero like this at desktop width alone. */
    --hero-center-scrim: linear-gradient(rgba(0, 60, 86, 0.34), rgba(0, 60, 86, 0.34));

    /* Dot grid. The alpha does NOT move off 0.07 — it is the ceiling, and it is
       the one lever in this treatment that was already spent. So the dot GROWS
       instead: 1px -> 1.5px at the same pitch, which is 2.25x the coverage and a
       dot that survives being scaled on a hi-dpi screen, at an identical worst
       case — worst case depends on the alpha a dot lands at, not on how many
       pixels wide it is. The mask's outer ramp closes at 88% rather than 100% so
       the margins reach full density INSIDE the band instead of only at its very
       corners; the grid was previously still ramping up where the band ran out. */
    --hero-dots: radial-gradient(rgba(255, 255, 255, 0.07) 1.5px, transparent 1.5px);
    --hero-dot-pitch: 24px;
    --hero-dot-mask: radial-gradient(ellipse 64% 60% at 50% 46%, transparent 42%, #000 88%);
}

/* ---------------------------------------------------------------------------
 * GROUP A — heroes that own their background box.
 *
 * Photo-less heroes, so the four layers go straight on the section. These were
 * the flattest surfaces on the site: with no photograph and (until now) no
 * third ramp stop, several were a single unmodulated fill.
 *
 * .contact-hero and .cct-hero join the family here rather than keeping the
 * one-off backdrops they shipped with — the wavy-contour .contact-hero-bg and
 * the base's 160x48 wave tile. Those are neutralised below, so each hero ends
 * up with exactly one ring set and one glow.
 *
 * position: relative is stated for .project-hero and .cct-hero, which do not
 * get it from .hero, and which the ::before group needs to anchor against.
 * ------------------------------------------------------------------------- */
.hero-home:not(.has-bg),
.hero.hero-background:not(.has-bg),
.project-hero:not(.has-image),
.care-club-terms-page .cct-hero {
    position: relative;
    overflow: hidden;
    background-color: var(--brand-primary-dark);
    background-image: var(--hero-wash), var(--hero-glow), var(--hero-ring-art), var(--hero-ramp);
    background-repeat: no-repeat, no-repeat, no-repeat, no-repeat;
    background-position: center, center, top right, center;
    background-size: auto, auto, var(--hero-ring-size) auto, cover;
}

/* ---------------------------------------------------------------------------
 * The contact-hero family (/contact/, /about/, /careers/, /financing/) — a
 * Group A section with a Group B problem, so it gets a rule of its own.
 *
 * These heroes always render .contact-hero-bg, a full-bleed child at z-index -2,
 * and on SOME pages that child carries a photograph as an inline style. Anything
 * painted on the section itself therefore lands UNDERNEATH the photo: /careers/
 * had the ramp and the whole ring set hidden behind its technician photo, whose
 * bright areas measured 3.48:1 on an eyebrow at 390px.
 *
 * So the section keeps only the ramp — which is all that is needed on the pages
 * with no photo, and is harmlessly hidden on the ones with — and the decoration
 * moves up onto .contact-hero-bg's own ::before, which is above the photograph on
 * every page in the family. One rule, correct with or without a photo, and the
 * top veil rides along so a future photo upload cannot reopen the failure.
 * ------------------------------------------------------------------------- */
.contact-hero {
    position: relative;
    overflow: hidden;
    background-color: var(--brand-primary-dark);
    background-image: var(--hero-ramp);
    background-repeat: no-repeat;
    background-position: center;
    background-size: cover;
}
.contact-hero-bg::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image: var(--hero-wash), var(--hero-glow), var(--hero-ring-art), var(--hero-top-veil);
    background-repeat: no-repeat, no-repeat, no-repeat, no-repeat;
    background-position: center, center, top right, center;
    background-size: auto, auto, var(--hero-ring-size) auto, cover;
}

/* The family's two bespoke backdrops. Blanked rather than deleted because both
 * are rendered unconditionally by the base template and still carry the layout
 * and stacking the pages depend on — and .contact-hero-bg is now the anchor for
 * the ::before above, so it has to stay. Only their artwork is dropped: the wavy
 * contour tile, and the glow that would otherwise bloom twice.
 *
 * No !important on the tile, deliberately: on /careers/ that background-image is
 * an inline style carrying a real photograph, and forcing it off would delete
 * page content to remove decoration. The rule blanks the CSS-driven waves and
 * leaves inline photos alone, which is exactly the intended split.
 *
 * The drift animation goes with the tile it animated — nothing is left to move,
 * so the @media (prefers-reduced-motion) guard on it is now moot. */
.contact-hero-bg {
    background-image: none;
    animation: none;
}
.contact-hero-glow {
    background: none;
}

/* Group A heroes whose inner wrapper is a static block. A positioned ::before
 * at z-index 0 paints ABOVE the text of in-flow, non-positioned content, so
 * without this the dot grid would land on top of these two headlines. The .hero
 * families are already safe — the base gives .hero-content z-index 2, and
 * .contact-hero-content z-index 1. */
.care-club-terms-page .cct-hero-inner,
.project-hero-inner {
    position: relative;
    z-index: 1;
}

/* ---------------------------------------------------------------------------
 * GROUP B — photo-backed heroes. ONE surface: the section's own ::before.
 *
 * This group is deliberately keyed off .has-bg and nothing else, and the reason
 * is worth writing down because it is not obvious and it is easy to get wrong.
 *
 * There is no single overlay ELEMENT to paint on. Surveyed across the live site,
 * the photo heroes render three different arrangements:
 *
 *   .hero-background.has-bg  (every service page — the largest family here)
 *                            renders .hero-bg-overlay with the ramp INLINE
 *   .care-club-hero          renders no overlay at all; ramp inline on the section
 *   .hero-offer-* (PPC)      renders .hero-offer-decor (v2/ticket) or nothing
 *                            at all (card); ramp inline on the section
 *
 * and template-parts/hero-background.php's .hero-overlay — the element an
 * earlier revision of this block targeted — turns out to exist only on the
 * photo-LESS variant, where it is inert. Targeting it decorated nothing and
 * silently skipped every service page on the site. Inline styles also mean the
 * ramp itself is unreachable from here in all three arrangements.
 *
 * The section's ::before is the one surface that is always present, always
 * reachable, and always in the right place in the stack: above the photo (z-index
 * -2) and its inline overlay (z-index -1), below the copy (.hero-content is
 * z-index 2 from the base). So all photo heroes get exactly one rule, whatever
 * their template happens to render inside.
 *
 * The ramp stays the template's. That is not a compromise: the filter in
 * functions.php already rewrites those inline ramps to a 3-stop brand ramp with
 * real depth (see its $ramp table — matching it is why --hero-ramp has a third
 * stop), so the photo heroes and the flat ones agree without this file touching
 * them.
 *
 * No dot grid, deliberately: a photograph is already the texture, and the grid
 * over one reads as dirt rather than as pattern. The rings and the corner bloom
 * are what tie a photo hero to a flat one. (The PPC heroes keep the masked grid
 * they shipped with, below — it is part of that family's identity and it
 * measures clean, so removing it would be a gratuitous change.)
 * ------------------------------------------------------------------------- */
.hero.has-bg::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image: var(--hero-wash), var(--hero-glow), var(--hero-ring-art), var(--hero-top-veil);
    background-repeat: no-repeat, no-repeat, no-repeat, no-repeat;
    background-position: center, center, top right, center;
    background-size: auto, auto, var(--hero-ring-size) auto, cover;
}

/* The two photo heroes whose picture is bright BEHIND THE COPY, not just at the
 * top. Both were failing AA before this work — 3.55:1 on the care-club H1 and
 * 3.79:1 on the card's lede, measured by switching these layers back off. The
 * extra scrim goes last so it sits under the decoration and over the template's
 * inline photo+ramp, which is the only place a stylesheet can get between that
 * photograph and the copy on top of it.
 *
 * They take DIFFERENT scrim shapes because their copy is laid out differently —
 * the card's is a left column, care-club's is centered and spans nearly the whole
 * band. See --hero-copy-scrim / --hero-center-scrim; getting this wrong reads as
 * a pass at desktop width and a failure at 900px.
 *
 * .hero.has-bg is repeated in both selectors purely for specificity. Both heroes
 * carry it, so the group rule above is (0,2,0) while a bare .care-club-hero::before
 * is (0,1,0) — it loses, silently, and the hero renders with the group's stack and
 * no extra scrim at all. That is not a hypothetical: it is what happened here, and
 * the only symptom was a contrast number that moved the wrong way. */
.hero.has-bg.hero-offer-card::before {
    background-image: var(--hero-wash), var(--hero-glow), var(--hero-ring-art), var(--hero-top-veil), var(--hero-copy-scrim);
    background-repeat: no-repeat, no-repeat, no-repeat, no-repeat, no-repeat;
    background-position: center, center, top right, center, center;
    background-size: auto, auto, var(--hero-ring-size) auto, cover, cover;
}
.hero.has-bg.care-club-hero::before {
    background-image: var(--hero-wash), var(--hero-glow), var(--hero-ring-art), var(--hero-top-veil), var(--hero-center-scrim);
    background-repeat: no-repeat, no-repeat, no-repeat, no-repeat, no-repeat;
    background-position: center, center, top right, center, center;
    background-size: auto, auto, var(--hero-ring-size) auto, cover, cover;
}

/* The offer card leaves its content wrapper static, so it would paint beneath a
 * positioned ::before. Every other hero in this group is already safe — the base
 * gives .hero-content z-index 2. */
.hero-offer-card > .container {
    position: relative;
    z-index: 2;
}

/* Insurance, not current behaviour: if a template ever does render .hero-overlay
 * on a photo hero, it should carry the brand scrim rather than the base's
 * previous-client navy. Nothing on the site matches this today (see the survey
 * above), which is exactly why it is a one-liner and not a whole group. */
.hero.has-bg .hero-overlay {
    background-image: var(--hero-photo-scrim);
}

/* ---------------------------------------------------------------------------
 * GROUP C — PPC decor element: stand down, don't duplicate.
 *
 * .hero-offer-decor still exists on the v2 and ticket variants and still carries
 * this family's masked dot grid. But the rings, wash and glow now come from the
 * Group B ::before, which those heroes get too — so the decor layer's own
 * backdrop and its glow child would paint a SECOND ring set and a second bloom
 * on top of the first. Both are stood down here rather than left to fight.
 *
 * The grid keeps its own mask (concentrated at 82% 28%, behind the offer body)
 * and only takes the shared dot values, so this family's texture stays where the
 * base put it while matching every other hero's pitch and alpha.
 * ------------------------------------------------------------------------- */
.hero-offer-decor {
    background-image: none;
}
.hero-offer-decor-grid {
    background-image: var(--hero-dots);
    background-size: var(--hero-dot-pitch) var(--hero-dot-pitch);
}
.hero-offer-decor-glow {
    background: none;
}

/* ---------------------------------------------------------------------------
 * NOT DECORATED, on purpose — .hero-offer-split.
 *
 * The split hero has no exposed background band to carry the motif: an opaque
 * copy panel fills one half of it and the offer photograph fills the other,
 * edge to edge, so every layer in this treatment would land behind something
 * opaque. (It also carries neither .has-bg nor .hero-background, so it matches
 * no group above — that is the correct outcome here, not an oversight.) The
 * panel already ramps light-to-dark on the brand hues, which is what keeps it
 * reading as part of the same family. Verified by screenshot, not assumed.
 *
 * Also intentionally untouched: .page-hero / .archive-hero / .blog-hero /
 * .post-hero (e.g. /faq/). Those are LIGHT surfaces on --brand-bg-soft with navy
 * type, so white arcs and a white dot grid would be invisible on them and the
 * warm layers would only muddy them. A light-surface variant of this motif would
 * need its own colour pass — see the navy-on-white rings already used by
 * .contact-info-card::before further up this file for the idiom to follow.
 * ------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
 * Dot grid — Group A only, on ::before.
 *
 * Its own layer because it is the only one that needs a mask, and an element
 * carries one mask, not one per background layer. z-index 0 keeps it above the
 * section background and below the content; on the home hero it also stays
 * below the base's .hero-home::after bottom fade (positioned, z-index auto,
 * later in tree order, so it paints on top and is left untouched).
 *
 * The mask is the whole reason this is separate: transparent through the middle
 * 42% ellipse, opaque by the edges, so the texture lives in the margins of the
 * band and simply is not there behind the headline. Engines without mask support
 * render the grid across the full band, which is graceful degradation rather
 * than a break — a 1.5px dot on 24px centres is about 1.2% coverage, at 0.07.
 * ------------------------------------------------------------------------- */
.hero-home:not(.has-bg)::before,
.hero.hero-background:not(.has-bg)::before,
.project-hero:not(.has-image)::before,
.contact-hero::before,
.care-club-terms-page .cct-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image: var(--hero-dots);
    background-size: var(--hero-dot-pitch) var(--hero-dot-pitch);
    -webkit-mask-image: var(--hero-dot-mask);
            mask-image: var(--hero-dot-mask);
}

/* Below ~700px a hero narrows to roughly the width of its own copy, so there is
 * no margin left for the grid to live in — the mask's clear centre would cover
 * nearly the whole band and the few dots that survived would sit under the
 * headline rather than beside it. Dropped there; the fluid ring set and the warm
 * layers carry the decoration on small screens. */
@media (max-width: 700px) {
    .hero-home:not(.has-bg)::before,
    .hero.hero-background:not(.has-bg)::before,
    .project-hero:not(.has-image)::before,
    .contact-hero::before,
    .care-club-terms-page .cct-hero::before {
        display: none;
    }
}

/* Not heroes — kept on the plain two-stop brand ramp. */
.about-stats,
.award-medal {
    background-image: linear-gradient(165deg, var(--grad-light) 0%, var(--brand-primary) 48%, var(--brand-primary-dark) 100%);
}



/* ---------------------------------------------------------------------------
 * Photo-backed overlays — brand ramp, and thin enough to see the image.
 *
 * These are the two overlays the base exposes as real elements, so unlike the
 * inline hero gradients (handled by the rewrite filter in functions.php) they
 * are styled straight from here.
 *
 * The alpha is a legibility budget, not a taste call. White text over a scrim
 * has to survive the BRIGHTEST pixel the photo might put behind it, and the
 * light end of the ramp cannot do that at any usable alpha — #00779f at 0.85
 * still only reaches 3.90:1 against white. So the deep stop carries the text
 * and the light stop is spent where no text lands:
 *
 *   hero      gradient runs 115deg, densest at the left where the heading and
 *             breadcrumb sit (0.88 -> 6.99:1 worst case), thinning to 0.44 at
 *             the right, which is the half of the frame the photo shows through.
 *   service   card text is bottom-anchored, so the scrim is densest at the
 *             bottom edge and clears almost entirely by 70% height.
 *
 * text-shadow is the safety net for the middle band, where a blown-out sky can
 * still undercut the scrim. Cheap, and it costs nothing on a dark photo.
 * ------------------------------------------------------------------------- */
.hero.has-bg .hero-heading,
.hero.has-bg .hero-description,
.hero.has-bg .hero-eyebrow {
    text-shadow: 0 1px 3px rgba(0, 28, 41, 0.55);
}

.service-image-card-overlay {
    background: linear-gradient(
        to top,
        rgba(0, 60, 86, 0.90) 0%,
        rgba(0, 60, 86, 0.58) 40%,
        rgba(0, 102, 151, 0.18) 72%,
        rgba(0, 102, 151, 0.04) 100%
    );
}
.service-image-card:hover .service-image-card-overlay {
    background: linear-gradient(
        to top,
        rgba(0, 40, 58, 0.93) 0%,
        rgba(0, 40, 58, 0.68) 45%,
        rgba(0, 102, 151, 0.30) 80%,
        rgba(0, 102, 151, 0.10) 100%
    );
}
.service-image-card h3,
.service-image-card p {
    text-shadow: 0 1px 3px rgba(0, 28, 41, 0.5);
}

/* ---------------------------------------------------------------------------
 * Reviews page — hero breadcrumb + the unstyled intro block
 *
 * 1. BREADCRUMB. `.breadcrumb-list a` defaults to --brand-muted, a grey meant
 *    for light pages, and the white-on-dark treatment is opted into per page
 *    template (.project-hero, .ac-repair-page, .contact-hero-content). The
 *    our-reviews template is not on that list, so its trail rendered grey on
 *    the dark hero. Keyed off .hero instead of the template name, which is the
 *    actual condition that matters — any dark hero, including future ones.
 *
 * 2. INTRO BLOCK. A class-name collision in the base, not a missing style.
 *    `.reviews-aggregate` is defined there as a full-width stats BAND for the
 *    [idm-gpr-listing] shortcode — padding 48px 0, soft-grey fill, bottom rule
 *    — but page-our-reviews.php puts the same class on a bare <p>. The
 *    paragraph therefore inherited a section's chrome: a grey slab with the
 *    sentence stranded at its left edge. Scoping to .reviews-intro (0,2,0)
 *    outranks the band rule (0,1,0) and restores it to a rating callout,
 *    without touching the shortcode band that legitimately uses the class.
 * ------------------------------------------------------------------------- */
.hero .breadcrumb-list,
.hero .breadcrumb-list a,
.hero .breadcrumb-list .breadcrumb-current {
    color: rgba(255, 255, 255, 0.85);
}
.hero .breadcrumb-list a:hover,
.hero .breadcrumb-list a:focus-visible {
    color: #ffffff;
    text-decoration: underline;
}

.reviews-intro {
    padding: 56px 0 8px;
    text-align: center;
}
.reviews-intro .reviews-aggregate {
    /* Undo the band chrome inherited from the shortcode rule. */
    padding: 0;
    background: none;
    border-bottom: 0;
    margin: 0 auto;
    max-width: 48ch;
    color: var(--brand-muted);
    font-size: 1.05rem;
    line-height: 1.6;
}
/* <strong> wraps "4.7 ★" as a single unit in the markup, so it is promoted
 * whole rather than trying to colour the glyph separately. */
.reviews-intro .reviews-aggregate strong {
    display: block;
    margin-bottom: 6px;
    font-size: clamp(2.4rem, 5vw, 3rem);
    font-weight: 900;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--brand-primary-dark);
}
/* Body copy from the_content(), when the page has any. */
.reviews-intro .container > p:not(.reviews-aggregate) {
    max-width: 62ch;
    margin: 14px auto 0;
    color: var(--brand-muted);
    line-height: 1.7;
}

/* ---------------------------------------------------------------------------
 * Keyless map embed (see bcarlson_keyless_map() in functions.php).
 *
 * The base theme sizes .sa-map-wrap / .sa-city-map around a JS-rendered <div>,
 * so the <iframe> standing in for it needs its own box. aspect-ratio holds the
 * space before the lazy iframe loads, which keeps this off the CLS budget.
 * ------------------------------------------------------------------------- */
.bc-map-fallback {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-lg, 14px);
    box-shadow: 0 4px 14px rgba(17, 35, 55, 0.10);
}
.bc-map-fallback iframe {
    display: block;
    width: 100%;
    height: 100%;
    min-height: 340px;
    border: 0;
}
/* In the home/teaser grid the map is a column, so it stretches to the card. */
.sa-map-wrap.bc-map-fallback { min-height: 340px; height: 100%; }
/* On a city page it is a full-width band under its own heading. */
.sa-city-map-wrap.bc-map-fallback { aspect-ratio: 16 / 7; }
@media (max-width: 720px) {
    .sa-city-map-wrap.bc-map-fallback { aspect-ratio: 4 / 3; }
}

/* ===========================================================================
 * Care Club — annual value breakdown
 *
 * The base template renders cc_savings_items as a flat <ul> of label/value
 * pairs with no dedicated total row, so the final item IS the total. Style
 * :last-child as a total line: solid rule above (rather than the dotted
 * separators), heavier type, and no trailing separator.
 * ======================================================================== */
.cc-savings-list {
    list-style: none;
    margin: 22px 0 0;
    padding: 22px 24px;
    background: var(--brand-bg-soft);
    border-radius: 12px;
}
.cc-savings-list li {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 20px;
    padding: 14px 0;
    border-bottom: 1px dashed var(--brand-border);
}
.cc-savings-label { color: var(--brand-ink); }
.cc-savings-value {
    flex-shrink: 0;
    font-weight: 700;
    color: var(--brand-primary);
    font-variant-numeric: tabular-nums;   /* figures align down the column */
}

/* Final row = the total. */
.cc-savings-list li:last-child {
    margin-top: 4px;
    padding-top: 18px;
    border-bottom: 0;
    border-top: 2px solid var(--brand-primary-dark);
}
.cc-savings-list li:last-child .cc-savings-label {
    font-weight: 700;
    color: var(--brand-primary-dark);
}
.cc-savings-list li:last-child .cc-savings-value {
    font-size: 1.35rem;
    color: var(--brand-primary-dark);
}

.cc-savings-footnote {
    margin-top: 18px;
    color: var(--brand-muted);
    font-size: 0.92rem;
}

@media (max-width: 560px) {
    .cc-savings-list { padding: 18px 16px; }
    .cc-savings-list li { gap: 12px; }
    .cc-savings-list li:last-child .cc-savings-value { font-size: 1.2rem; }
}

/* ===========================================================================
 * FontAwesome SVG-replacement compatibility
 *
 * The site loads the FA kit as JavaScript (kit.fontawesome.com/…js), which runs
 * in the browser and REPLACES every <i class="fa-…"> with an <svg class="svg-
 * inline--fa">. Any CSS written against a bare `i` therefore stops matching
 * after hydration — the rule is correct in the stylesheet and simply never
 * applies to what is on screen.
 *
 * That is what broke the homepage "Our Promise" list. idiom-base sets:
 *     .intro-features li { grid-template-columns: auto 1fr; }
 *     .intro-features i  { grid-row: span 2; }
 * With the <i> gone, the icon no longer spans two rows, so auto-placement drops
 * the <span> description into column 1 row 2. Column 1 is `auto`, so it sizes
 * itself to the long description and squeezes the <strong> title into a sliver —
 * producing the one-word-per-line wrapping.
 *
 * Fix: place all three children explicitly and match BOTH `i` and `svg`, so the
 * layout is identical before and after FA hydrates. `.intro-features` is used by
 * section-promise, section-content-grid, page-service, page-guarantee,
 * page-service-details, single-service_area and single-ppc_page — so this repairs
 * the homepage, all trade hubs, all service-detail pages, the guarantee page and
 * every service-area page at once.
 * ======================================================================== */
.intro-features li {
    /* minmax(0,1fr) lets the text column shrink instead of forcing overflow. */
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 14px;
    row-gap: 4px;
}
.intro-features li > i,
.intro-features li > svg {
    grid-column: 1;
    grid-row: 1 / span 2;
    align-self: start;
    justify-self: center;
    width: 1.4rem;            /* an <svg> has no font-size-driven width */
    height: 1.4rem;
    font-size: 1.4rem;
    color: var(--brand-primary);
    flex-shrink: 0;
}
.intro-features li > strong {
    grid-column: 2;
    grid-row: 1;
    align-self: start;
    line-height: 1.35;
}
.intro-features li > span {
    grid-column: 2;
    grid-row: 2;
    line-height: 1.55;
}

/* `.repairs-checklist` is the same bug in the same shape, missed when
 * `.intro-features` was fixed above. Base builds each cell as
 * `grid-template-columns: auto 1fr` holding three children — icon, title,
 * description — and relies on `.repairs-checklist i { grid-row: span 2 }` to
 * hold the icon down column 1 so the title and description stack in column 2.
 * That selector dies the moment the FA kit swaps the <i> for an <svg>, the icon
 * falls back to a single cell, and the description wraps onto row 2 COLUMN 1 —
 * where the `auto` track sizes itself to the description and squeezes the title
 * into the 1fr remainder. Measured on Emergency AC Repair: a 76px title column
 * against a 248px icon column, which is the one-word-per-line wrapping.
 *
 * The cell is `li` normally and `li.has-link > a` when the card links out, so
 * both have to be covered or the linked cards keep the broken layout. */
.repairs-checklist li,
.repairs-checklist li.has-link > a {
    grid-template-columns: auto minmax(0, 1fr);
}
.repairs-checklist li > i,
.repairs-checklist li > svg,
.repairs-checklist li.has-link > a > i,
.repairs-checklist li.has-link > a > svg {
    grid-column: 1;
    grid-row: 1 / span 2;
    align-self: start;
    justify-self: center;
    width: 1.5rem;            /* an <svg> has no font-size-driven width */
    height: 1.5rem;
    font-size: 1.5rem;
    color: var(--brand-primary);
    flex-shrink: 0;
}
.repairs-checklist li > strong,
.repairs-checklist li.has-link > a > strong { grid-column: 2; grid-row: 1; align-self: start; }
.repairs-checklist li > span,
.repairs-checklist li.has-link > a > span   { grid-column: 2; grid-row: 2; }

/* The remaining bare-`i` icon rules that render on this site. Auditing the base
 * stylesheet turned up 18 rules setting layout on an `i` the FA kit deletes;
 * these are the three whose containers actually appear on a page here. Measured
 * before the fix, every one of them had `flex-shrink: 1` where base wrote
 * `flex: 0 0 auto`, and the Care Club icon really was collapsing — 19px wide at
 * 1600px, 12.4px at 900px, because a flex row was allowed to squeeze it.
 *
 * The `--fa-*` colour variables go on the CONTAINER, not the icon. Set on the
 * `i` they die with it; set on the parent they inherit onto whatever FA renders,
 * so the colours survive the swap without needing to name the element at all.
 * The child's own `.care-club-perks i` and `.why-us-card i` colour rules were
 * dead for exactly this reason — those icons were falling back to inherited
 * grey rather than the cyan and navy they were assigned. */
.trust-strip-item i,
.trust-strip-item .svg-inline--fa { flex-shrink: 0; }

.care-club-perks { --fa-primary-color: var(--brand-cyan-light); --fa-secondary-color: var(--brand-cyan-light); }
.care-club-perks i,
.care-club-perks .svg-inline--fa {
    color: var(--brand-cyan-light);
    font-size: 1.2rem;
    flex-shrink: 0;
}

.why-us-card {
    --fa-secondary-color:   var(--brand-cyan);
    --fa-primary-color:     var(--brand-primary-dark);
    --fa-primary-opacity:   1;
    --fa-secondary-opacity: 1;
}
.why-us-card i,
.why-us-card .svg-inline--fa {
    display: block;
    font-size: 2rem;
    margin-bottom: 12px;
    flex-shrink: 0;
}

/* Same class, same guard, for the other icon rules that target a bare `i`. */
.award-item svg          { color: var(--brand-primary); width: 2rem; height: 2rem; flex-shrink: 0; }
.footer-licenses .license-item > svg { width: 22px; height: 22px; }
.nav-links .sub-menu a svg,
.top-menu-links .sub-menu a svg,
.mobile-nav-links .sub-menu a svg,
.mobile-secondary-links a svg { width: 18px; height: 18px; }

@media (max-width: 600px) {
    .intro-features li > i,
    .intro-features li > svg { width: 1.25rem; height: 1.25rem; font-size: 1.25rem; }
}

/* ---------------------------------------------------------------------------
 * Top bar mascot (desktop only).
 *
 * The bar runs the full viewport width — the logo simply sits on top of it —
 * so the dog has to be offset past the logo column by hand rather than being
 * anchored to the bar's own left edge, which is at x=0 behind the mark.
 *
 * That offset is the left gutter plus the logo column: the grid is
 * `gutter | logo | menus | gutter` with the gutters collapsing to 0 once the
 * viewport drops below --container-max, which is what the max() handles. Using
 * the same two variables the grid itself is built from keeps the dog pinned to
 * the logo's right edge at every width instead of drifting.
 *
 * Sized by height to match the bar exactly and anchored to its bottom, with
 * aspect-ratio supplying the width from the asset's own 373x500 proportions —
 * no hardcoded pixel width to fall out of sync if the bar height changes.
 *
 * z-index 1 keeps it above the bar fill but under the logo (z-index 3), so the
 * mark always wins if the two ever overlap. Desktop only: .nav-wrapper is
 * display:none under 1100px, and the media query makes that explicit.
 *
 * DISABLED. Commented out rather than deleted so the offset maths above — the
 * one genuinely hard-won part of this — survives if it is ever switched back
 * on. To restore, delete the two wrapper lines below and nothing else.
 *
 * Note the rule block below deliberately carries no inline comments of its
 * own. CSS comments do not nest, so a comment opened inside it would close the
 * wrapper early and re-enable the back half of the rule.
 * ------------------------------------------------------------------------- */
/* -- top bar mascot disabled — remove this line and its pair to restore --
@media (min-width: 1101px) {
    .nav-wrapper .top-menu {
        position: relative;
    }
    .nav-wrapper .top-menu::after {
        content: "";
        position: absolute;
        bottom: 0;
        left: calc(
            max(0px, (100vw - var(--container-max, 1580px)) / 2)
            + var(--nav-logo-col)
            + 100px
        );
        height: 100%;
        aspect-ratio: 373 / 500;
        background: url("../photos/dog-chihuahua.webp") no-repeat bottom center;
        background-size: contain;
        pointer-events: none;
        z-index: 1;
    }
}
-- end top bar mascot -- */

/* ==========================================================================
 * SERVICE-AREA DETAIL — services grid icon colour  [REMOVED, see section 6]
 * --------------------------------------------------------------------------
 * This block used to pull the heating glyphs in the single-service_area
 * services grid back to --brand-primary, because the then-global trade colour
 * coding left fa-fire as the lone orange icon in a row of blues.
 *
 * It was the same bug section 6 now fixes at the source, just patched at one of
 * its ~336 sites. With the colour coding made opt-in, `.service-card-icon`
 * resolves to --brand-primary for every glyph in the grid on its own.
 *
 * Deleting it rather than leaving it inert also removes a second inconsistency
 * it introduced: it set --fa-secondary-color to --brand-cyan for heating glyphs
 * only, so those cards carried a navy/cyan duotone while every other card in
 * the same grid carried navy/navy-at-40%. All cards now match.
 * ========================================================================== */

/* ==========================================================================
 * FEATURED-OFFER HERO — "Offer Card" side panel + hero width
 * (template-parts/hero-offer-card.php, seen on /best/brand-summer-26/)
 * --------------------------------------------------------------------------
 * The white claim card on the right of this hero was hard to read. Two separate
 * causes, both worth stating because they fail in different ways:
 *
 * (a) TOKEN. The base built .hero-card-* around var(--brand-accent-ink,
 *     #0E1A30) meaning "the DARK ink that goes on an accent surface". The child
 *     re-points that token to #ffffff per the brand direction, which is right on
 *     the solid orange fills it was chosen for — but the card also uses it on a
 *     22%-opacity tint, where white has nothing to sit on (1.36:1).
 *
 * (b) FONT AWESOME DUOTONE. FA runs here in SVG-with-JS mode, so every <i> is
 *     replaced by <svg> at runtime and the duotone glyph is split into
 *     .fa-primary and .fa-secondary, the latter drawn at
 *     opacity: var(--fa-secondary-opacity, .4). On the glyphs where FA carries
 *     the whole shape as the SECONDARY path, that means the icon renders at 40%
 *     strength no matter what `color` says. Measured off a screenshot of this
 *     panel before the fix: perk checks rgb(160,167,175) ≈ 2.4:1 and filled
 *     rating stars rgb(210,171,149) ≈ 1.9:1, both under the 3:1 that WCAG 1.4.11
 *     asks of a non-text element carrying meaning. Setting
 *     --fa-secondary-opacity: 1 is what actually fixes those; `color` alone
 *     cannot.
 *
 * The <i>→<svg> swap also means any `i` selector is dead at runtime. Several of
 * the base's own rules here are inert for that reason and are called out below
 * rather than mirrored.
 *
 * Fixing --brand-accent-ink globally would drag every other accent surface back
 * to dark ink, so each affected part is re-pinned here instead. No new colours:
 * the fixes use --brand-ink, --brand-accent and --brand-accent-text, all already
 * declared at the top of this file.
 *
 * Verified in Chromium at 1600px against the live page; the ratios quoted below
 * are measured off rendered pixels, not off the declared values.
 * ========================================================================== */

/* --- 1. Eyebrow pill — the worst of it -------------------------------------
 * "Financing on New A/C Systems" was white text on rgba(216,63,64,.22) over the
 * white card. That tint resolves to roughly #f6d5d5, so the pill measured about
 * 1.25:1 — effectively invisible. The tint is also the previous client's red,
 * not a B. Carlson colour.
 *
 * Now a brand-orange tint carrying --brand-accent-text: 4.67:1, which clears AA
 * for the 0.72rem uppercase label. The bolt glyph is duotone and was rendering
 * at 40% (rgb(223,170,139)), so it gets full opacity to match the label. */
.hero-card-eyebrow {
    background: rgba(255, 123, 44, 0.16);
    border-color: rgba(175, 76, 18, 0.38);
    color: var(--brand-accent-text);
    --fa-secondary-opacity: 1;
}

/* --- 2. Price block ---------------------------------------------------------
 * Same leftover red, as a gradient tint plus border. Re-tinted to the brand
 * orange. The figures on it are --brand-primary-dark and --brand-ink, which
 * measure 10.2:1 and 15.1:1 on the resulting surface. */
.hero-card-price {
    background: linear-gradient(120deg, rgba(255, 123, 44, 0.18) 0%, rgba(255, 123, 44, 0.05) 100%);
    border-color: rgba(175, 76, 18, 0.32);
}

/* --- 3. Perk checkmarks -----------------------------------------------------
 * SUPERSEDED — see "PPC hero perk checks" near the end of this file, which now
 * paints the disc the base always intended on all three affected hero designs
 * at once. This block previously left the card's tick as a bare orange glyph on
 * the grounds that "a disc has never rendered here"; the client asked for the
 * disc, so the decision is reversed and the rule moved rather than left here to
 * fight the newer one from a different part of the cascade.
 *
 * The perk TEXT is unaffected either way — it lives in a sibling <span> and
 * stays --brand-ink at 15.9:1. */

/* --- 4. Rating stars --------------------------------------------------------
 * The stars are the only thing carrying the 4.7 rating visually, so they need
 * the 3:1 non-text floor. Two things were in the way:
 *
 *   - the base's #f5a623 is ~2.0:1 on the white card, and is not a brand value;
 *   - these are the partial-fill stars from mainstream_render_review_stars(),
 *     so the filled row is a .review-stars-fg layer whose colour cascades from
 *     this wrapper — but its duotone glyphs still rendered at 40%, landing at
 *     rgb(210,171,149) ≈ 1.9:1 even once the colour was right.
 *
 * Full opacity plus --brand-accent-text puts the filled stars at 5.43:1. The
 * .review-stars-bg row keeps its own rgba(15,23,42,.22) grey, which is what
 * makes the 4.7 partial fill legible as a fraction. */
.hero-card-rating-stars {
    color: var(--brand-accent-text);
    --fa-secondary-opacity: 1;
}

/* --- 5. Headline accent, left column ---------------------------------------
 * Not in the side box, but the same defect on the same hero: the base paints
 * the accent half of the headline --brand-accent (#ff7b2c).
 *
 * This hero is a photo under linear-gradient(162deg, rgba(46,83,130,.94),
 * rgba(11,28,64,.97)), so the worst surface any letter can cross is the light
 * stop over a blown-out photo pixel — about #3b5d8a. Measured there:
 *
 *   #ff7b2c (base) ......... 2.60:1   fails
 *   #ff9a5c (this fix) ..... 3.22:1   passes
 *
 * .hero-card-heading is Figtree 900 at clamp(2rem, 4vw, 3.1rem), which is WCAG
 * large text, so the AA floor is 3:1 rather than 4.5:1 — and the trust-row
 * icons below are non-text, same 3:1 floor under 1.4.11. Note this is a tighter
 * surface than the flat navies --brand-accent-on-dark was tuned against, so the
 * margin here is thin: re-measure if the gradient opacities ever drop.
 *
 * The underline shadow is re-tinted off the previous client's red at the same
 * time.
 *
 * The trust-row icons under it are deliberately NOT touched. The base points
 * them at --brand-accent via `.hero-card-leadtrust i`, which is inert (the <i>
 * is gone), so they fall through to the row's own rgba(255,255,255,.82) — white
 * at 82% on the navy, which measures better than any orange available here.
 * Repainting them accent would be a downgrade, so they stay as they render. */
.hero-card-heading-accent {
    color: var(--brand-accent-on-dark);
    box-shadow: inset 0 -0.13em 0 rgba(255, 154, 92, 0.32);
}

/* --- 6. Hero width ----------------------------------------------------------
 * Per direction, cap the featured-offer hero at 1280px instead of the sitewide
 * --container-max (1580px). Scoped to .hero-offer-takeover so all four hero
 * variants that share this template family — card, ticket, split and classic —
 * stay the same width as each other; the rest of the page keeps 1580px.
 *
 * .container's own max-width is a single class (0,1,0), so the descendant
 * selector below (0,2,0) wins without !important. The ticket variant's inner
 * .hero-deal-wrap is already capped at 920px and is unaffected. */
.hero-offer-takeover .container {
    max-width: 1280px;
}

/* ==========================================================================
 * FEATURED-OFFER HERO — "Coupon Ticket" price stub
 * (template-parts/hero-offer-ticket.php)
 * --------------------------------------------------------------------------
 * The tear-off stub on the left of the ticket shipped as a literal yellow —
 * linear-gradient(165deg, #FFF200, #F5D400) — left over from the base theme's
 * yellow-accent era, where the stub's ink token resolved to navy and the
 * pairing worked. In this child --brand-accent-ink is #ffffff, so the stub
 * rendered white type on bright yellow:
 *
 *   white on #FFF200 ... 1.17:1
 *   white on #F5D400 ... 1.47:1
 *
 * i.e. the price was very nearly invisible. No page currently uses the ticket
 * variant, which is the only reason this was not visible on the site — it would
 * have broken the moment a page was switched to it.
 *
 * Per direction the stub is now the brand accent. Same 165deg light-to-dark
 * structure, --brand-accent into --brand-accent-hover, so it reads as one piece
 * with the phone CTA rather than as a second, unrelated accent. Ink is
 * --brand-ink, the same pairing used on those buttons:
 *
 *   #112337 on #ff7b2c (light stop) ... 6.16:1  AA
 *   #112337 on #ec6410 (dark stop) .... 4.85:1  AA
 *
 * The dark stop is the binding one; every part of the stub clears AA on it.
 * ========================================================================== */
.hero-deal-stub {
    background: linear-gradient(165deg, var(--brand-accent) 0%, var(--brand-accent-hover) 100%);
    color: var(--brand-ink);
}

/* The base fades this label to opacity .82, which was fine as navy-on-yellow
 * but lands at 3.95:1 over the gradient's dark stop — under AA for 0.62rem
 * text. Full opacity puts it back on the 4.85:1 floor above. The amount and
 * label below it carry no opacity and need no equivalent rule. */
.hero-deal-stub-eyebrow {
    opacity: 1;
}


/* ==========================================================================
 * CTA FILL SWEEP — every accent-filled ACTION onto --cta-fill (#ff7b2c)
 * --------------------------------------------------------------------------
 * Routes every clickable accent-filled thing through --cta-fill instead of
 * --brand-accent. The two tokens hold the same orange again, so this sweep is
 * currently a no-op visually — it exists so the interactive set can be
 * recoloured in one place next time. idiom-base paints both from the single
 * --brand-accent token, so the split has to be made selector by selector — this
 * block is the complete list of INTERACTIVE surfaces, gathered by grepping
 * styles.css for `background: var(--brand-accent` and keeping the ones a
 * visitor can click, tap or focus.
 *
 * Deliberately NOT in this list (decorative, still orange): .offer-card::before
 * (coupon-card top rule), .plan-badge, .symptom-card::before,
 * .process-step-number, .diagnostic-offer-badge, .cc-col-badge,
 * .contact-channel-icon, .sa-map-dot--hq, .sa-services-list li::before,
 * .hero-deal-stub (above), and the trade-coded duotone glyph colours. Flip any
 * of those by adding it here.
 *
 * (.hero-offer-perk-check and .hero-*-perks i were on that exclusion list and
 * have since been flipped — see the offer-hero accent block below.)
 *
 * INK. Every rule below pins its label/glyph as well as its fill, because the
 * navy ink the orange required inverts on red — see the ratio table on
 * --cta-fill. Where the base already resolves ink from --brand-accent-ink (which
 * this child sets to #ffffff) the rule only swaps the fill.
 * ========================================================================== */

/* --- Generic accent button ------------------------------------------------ */
.btn-accent        { background: var(--cta-fill); color: var(--cta-fill-ink); }
.btn-accent:hover  { background: var(--cta-fill-hover); color: var(--cta-fill-ink); }

/* --- "Book Now" / schedule buttons: header, mobile bar, mobile menu ------- */
.header-cta .book-now,
.mobile-cta .book-now,
.mobile-menu-cta .book-now {
    background: var(--cta-fill);
    color: var(--cta-fill-ink);
}

/* --- Sidebar CTAs on posts and projects, and the reviews shortcode CTA ---- */
.project-sidebar-cta .book-now,
.post-sidebar-cta .book-now,
.reviews-aggregate-cta .btn {
    background: var(--cta-fill);
    color: var(--cta-fill-ink);
}
/* (hover for all five of the above is handled by the shared hover rule in the
   "Accent-filled button hovers" block earlier in this file.) */

/* --- Featured-offer hero: primary CTA + the ticket variant's phone button -
 * Both already take their ink from --brand-accent-ink, which is white here, so
 * only the fill moves. .hero-deal-phone's glyph chip is the inverse pair
 * (white disc, accent glyph) and follows the fill. */
.hero-offer-cta-primary,
.hero-deal-phone {
    background: var(--cta-fill);
}
.hero-deal-phone i {
    color: var(--cta-fill);
}

/* --- Offer-hero accent word + perk check discs ----------------------------
 * Both were the last two orange things left on a hero whose every other accent
 * is red — the starburst badge, the phone CTA and the coupon artwork.
 *
 * They were not a design choice, they were a half-migration. idiom-base wrote
 * these rules when its own --brand-accent WAS #d83f40, so it paired
 * `var(--brand-accent)` fills with hardcoded rgba(216, 63, 64, …) decoration on
 * the same element. This child re-points --brand-accent to the orange, so the
 * var half went orange while the hardcoded half stayed red, and each element
 * ended up wearing both:
 *
 *   .hero-offer-heading-accent   orange text  + rgba(216,63,64,.32) underline
 *   .hero-split-heading-accent   orange text  + rgba(216,63,64,.32) underline
 *   .hero-offer-perk-check       orange disc  + rgba(216,63,64,.22) drop shadow
 *
 * Moving the text and the disc onto --cta-fill makes each element one colour
 * again and matches the base's original intent, so the hardcoded halves need no
 * edit.
 *
 * SCOPE — why these three selectors and no more:
 *
 *   Both heading accents are flipped, not just the one class named, because they
 *   are the same rendered defect under two variant names and the site uses both:
 *   `split` on 31 pages, `offer` on 2. Flipping only `offer` would have left the
 *   orange/red split in place on 31 PPC pages.
 *
 *   `.hero-deal-heading-accent` is left alone — --brand-primary navy on the
 *   light ticket stub by design, not an orange leftover. `.hero-card-heading-
 *   accent` is left alone too: section 5 above already moved it to
 *   --brand-accent-on-dark to clear the 3:1 floor on that gradient.
 *
 *   Only ONE perk-tick selector HERE, because only one renders from an element
 *   the base can style. The base also wants 17px discs via `.hero-split-perks i`,
 *   `.hero-deal-perks i` and `.hero-card-perks i`, and all three are INERT — the
 *   FontAwesome kit replaces the <i> with an <svg> before they can apply.
 *   `.hero-offer-perk-check` is a <span>, which survives, which is why it is the
 *   one that shows and the one that was orange. The other three are revived in
 *   "PPC hero perk checks" near the end of this file.
 *
 * INK / CONTRAST.
 *   Accent word — RESOLVED by the revert. The measured background behind the
 *   word on the dark hero is #004460. The brick-red --cta-fill landed at 2.36:1
 *   there, under both the 4.5:1 small-text and the 3:1 large-text floor, and
 *   this comment used to flag it as a live regression on the most prominent
 *   line of a paid-traffic landing page. --cta-fill is back on #ff7b2c, which
 *   measures 4.07:1 on #004460 — AA large, and AA small is not required at this
 *   size. No --cta-fill-on-dark stop is needed after all.
 *
 *   Perk disc — went the other way. Its glyph is --cta-fill-ink white, which
 *   was 4.46:1 on the red and is 2.59:1 back on the orange. The rule is
 *   SUPERSEDED below (the disc is gone entirely), so nothing renders it today —
 *   but reinstating it would reinstate the failure. The underlines are
 *   decoration and need no change either way. */
.hero-offer-heading-accent,
.hero-split-heading-accent {
    color: var(--cta-fill);
}
/* SUPERSEDED — the disc is gone entirely; see "PPC hero perk checks" near the
   end of this file, which strips the background and repaints the bare tick.
   Kept so the reasoning above still reads against the rule it describes. */
.hero-offer-perk-check {
    background: var(--cta-fill);
    color: var(--cta-fill-ink);
}

/* --- Starburst price badge (the classic offer hero) -----------------------
 * .hero-offer-badge-burst is a 20-stop conic-gradient and .hero-offer-badge-inner
 * the disc inside it; both read var(--brand-accent, #d83f40). Rather than
 * restate that gradient here (30 lines that would then have to be kept in sync
 * with the base), --brand-accent is redefined for the badge subtree only, so the
 * serrated ring and the disc resolve to the CTA fill together and stay one
 * object. The inner's ink is --brand-accent-ink = white → 4.46:1 on the fill.
 *
 * The badge is aria-hidden decorative in the template, but the base fades its
 * eyebrow to .85 and its label to .92, which on red land at 3.6:1 and 4.0:1.
 * Full opacity costs nothing and puts both on the 4.46:1 floor. */
.hero-offer-badge {
    --brand-accent: var(--cta-fill);
}
.hero-offer-badge-eyebrow,
.hero-offer-badge-label {
    opacity: 1;
}

/* --- "What we repair" showcase links (accent on hover/focus) --------------
 * These sit on a dark panel and fill accent on hover with the base's
 * --brand-primary-dark label, which is 2.64:1 on red. Label and glyph go white.
 * The focus-visible outline is left on --brand-accent: a ring in a different hue
 * from the fill it surrounds is easier to see, and the orange is a known-good
 * value against this panel. */
.repairs-showcase-link:hover,
.repairs-showcase-link:focus-visible {
    background: var(--cta-fill);
    border-color: var(--cta-fill);
    color: var(--cta-fill-ink);
}
.repairs-showcase-link:hover i,
.repairs-showcase-link:focus-visible i {
    color: var(--cta-fill-ink);
}

/* --- Video playlist play button + carousel/modal controls -----------------
 * .video-playlist__play resolves its ink from --brand-accent-ink (white). The
 * two careers controls do not — they are white discs with a
 * --brand-primary-dark glyph that fill accent on hover, and navy on red is
 * 2.64:1, under the 3:1 floor 1.4.11 sets for an icon. Glyph goes white. */
.video-playlist__play {
    background: var(--cta-fill);
}
.careers-videos-nav:hover,
.careers-video-modal-close:hover {
    background: var(--cta-fill);
    color: var(--cta-fill-ink);
}


/* ==========================================================================
 * Framed offer photo as a CTA
 * (pairs with the child's template-parts/hero-offer-classic.php)
 * --------------------------------------------------------------------------
 * The classic hero's framed photo is now an <a href="#" data-st-scheduler="">
 * wrapping the existing .hero-offer-media-frame. The anchor is purely a hit
 * target — every visual (the 14px radius, the drop shadow, the -1deg tilt and
 * the un-tilt on hover) still belongs to the frame inside it, so the wrapper
 * only has to (a) not disturb layout and (b) be obviously focusable.
 *
 * display:block kills the inline baseline gap a wrapping <a> would otherwise add
 * under the figure. No `position` is set: the starburst badge is absolutely
 * positioned against .hero-offer-media, and giving the link a containing block
 * would pull it out of that corner.
 *
 * FOCUS. The photo is the largest interactive element in the hero, so the ring
 * has to survive both the navy band and whatever the photograph puts under its
 * edge — hence white at 3px with a 3px offset, plus a navy halo so the white ring
 * itself never disappears into a light photo. matching the frame's radius keeps
 * it hugging the image instead of boxing it.
 *
 * KEYBOARD PARITY. The base un-tilts the frame on .hero-offer-media:hover; the
 * same acknowledgement is given to :focus-visible so tab users get the identical
 * feedback a mouse user gets. Suppressed under prefers-reduced-motion, where the
 * ring alone carries it.
 * ========================================================================== */
.hero-offer-media-link {
    display: block;
    border-radius: 14px;
    color: inherit;
    text-decoration: none;
}
.hero-offer-media-link:focus-visible {
    outline: 3px solid #ffffff;
    outline-offset: 3px;
    box-shadow: 0 0 0 7px rgba(0, 28, 41, 0.55);
}
.hero-offer-media-link:focus-visible .hero-offer-media-frame {
    transform: rotate(0deg);
}
@media (prefers-reduced-motion: reduce) {
    .hero-offer-media-link:focus-visible .hero-offer-media-frame {
        transform: none;
        transition: none;
    }
}

/* ============================================================================
 * PPC hero perk checks — revive the disc the base draws for the <i>
 * ----------------------------------------------------------------------------
 * All 31 /best/* landing pages render a Featured-Offer hero in one of four
 * designs, and each draws its bullet list differently:
 *
 *   classic (2 pages)  <span class="hero-offer-perk-check"><i></span>   works
 *   split   (15)       <i class="fa-duotone fa-check">                  INERT
 *   ticket  (7)        <i class="fa-duotone fa-check">                  INERT
 *   card    (7)        <i class="fa-duotone fa-check">                  INERT
 *
 * idiom-base styles the last three as a 17px accent disc with a contrasting
 * check (`.hero-split-perks i`, `.hero-deal-perks i`, `.hero-card-perks i`), but
 * the FontAwesome kit runs in SVG-with-JS mode: it deletes each <i> and
 * substitutes <svg class="svg-inline--fa fa-check">, so those three rules match
 * nothing by the time the page is interactive. Measured live, the ticks were
 * bare glyphs — white-96% on split, #112337 on the ticket stub, orange on the
 * card — while the classic hero, whose marker is a <span> the kit leaves alone,
 * showed the disc. Only the design that happened to use a wrapper element was
 * correct.
 *
 * Re-pointing the rule at `.svg-inline--fa` is the fix, and it is the pattern
 * this file already uses for the trust strip, Care Club perks and why-us cards.
 *
 * WHY WIDTH AND HEIGHT ON AN <svg> IS SAFE HERE. FA sizes its SVG from
 * `height: 1em` and lets width follow the viewBox, so a square box would
 * normally distort the glyph. It does not: an <svg> with a viewBox defaults to
 * preserveAspectRatio="xMidYMid meet", so the check scales to fit and centres
 * itself inside the padding box instead of stretching. That is what turns the
 * padding into a ring of disc around a correctly-proportioned tick, and it is
 * why this does not need the flex centring the <i> rule used.
 *
 * COLOUR — per direction, the disc is gone and the marker is a bare tick.
 * The four designs do not share one surface, so the tick cannot share one
 * colour. Measured behind the perk list:
 *
 *   classic  #004460 dark hero   -> white   9.45:1
 *   split    #004460 dark panel  -> white   9.45:1
 *   ticket   #ffffff stub        -> white would be invisible; --cta-fill 4.46:1
 *   card     #ffffff offer card  -> white would be invisible; --cta-fill 4.46:1
 *
 * So: white on the two dark heroes, --cta-fill red on the two light ones. The
 * tick is decoration — the perk text states the meaning on its own — but an
 * invisible marker would leave the list looking mis-indented, which is why the
 * light surfaces keep a visible colour rather than inheriting white.
 *
 * The disc previously supplied the visual weight; without it the glyph grows
 * 17px-with-4px-padding (~9px of tick) to a full 16px so it still reads.
 * ========================================================================= */
.hero-split-perks .svg-inline--fa,
.hero-deal-perks .svg-inline--fa,
.hero-card-perks .svg-inline--fa {
    box-sizing: border-box;
    flex-shrink: 0;
    margin-top: 3px;
    width: 16px;
    height: 16px;
    padding: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    /* The kit ships these as duotone, whose secondary layer defaults to 40%
     * currentColor — that reads as a smudge behind the tick at this size. */
    --fa-primary-opacity: 1;
    --fa-secondary-opacity: 1;
}

/* Dark heroes — white tick. */
.hero-split-perks .svg-inline--fa {
    color: #fff;
    --fa-primary-color: #fff;
    --fa-secondary-color: #fff;
}

/* Light surfaces (ticket stub, offer card) — red tick. */
.hero-deal-perks .svg-inline--fa,
.hero-card-perks .svg-inline--fa {
    color: var(--cta-fill);
    --fa-primary-color: var(--cta-fill);
    --fa-secondary-color: var(--cta-fill);
}

/* Classic hero. Its marker is a <span> wrapper the FA kit leaves alone, so the
 * disc lives on the span and the base's own rule has to be undone here rather
 * than on the glyph. Sits on the dark hero, so white. */
.hero-offer-perk-check {
    width: auto;
    height: auto;
    min-width: 16px;
    border-radius: 0;
    background: none;
    box-shadow: none;
    color: #fff;
    font-size: 1rem;
    margin-top: 3px;
    --fa-primary-color: #fff;
    --fa-secondary-color: #fff;
    --fa-primary-opacity: 1;
    --fa-secondary-opacity: 1;
}

/* ============================================================================
 * Featured-offer hero — larger lede and perk type
 * ----------------------------------------------------------------------------
 * Both rules are the idiom-base originals restated in full, per this file's
 * convention of keeping a rule readable as one block at site level, with a
 * single value changed in each:
 *
 *   .hero-offer-lede      font-size clamp ceiling  1.05rem -> 1.2rem
 *   .hero-offer-perks li  font-size                0.95rem -> 1.1rem
 *
 * Nothing else differs from the base. If idiom-base ever retunes the colour,
 * spacing or line-height on either, the change will be masked here — diff
 * against styles.css before assuming these blocks are still a faithful copy.
 *
 * Both are paid-landing-page surfaces (the classic offer hero on /best/* and
 * the special-offer detail pages), so the larger type is a legibility call on
 * the pages with the most to lose from a skimmed hero. The perks list is a
 * two-column grid from four items up; at 1.1rem the longer B. Carlson perk
 * strings wrap to two lines in that grid rather than one, which is expected —
 * the list grows taller, it does not overflow.
 * ========================================================================= */
.hero-offer-lede {
    color: rgba(255, 255, 255, .92);
    font-size: clamp(.96rem, 1.2vw, 1.2rem);
    line-height: 1.5;
    margin: 0 0 14px;
    max-width: 54ch;
}

.hero-offer-perks li {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    color: rgba(255, 255, 255, .96);
    font-size: 1.1rem;
    line-height: 1.4;
}

/* ============================================================================
 * PPC hero — offer choice block ("this OR that, not both")
 * ----------------------------------------------------------------------------
 * Markup: template-parts/hero-ppc.php (child override). Fields: functions.php,
 * "Offer Choice" on the Page Hero group.
 *
 * The job is to make an either/or read as an either/or at a glance. Two devices
 * do that: the cards are the only LIGHT surfaces in a navy hero band, so they
 * read as one paired unit lifted out of the page; and a hard "or" token sits
 * between them rather than a gap, because a gap reads as "and".
 *
 * CONTRAST — sampled from the RENDERED page, not computed against the gradient
 * literal. This child re-tints the hero band (its first stop is
 * rgba(0,119,159,.619) — far more transparent than the base's .92), so the
 * effective backdrop is partly the photo behind it and cannot be derived from
 * the CSS alone. Values below are the brightest background pixel found
 * directly beneath each text run at 1440px, i.e. the worst case:
 *
 *   white              on rgb(33,92,116) ... 7.37:1  AA   (intro line)
 *   rgba(255,255,255,.82) on rgb(55,77,86) . 6.64:1  AA   (fine print)
 *   white              on #003c56 ......... 11.79:1  AA   (the "or" token)
 *
 * Re-measure rather than recalculate if the band gradient or the hero
 * background image changes — a lighter photo moves these numbers.
 *
 * And inside the card. NOTE the card is no longer solid white — it is
 * #ffffffe6, so these are measured against the COMPOSITE of that 90% white over
 * the band, ~rgb(233,238,240), not against #fff. Re-sample from the rendered
 * page if the alpha or the band changes; the solid-white numbers this block
 * used to quote were ~1 point higher across the board and would flatter a
 * surface that no longer exists:
 *
 *   --brand-ink     #112337 ........... 13.34:1  AAA  (the headline/number)
 *   --brand-muted   #5d6169 ............ 5.17:1  AA   (detail line)
 *   --brand-accent-text #af4c12 ........ 4.55:1  AA   (kicker + glyph)
 *
 * The kicker and glyph use --brand-accent-text, NOT the bright --brand-accent:
 * #ff7b2c is 2.59:1 on white and would fail outright at this size. The kicker
 * is the tightest run on the page at 4.55:1 — it is what pins the card alpha.
 *
 * NO hover or transition on the cards. They are not links — the offer is chosen
 * with a salesperson, and the hero already has exactly one action. A hover lift
 * here would advertise a click that does nothing. Nothing animates, so there is
 * no reduced-motion branch to write.
 * ========================================================================= */
.hero-choice {
    margin: 0 0 22px;
}

.hero-choice-intro {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin: 0 0 12px;
    color: #fff;                      /* 7.85:1 on the band */
    font-size: .95rem;
    font-weight: 700;
    line-height: 1.4;
}
.hero-choice-intro i {
    color: var(--brand-accent-on-dark); /* decorative only — see block header */
    flex: none;
}

/* card | or | card on wide; a single stacked column on narrow. `align-items:
   stretch` keeps the two cards the same height when one detail line wraps. */
.hero-choice-grid {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: stretch;
    gap: 12px;
}

.hero-choice-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
    /* Slightly translucent so the band reads through the card and it sits IN the
       hero rather than on top of it.

       THE ALPHA IS LOAD-BEARING, NOT DECORATION. It sets the surface the three
       text runs are measured against, and .90 is the FLOOR — the lowest value at
       which all three still clear AA. Measured on the rendered page at 1440px,
       worst-case pixel under each run (see the block header for the tokens):

         alpha   kicker   detail   headline
         1.00     5.43     6.21     15.92
          .90     4.55     5.17     13.34   <- here
          .85     4.13     4.69     12.01   kicker fails
          .69     2.95     3.35      8.64   kicker + detail fail

       Going lower needs darker tokens in the card, not just a lower alpha. */
    background: #ffffffe6;
    border: 1px solid rgba(255, 255, 255, .5);
    border-radius: var(--radius-lg, 16px);
    padding: 20px 22px;
    box-shadow: 0 10px 28px rgba(11, 28, 64, .28);
}

/* Duotone glyphs default to a 40%-opacity secondary layer, which on a white
   card renders as a washed peach. Which layer carries the visual mass depends
   on the glyph — outline icons (fa-percent) put it on the primary, filled ones
   (fa-badge-dollar) on the secondary — so any single opacity below 1 makes the
   icon's weight depend on which class the editor typed. Both layers are pinned
   to the accent colour at full opacity: it reads as a solid accent glyph and is
   legible for every icon, which matters more here than duotone shading. */
.hero-choice-icon {
    font-size: 1.5rem;
    line-height: 1;
    color: var(--brand-accent-text);   /* 5.43:1 on white */
    margin-bottom: 2px;
    /* Set on the SPAN, not the <i>. The FontAwesome kit replaces the <i> with
       an <svg>, so `.hero-choice-icon i {}` never matches at runtime — the same
       trap documented in the PPC hero perk-check block above. Custom properties
       set on the surviving wrapper are inherited by the injected svg. */
    --fa-primary-color: var(--brand-accent-text);
    --fa-secondary-color: var(--brand-accent-text);
    --fa-primary-opacity: 1;
    --fa-secondary-opacity: 1;
}

.hero-choice-kicker {
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--brand-accent-text);   /* 5.43:1 on white */
}

/* The number. Everything else in the card exists to frame this line. */
.hero-choice-headline {
    margin: 0;
    color: var(--brand-ink);           /* 15.92:1 on white */
    font-size: clamp(1.3rem, 2.1vw, 1.75rem);
    font-weight: 900;
    line-height: 1.15;
    letter-spacing: -.01em;
}

.hero-choice-detail {
    margin: 0;
    color: var(--brand-muted);         /* 6.21:1 on white */
    font-size: .9rem;
    line-height: 1.45;
}

/* The "or" token. A hard divider, not a gap — a gap between two offers reads
   as "you get both". Stretches full height on wide so it reads as a wall. */
.hero-choice-or {
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
}
.hero-choice-or::before {
    content: "";
    position: absolute;
    inset: 8px auto 8px 50%;
    width: 2px;
    transform: translateX(-50%);
    background: rgba(255, 255, 255, .28);
}
.hero-choice-or span {
    position: relative;
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--brand-primary-dark, #003c56);
    border: 2px solid rgba(255, 255, 255, .6);
    color: #fff;                       /* 11.79:1 on #003c56 */
    font-size: .74rem;
    font-weight: 800;
    letter-spacing: .06em;
    text-transform: uppercase;
}

.hero-choice-fine {
    margin: 10px 0 0;
    color: rgba(255, 255, 255, .82);   /* 5.90:1 on the band */
    font-size: .8rem;
    line-height: 1.45;
}

/* Stack below 620px. The divider flips to a horizontal rule so the "or" still
   sits between the two cards rather than beside them. */
@media (max-width: 620px) {
    .hero-choice-grid {
        grid-template-columns: 1fr;
        gap: 10px;
    }
    .hero-choice-or::before {
        inset: 50% 8px auto 8px;
        width: auto;
        height: 2px;
        transform: translateY(-50%);
    }
    .hero-choice-card {
        padding: 18px 18px;
    }
}


/* ============================================================================
 * PPC hero — full-bleed 50/50 image split (desktop only)
 * ----------------------------------------------------------------------------
 * Markup: template-parts/hero-ppc.php (child override). Restyles the base's
 * .hero-offer-* grid; no new classes, no template restructure.
 *
 * WHY
 * The base lays the PPC hero out as a 5fr/6fr container grid with the photo in a
 * tilted, rounded, drop-shadowed frame sized by its own aspect ratio. The copy
 * column on this campaign is tall (eyebrow → H1 → lede → the two offer-choice
 * cards → CTA row), so the photo — a 16:9 crop capped by a 5fr column — finishes
 * roughly 200px above the bottom of the band and leaves a wide slab of empty
 * teal under it. The hero reads as a picture parked in a coloured box.
 *
 * This flips it to the same construction as the featured-offer split hero
 * (.hero-offer-split on single-special_offer.php): two equal full-bleed columns,
 * photo edge-to-edge and top-to-bottom on one side, copy on the other. The photo
 * now takes its height from the copy instead of from its own aspect ratio, so
 * there is no leftover space to explain, and the two offer templates read as one
 * family.
 *
 * MIRRORED, NOT COPIED. The split hero puts its photo on the RIGHT; this one
 * keeps it on the LEFT because that is where the PPC hero has always had it and
 * moving it would change the page's composition for no reason the request asks
 * for. Everything directional is therefore flipped: the seam scrim runs 270deg
 * (darkening the photo's RIGHT edge, where it meets the panel) and the container
 * gutter is padded on the copy's RIGHT.
 *
 * CONTAINER ALIGNMENT — same technique as the "Split-hero copy" block near the
 * top of this file. The grid goes full-bleed (max-width/padding cleared), which
 * would otherwise let the copy drift to the raw viewport edge. Padding its right
 * side by max(24px, (100vw - container-max)/2 + 24px) lands the copy's outer
 * edge on the same vertical line as every other section on the page, at any
 * viewport, without wrapping anything in a second element.
 *
 * DESKTOP ONLY. Scoped to min-width: 901px, which is exactly where the base
 * stacks (its rule is max-width: 900px). Below that there is no 50% to take up:
 * the hero is one column, the base's centred 440px framed photo still reads
 * correctly, and every declaration here — absolute frame, stretched column,
 * full-bleed grid — would actively break it. Nothing below 901px is touched.
 *
 * SPECIFICITY. Doubled up as .hero-ppc.hero-offer-takeover so each rule outranks
 * the base pair it replaces (.hero-offer-takeover .hero-offer-media and friends
 * match at the same weight). The child sheet already loads last, so this is
 * belt-and-braces — but it means these rules survive an enqueue-order change.
 * ========================================================================= */
@media (min-width: 901px) {

    /* Band padding belongs to the copy column now — the photo must reach the
       top and bottom edges, and section padding would inset it. */
    .hero-ppc.hero-offer-takeover {
        padding: 0;
    }

    .hero-ppc.hero-offer-takeover .container.hero-content.hero-offer-grid {
        max-width: none;
        padding: 0;
        grid-template-columns: 1fr 1fr;
        gap: 0;
        /* stretch, not start: the photo column has to fill the row's height.
           The base top-aligns both items so the shorter one doesn't float. */
        align-items: stretch;
        /* Floor for short PPC heroes (no choice block, few perks) so the photo
           still has a band to fill. Same clamp as .hero-split-grid. */
        min-height: clamp(480px, 56vh, 600px);
    }

    /* ---- Photo column (left) ---------------------------------------- */
    .hero-ppc.hero-offer-takeover .hero-offer-media {
        grid-column: 1;
        grid-row: 1;
        align-self: stretch;
        position: relative;
        overflow: hidden;
        /* Shows through in the sliver before the photo decodes, and behind a
           portrait source that can't cover the column. */
        background: var(--brand-primary-dark, #003c56);
    }

    /* The frame stops being a frame. Absolute inset gives the <img> a fixed box
       to cover; the tilt, radius and drop shadow all read as "floating card",
       which is the opposite of what a full-bleed column is doing. The hover
       un-tilt goes with them — there is no tilt left to undo. */
    .hero-ppc.hero-offer-takeover .hero-offer-media-frame,
    .hero-ppc.hero-offer-takeover .hero-offer-media:hover .hero-offer-media-frame {
        position: absolute;
        inset: 0;
        border-radius: 0;
        box-shadow: none;
        transform: none;
        transition: none;
    }

    /* Base sets height:auto to honour the intrinsic ratio. Here the column
       dictates the box and the photo crops to it. */
    .hero-ppc.hero-offer-takeover .hero-offer-image {
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

    /* Seam scrim. Without it a bright right-hand edge on the photo butts
       straight into the navy panel and the join reads as a hard cut. Fades over
       the last quarter of the column, so the photo itself is untouched. */
    .hero-ppc.hero-offer-takeover .hero-offer-media::after {
        content: "";
        position: absolute;
        inset: 0;
        z-index: 1;
        pointer-events: none;
        background: linear-gradient(270deg, rgba(0, 40, 58, .34) 0%, transparent 24%);
    }

    /* Starburst badge / review disc — both are absolutely positioned against
       this figure. The base hangs them off the frame's corner at negative
       offsets (top/left -22px), which worked when the frame was inset from the
       band. Against a full-bleed column those offsets are outside the viewport
       and the section's overflow:hidden clips them away entirely. Bring both
       inside, and lift above the scrim. */
    .hero-ppc.hero-offer-takeover .hero-offer-badge,
    .hero-ppc.hero-offer-takeover .hero-ppc-rating {
        top: clamp(20px, 3vw, 36px);
        left: clamp(20px, 3vw, 36px);
        right: auto;
        z-index: 2;
    }

    /* ---- Copy column (right) ---------------------------------------- */
    .hero-ppc.hero-offer-takeover .hero-offer-body {
        grid-column: 2;
        grid-row: 1;
        align-self: center;
        padding-block: clamp(40px, 5vw, 72px);
        padding-left: clamp(28px, 4vw, 64px);
        padding-right: max(24px, calc((100vw - var(--container-max, 1580px)) / 2 + 24px));
    }
}

/* The offer-choice pair needs roughly 520px of copy width to hold two cards
   side by side without the headline ($1,700 Off / 0% for 60 Months) breaking
   across three lines. At 50% of the viewport minus gutters that runs out around
   1180px, so stack the cards below it and flip the "or" divider to a horizontal
   rule — the same treatment the 620px mobile rule applies, for the same reason,
   just triggered by the column being half-width instead of the screen being
   small. The hero simply grows taller; the photo grows with it. */
@media (min-width: 901px) and (max-width: 1180px) {
    .hero-ppc .hero-choice-grid {
        grid-template-columns: 1fr;
        gap: 10px;
    }
    .hero-ppc .hero-choice-or::before {
        inset: 50% 8px auto 8px;
        width: auto;
        height: 2px;
        transform: translateY(-50%);
    }
    .hero-ppc .hero-choice-card {
        padding: 18px 20px;
    }
}

/* ===========================================================================
 * Mobile CTA bar — re-docked from bottom of the viewport to above the header
 * ---------------------------------------------------------------------------
 * The base theme pins .mobile-cta (phone + Book Now, printed in footer.php) to
 * the BOTTOM of the screen at <=900px. Here it moves to the very top of the
 * viewport, with the fixed .mobile-header (logo + hamburger) pushed down to
 * sit beneath it, so the two read as one top chrome and the bottom of the
 * viewport is clear again. The markup stays in footer.php — position:fixed
 * makes its document location irrelevant, so the shared parent doesn't need
 * editing.
 *
 * Geometry: the bar is its two 48px full-bleed button segments (no tray
 * padding), then the 64px header = 112px of chrome. body padding-top reserves
 * that, and scroll-padding-top keeps in-page anchors (incl. the skip link)
 * from landing under it. The safe-area-inset-bottom pad and the footer's 96px
 * clearance both existed only because the bar sat at the bottom; the footer
 * returns to its base 24px. */
@media (max-width: 900px) {
    /* Full-bleed segmented action bar: the bar IS the two buttons — blue
     * phone segment left, accent book segment right, split by a 2px navy
     * seam. This restores the one CTA pattern the rest of the site teaches
     * (desktop header and both sidebars pair a blue-filled phone button with
     * the accent book button, in this order) instead of the one-off bare
     * text + pill treatment, and full-bleed segments let the bar shrink to
     * 48px while every tappable pixel stays a button. */
    .mobile-cta {
        top: 0;
        bottom: auto;
        padding: 0;
        background: var(--brand-primary-dark); /* shows only as the seam */
        box-shadow: none;
    }
    .mobile-cta-buttons {
        display: grid;
        grid-template-columns: 1fr 1fr;
        /* Seam doubles as a mis-tap buffer between adjacent targets. */
        gap: 2px;
    }
    .mobile-cta .phone-cta,
    .mobile-cta .book-now {
        min-height: 48px;
        padding: 0 12px;
        border-radius: 0; /* edge-to-edge segments, not floating pills */
    }
    .mobile-cta .phone-cta { background: var(--brand-primary); color: #fff; }
    .mobile-cta .phone-cta:active { background: var(--brand-primary-dark); }
    .mobile-cta .book-now:active { background: var(--cta-fill-hover); }
    /* Full-bleed segments clip a default outline; draw focus inside. */
    .mobile-cta .phone-cta:focus-visible,
    .mobile-cta .book-now:focus-visible {
        outline: 2px solid #fff;
        outline-offset: -4px;
    }
    .mobile-header { top: 48px; }
    body { padding-top: 112px; }
    html { scroll-padding-top: 128px; }
    .footer { padding-bottom: 24px; }
    /* The drawer overlay (z 1099) covers the bar (z 90) but slides UNDER the
     * header (z 1100), whose top offset it was built around: the base theme's
     * 80px scroll padding assumed the header at top:0. Clear the lowered
     * header (bottom edge 112px) instead so the drawer's own CTA row isn't
     * trapped behind it. The 0-48px strip above the open drawer's header is
     * the drawer's background, which is the same --brand-primary-dark as the
     * header, so the two read as one tall dark bar. */
    .mobile-menu-scroll { padding-top: 128px; }
}
/* Same admin-bar offsets the base theme gives .mobile-header — WP's toolbar
 * pushes the whole fixed stack down when logged in. */
@media (max-width: 900px) and (min-width: 783px) {
    .admin-bar .mobile-cta { top: 32px; }
    .admin-bar .mobile-header { top: calc(48px + 32px); }
}
@media (max-width: 782px) {
    .admin-bar .mobile-cta { top: 46px; }
    .admin-bar .mobile-header { top: calc(48px + 46px); }
}

/* ---------------------------------------------------------------------------
 * Drawer CTA row — same segmented treatment as the fixed top bar
 * ---------------------------------------------------------------------------
 * The base theme styles the drawer's phone + book row as bare accent text
 * beside a pill; restyled here as the identical full-bleed 48px segment pair
 * (blue phone / accent book, 2px seam) so the drawer and the top bar speak
 * one CTA language. Unscoped because .mobile-menu-cta only exists inside the
 * drawer, which the base theme already hides above 1100px. The seam shows
 * the drawer's own navy background through the grid gap. */
.mobile-menu-cta {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2px;
    padding: 0;
    border-bottom: 0;
}
.mobile-menu-cta .phone-cta,
.mobile-menu-cta .book-now {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 48px;
    padding: 0 12px;
    border-radius: 0;
    font-size: 0.95rem; /* one size across both segments (base: 1.05/0.9) */
    white-space: nowrap;
}
.mobile-menu-cta .phone-cta { background: var(--brand-primary); color: #fff; }
.mobile-menu-cta .phone-cta:hover { color: #fff; }
.mobile-menu-cta .phone-cta:active { background: var(--brand-primary-dark); }
.mobile-menu-cta .book-now:active { background: var(--cta-fill-hover); }
.mobile-menu-cta .phone-cta:focus-visible,
.mobile-menu-cta .book-now:focus-visible {
    outline: 2px solid #fff;
    outline-offset: -4px;
}

/* Logged-in drawer clearance. The WP toolbar pushes the fixed header down by
 * its own height, but the drawer's scroll padding was tuned for a logged-out
 * header, so the CTA row slid under the header for admins (base theme had the
 * same bug at its original 80px). Header bottom edge per range: toolbar
 * (32/46px) + bar (48px, <=900px only) + header (64px); pad 16px past it. */
@media (max-width: 1100px) and (min-width: 901px) {
    .admin-bar .mobile-menu-scroll { padding-top: calc(80px + 32px); }
}
@media (max-width: 900px) and (min-width: 783px) {
    .admin-bar .mobile-menu-scroll { padding-top: calc(128px + 32px); }
}
@media (max-width: 782px) {
    .admin-bar .mobile-menu-scroll { padding-top: calc(128px + 46px); }
}

/* ============================================================================
 * PPC hero — offer choice block, "both" mode
 * ----------------------------------------------------------------------------
 * Markup: template-parts/hero-ppc.php, when the page's "Offer Choice —
 * Relationship" field is set to `both`.
 *
 * Same two cards, different claim. The default block is an either/or: a hard
 * "or" token sits between the cards because a plain gap between two offers
 * reads as "you get both". Here the customer DOES get both — the two offers
 * cover different products (the Sell-A-Thon runs AC financing alongside water-
 * heater financing) and nothing about taking one rules out the other. So the
 * token and its divider come out, and the grid loses the middle `auto` track it
 * occupied.
 *
 * That is the entire visual difference. Cards, spacing, type and contrast are
 * untouched, so the Comfort Cash and Sell-A-Thon heroes still read as one
 * family — which is the point: the reader learns the card shape once, and only
 * the relationship between the cards changes.
 *
 * SPECIFICITY / SOURCE ORDER. These rules are appended AFTER the base block on
 * purpose. `.hero-choice--both .hero-choice-grid` is 0,2,0 and would otherwise
 * beat the 0,1,0 stacking rule inside the 620px media query no matter where it
 * sat, leaving the cards side by side on a phone. Each stacking breakpoint is
 * therefore restated here at matching-or-higher specificity rather than relying
 * on the cascade to sort it out.
 * ========================================================================= */
.hero-choice--both .hero-choice-grid {
    grid-template-columns: 1fr 1fr;
}

/* Restates the base 620px stack: equal specificity (0,2,0), later in source. */
@media (max-width: 620px) {
    .hero-choice--both .hero-choice-grid {
        grid-template-columns: 1fr;
    }
}

/* Restates the half-width-column stack from the 50/50 hero split. 0,3,0, so it
   outranks the wide-mode rule above at every viewport in the range. */
@media (min-width: 901px) and (max-width: 1180px) {
    .hero-ppc .hero-choice--both .hero-choice-grid {
        grid-template-columns: 1fr;
    }
}
