/* ==========================================================================
   PCRS × Divi — COMPAT layer.  Loads AFTER pcrs-design.css.

   Only things that target Divi's OWN chrome and box model live here, so this
   file can never out-rank a mockup rule. Anything touching a .ccmm-* element
   the mockups style belongs in pcrs-divi-reset.css instead — see the note at
   the top of that file.

   Per SERVICE-PAGE-PIPELINE section 6: an override block may fix breakage,
   never restyle.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Divi's native header and footer.
   The approved design brings its own top bar, nav and footer via global
   modules 1128 / 1173 / 1165, which the child theme injects as
   #custom_header, #custom_header_mobile and an et_after_main_content footer.
   Divi's #main-header would render a SECOND navigation right below ours.
   -------------------------------------------------------------------------- */
#main-header,
#top-header,
#et-secondary-nav,
#main-footer,
#et-footer-nav,
.et_slide_in_menu_container { display: none !important; }

/* Divi reserves scroll offset for a fixed header that no longer exists. */
#page-container { padding-top: 0 !important; margin-top: 0 !important; }
.et_non_fixed_nav.et_transparent_nav #page-container,
.et_fixed_nav #page-container { padding-top: 0 !important; }

/* Note: do NOT add a blanket `.et_animated { animation: none !important }`
   here. It was tried to force Divi's animated modules visible, and it threw
   the template pages' layout apart — Divi's animation classes also carry the
   module's horizontal placement, so cancelling the animation dragged headings
   off the left edge of the page. Bisecting showed the animated modules render
   correctly on their own; hiding #main-header alone causes no problem. */

/* --------------------------------------------------------------------------
   1b. Heading COLOUR only.
   Divi's customizer emits `h1,h2,h3,h4,h5,h6 { color:#1d2145 }` into an
   inline <style> block in the document head. Inline blocks come after every
   linked stylesheet, so the zero-specificity :where() reset cannot reach it
   on load order alone — this rule needs a real class to outrank it.

   COLOUR AND NOTHING ELSE. The mockups' type scale is written with bare
   element selectors (`h1 { font-size: … }`), so adding font-size, weight or
   line-height here would beat the mockups too — that is precisely the bug
   that collapsed every heading to 16px on the first attempt. Everything else
   stays in pcrs-divi-reset.css at zero specificity.

   The customizer value itself is correct for the ~35 pages still on the
   template layout, so it is left alone rather than changed globally.
   -------------------------------------------------------------------------- */
.ccmm-sec h1, .ccmm-sec h2, .ccmm-sec h3,
.ccmm-sec h4, .ccmm-sec h5, .ccmm-sec h6,
.ccmm-topbar h1, .ccmm-topbar h2, .ccmm-topbar h3,
.ccmm-header h1, .ccmm-header h2, .ccmm-header h3,
.ccmm-footer h1, .ccmm-footer h2, .ccmm-footer h3,
.ccmm-footer h4, .ccmm-footer h5, .ccmm-footer h6,
.ccmm-form-card h1, .ccmm-form-card h2, .ccmm-form-card h3,
.tier2-main h1, .tier2-main h2, .tier2-main h3, .tier2-main h4,
.tier2-sidebar h2, .tier2-sidebar h3 {
	color: inherit;
}

/* --------------------------------------------------------------------------
   1c. Heading SIZE.
   The template's Divi Theme Options carry a hand-written CSS block that hard
   codes heading sizes, emitted inline in the head:
       h1 { font-size: 45px }
       h2 { font-weight: 800; font-size: 32px !important }
       h3 { font-size: 32px }
   Those flatten the mockups' fluid clamp() scale — the hero dropped from
   65.6px to 45px and section headings from 54.4px to 32px at 1440.

   These declarations RESTATE the mockups' own token values verbatim; they
   introduce no new sizing decision. `!important` appears only where the
   template's block used it, and everything is scoped to the PCRS blocks so
   the ~35 pages still on the template layout keep their existing type.
   -------------------------------------------------------------------------- */
.ccmm-sec h1, .ccmm-hero h1 {
	font-size: var(--wp--preset--font-size--hero) !important;
}
/* `.ccmm-footer h2` was in this selector until 2026-08-14. It was harmless for
   as long as the footer's column headings were h3 — the rule matched nothing.
   Promoting them to h2 (to stop an h1 -> h3 skip on every thin page) armed it,
   and the five footer headings jumped from 1rem to the 54.4px section size.
   The footer sets its own heading size in pcrs-design.css; it must not be swept
   into the section scale. A defensive selector for markup that does not exist
   yet is a trap for whoever creates that markup later. */
.ccmm-sec h2 {
	font-size: var(--wp--preset--font-size--section) !important;
}

/* --------------------------------------------------------------------------
   1c-ii. Long-heading guard (2026-08-19).
   The section token is keyed to vw: clamp(1.9rem, 4.6vw, 3.4rem). That suits a
   full-bleed heading, but every band head sits inside .ccmm-head, which is
   capped at 768px full-width and only ~536px when it shares a .ccmm-grid2 row
   with a photo. The column never grows, so a wider viewport pushed the type
   toward its 54.4px ceiling while the container stayed fixed - about 11
   characters per line. The SEO copy delivered 2026-08-18 runs 84-107 characters
   per h2, so real pages stacked 8-10 lines of display type (carpet 84ch/8,
   home 91ch/8, contact 107ch/9, reviews 104ch/10).

   Capped at 2rem/32px, which is also the size the template's own Divi block
   wanted - so this reasserts the template's intent for constrained heads while
   leaving full-bleed hero h1 on the mockups' scale.

   It lives HERE, not in pcrs-design.css, because the rule above is
   `!important` and (0,1,1); an equal-specificity rule in the earlier layer
   loses on load order. This one is (0,2,1) + !important so it wins reliably.
   Scoped to .ccmm-head ONLY - do not broaden it. A `.ccmm-sec h1` rule once
   out-ranked the mockups' bare-element scale and collapsed every heading on the
   site to 16px (BUILD-NOTES, failed approaches). Verify by measuring rendered
   line counts, never by eye.
   -------------------------------------------------------------------------- */
.ccmm-sec .ccmm-head h2 {
	font-size: clamp(1.4rem, 2vw, 2rem) !important;
}

/* --------------------------------------------------------------------------
   1d. Footer headings, after the h3 -> h2 promotion (2026-08-14).
   The footer's five column headings became h2 to stop an h1 -> h3 skip on
   every page with no h2 in its body. That moved them out of the reach of the
   footer's own sizing rule and into two Divi declarations that h3 never hit:

     et-divi-customizer-global.min.css   h2 { font-size: 32px !important }
     inline customizer block             h1, .et-db #et-boc .et-l h1, h2,
                                         .et-db #et-boc .et-l h2 { color:#1d2145 }

   The first wins on `!important`; the second on specificity, because the
   footer sits inside `.et-db #et-boc .et-l`, so that arm of the selector
   scores (0,3,1) against the plain `.ccmm-footer h2` (0,1,1) above.

   Neither can be outranked without `!important` here, so this is the one place
   the footer restates its own values. They are the SAME values the footer
   already declares in pcrs-design.css — no new sizing decision, just the
   specificity needed to keep it. Scoped to `.ccmm-footer` so nothing else
   moves. `color: inherit` resolves to the footer's own paper on inkdeep.
   -------------------------------------------------------------------------- */
.ccmm-footer :is(h2, h3) {
	font-size: 1rem !important;
	color: inherit !important;
}

/* --------------------------------------------------------------------------
   1e. Story-card titles, same trap as 1d (2026-08-14).
   `pcrs_pcs_gallery()` now picks the card heading level from context: h3 when
   the gallery prints its own h2, h2 when it does not — the story archive, where
   a fixed h3 skipped a level under the page h1.

   `.pcs-title` sizes the card in pcrs-divi-compat.css at 1.02rem, and that held
   easily while the cards were h3. As h2 they inherit BOTH Divi declarations:
   `h2 { font-size:32px !important }` and, inside a band, this file's own
   `.ccmm-sec h2 { section-size !important }` — which rendered the archive's
   card titles at 54.4px, overflowing into the neighbouring column.

   Two selectors because they answer two different opponents: the bare class
   out-specifies `h2` (0,1,0 beats 0,0,1), and the band-scoped one out-specifies
   `.ccmm-sec h2` (0,2,0 beats 0,1,1). Both need `!important` to enter the
   contest at all.

   The lesson from 1d and 1e together: on this install, changing a heading's
   LEVEL is never only a semantic change. Every level carries Divi styling that
   the old level did not, so re-check the rendering after any promotion.
   -------------------------------------------------------------------------- */
.pcs-title,
.ccmm-sec .pcs-title {
	font-size: 1.02rem !important;
}

/* --------------------------------------------------------------------------
   1f. Mobile: the closed nav dropdowns were scrolling the whole site sideways.

   Found by actually measuring at 485px, not by reading the CSS. Every page
   reported `scrollWidth - clientWidth = 82`, and a real horizontal scrollbar
   appeared at the bottom of the viewport.

   Cause: `.ccmm-nav .ccmm-sub` is a fixed 17.5rem (280px) absolutely-positioned
   panel, closed with `opacity: 0; visibility: hidden` — but it keeps
   `display: block`. A `visibility: hidden` element still occupies layout and
   still counts toward `scrollWidth`. The Restoration panel starts at x=287, so
   287 + 280 = 567 against a 485px viewport: exactly the 82px measured.

   Hiding them below the mobile breakpoint costs nothing, because they are
   ALREADY unusable there: they open on `:hover`/`:focus-within`, and a touch
   device fires neither. Sub-items stay reachable — every parent page carries a
   link-down grid to its children (MODULE-2 2.7 Rule 2), which is how a phone
   visitor reaches them today regardless.

   Scoped to the breakpoint so the desktop dropdowns are untouched.

   NOTE this does NOT fix the underlying nav design: at 485px the header still
   renders all 7 top-level items wrapped, with no hamburger. That is a design
   decision to take, not a bug to patch in a compat file.
   -------------------------------------------------------------------------- */
@media (max-width: 700px) {
	.ccmm-nav .ccmm-sub { display: none; }
}

/* Belt and braces above the breakpoint. The 700px line above is a judgement
   call, and the panel's overflow point depends on where the nav happens to
   wrap at a given width — the Restoration panel's right edge measured 567px,
   but that x-position moves as items re-flow. Rather than trust one number,
   stop any dropdown from being able to exceed the viewport at ALL widths.
   17.5rem stays the preferred width; this only ever shrinks it. */
.ccmm-nav .ccmm-sub {
	max-width: calc(100vw - 2rem);
}

/* Touch targets on the story pager were 35px tall — under the 44px minimum.
   Height only; the visual weight is unchanged at desktop sizes because the
   padding already exceeded this there. */
@media (max-width: 700px) {
	.pcs-page {
		min-height: 44px;
		display: inline-flex;
		align-items: center;
		justify-content: center;
	}
}
.ccmm-sec h3, .ccmm-card h3, .ccmm-card-light h3, .ccmm-form-card h3 {
	font-size: var(--wp--preset--font-size--card) !important;
}
/* The tier-2 (secondary service page) layout runs its OWN, smaller type
   scale — `.tier2-main h1 { font-size: clamp(1.7rem, 3.4vw, 2.4rem) }` — and
   that selector already out-ranks Divi's bare h1. It must not be swept into
   the hero scale above; doing so rendered the dryer-vent page's body heading
   at 65px. */
/* The footer's column headings keep the mockups' own `.ccmm-footer h3
   { font-size: 1rem }`, which already out-ranks Divi's bare h3 rule. No
   override needed — an earlier guess at `--small` here made them 14.72px
   instead of 16px. */

/* --------------------------------------------------------------------------
   1d. Body copy baseline.
   Divi's customizer emits `body { font-size: 16px; color: #5c6484 }` inline,
   which beats the mockups' own `body { font-size: var(--…--base) }` on load
   order. Every paragraph inside a PCRS block therefore rendered a half-pixel
   small and slightly grey. Restated here at the mockups' own token value,
   scoped so the template pages keep Divi's setting.
   -------------------------------------------------------------------------- */
.ccmm-sec, .ccmm-topbar, .ccmm-header, .ccmm-footer,
.ccmm-form-card, .ccmm-phone-strip,
.tier2-main, .tier2-sidebar, .tier2-topbar {
	font-size: var(--wp--preset--font-size--base);
	line-height: 1.62;
}

/* --------------------------------------------------------------------------
   2. Section / module box model.
   Every PCRS block is a fullwidth code module. Divi pads its sections and
   modules by default, which double-pads bands that set their own
   padding-block and breaks the full-bleed colour bands.
   -------------------------------------------------------------------------- */
.et_pb_fullwidth_section { padding: 0 !important; }
.et_pb_fullwidth_code,
.et_pb_fullwidth_code .et_pb_code_inner {
	padding: 0 !important;
	margin: 0 !important;
	max-width: none !important;
	width: 100% !important;
}

/* The wrapper must not clip the sticky top bar. */
#custom_header .et_pb_section,
#custom_header .et_pb_module,
#custom_header .et_pb_code_inner,
#custom_header_mobile .et_pb_section,
#custom_header_mobile .et_pb_module,
#custom_header_mobile .et_pb_code_inner { overflow: visible !important; }

/* 7949 (the phone strip) is a MODULE-scope global, so it can only be
   referenced from inside a section/row/column shell. Divi's row caps that at
   the content width, which left the full-bleed red band floating at 1127px
   inside a wider page. Let any row whose code module contains one of our
   bands run edge to edge.

   Divi inserts `.et_pb_code_inner` between the module and its content — an
   earlier version of this selector omitted that step, matched nothing, and
   the band stayed boxed. */
.et_pb_row:has(.et_pb_code_inner > .ccmm-sec),
.et_pb_row:has(.et_pb_code_inner > .ccmm-phone-strip) {
	padding: 0 !important;
	width: 100% !important;
	max-width: none !important;
}
.et_pb_column:has(.et_pb_code_inner > .ccmm-sec) {
	padding: 0 !important;
	width: 100% !important;
}
/* ...and the SECTION too. Divi's default `.et_section_regular { padding: 4% 0 }`
   survived the row/column zeroing above and painted 56px of WHITE above and
   below the band at 1424px wide. Above the strip it hid against the white
   guarantee band; below it, it showed as a white sliver between the red CTA
   band and the dark footer — visible on the home page as a gap that belonged
   to neither section. Measured before the fix: section_18 padT=56.36px
   padB=56.36px bg=rgb(255,255,255). */
.et_pb_section:has(.et_pb_code_inner > .ccmm-sec),
.et_pb_section:has(.et_pb_code_inner > .ccmm-phone-strip) {
	padding: 0 !important;
	/* Every band domes UP into the band before it, so the dome (::before at
	   top:-sweep) is painted outside its own section. Divi stacks a fullwidth
	   section's .et_pb_module at z-index 9, while a regular section's
	   .et_pb_column sits at z-index 2 AND establishes a stacking context —
	   so the whole strip, dome included, painted underneath the preceding
	   band and the strip alone rendered with a flat top edge. Lifting the
	   section above 9 puts it back in line with every other boundary.
	   (Raising the inner module instead does nothing: the column's stacking
	   context traps it.) */
	position: relative;
	z-index: 10;
}

/* Counterpart to `.ccmm-strip:last-child` in patterns.css. On the built site
   each band sits in its own Divi section, so a strip is never its own
   parent's last child — "is there a band after this one" is a question about
   the DIVI wrapper, not the band. The final section in the builder's content
   is the one that butts against the theme-injected footer, and its strip must
   drop the dome reservation or it renders bottom-heavy with the text riding
   high in the band. */
   This applies to ANY final band, not just a strip: whatever is last on the
   page is reserving room for a dome that will never be drawn, and that
   reservation reads as dead colour above the footer. */
.et_builder_inner_content > .et_pb_section:last-child .ccmm-sec {
	padding-bottom: var(--wp--preset--spacing--70) !important;
}
.et_builder_inner_content > .et_pb_section:last-child .ccmm-sec.ccmm-strip {
	padding-bottom: var(--ccmm-strip-pad) !important;
}

/* --------------------------------------------------------------------------
   Job photography in the tier2 (sub-service) template.

   The approved dryer-vent mockup carried no photos, so the tier2 flow had no
   image treatment at all. Added 2026-08-12 when real job photography was
   plugged in. Lives here rather than in pcrs-design.css so that file stays a
   faithful copy of the approved mockups.
   -------------------------------------------------------------------------- */
.tier2-main figure {
	margin: 2rem 0;
}
.tier2-main figure img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: 14px;
}
.tier2-main figcaption {
	margin-top: 0.6rem;
	font-size: 0.9rem;
	line-height: 1.5;
	color: #5c6484;
}

/* --------------------------------------------------------------------------
   tier2 HERO + before/after — 2026-08-13
   The sub-service pages opened with a bare <h1> on white and carried their one
   photo halfway down the page, so every service page looked identical above
   the fold. Each now leads with a banner photo of that specific service.

   `loading` is deliberately NOT lazy on the hero: it is the LCP element, and
   lazy-loading the largest above-the-fold image is a measurable Core Web
   Vitals regression.
   -------------------------------------------------------------------------- */
.tier2-main figure.tier2-hero {
	margin: 0 0 1.6rem;
}
.tier2-main figure.tier2-hero img {
	aspect-ratio: 21 / 9;
	object-fit: cover;
	border-radius: 16px;
}
@media (max-width: 760px) {
	.tier2-main figure.tier2-hero img { aspect-ratio: 3 / 2; }
}

/* Before/after proof block. Both frames share one aspect-ratio so the
   comparison cannot flatter itself: a taller "after" would read as more
   improvement than actually happened. */
.tier2-ba-set {
	margin: 2rem 0;
}
.tier2-ba-set > h3 {
	margin-bottom: 0.75rem;
}
.tier2-ba {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 8px;
}
.tier2-main .tier2-ba figure {
	position: relative;
	margin: 0;
}
.tier2-main .tier2-ba img {
	aspect-ratio: 4 / 5;
	object-fit: cover;
	border-radius: 10px;
}
.tier2-ba figcaption {
	position: absolute;
	inset-inline-start: 8px;
	inset-block-start: 8px;
	margin: 0;
	padding: 0.18rem 0.55rem;
	border-radius: 999px;
	font-size: 0.62rem;
	font-weight: 800;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	background: rgba(29, 33, 69, 0.78);
	color: #fff;
}
.tier2-ba figure.is-after figcaption {
	background: #ed1c24;
	color: #fff;
}

/* --------------------------------------------------------------------------
   /service-area/ — chip cloud, 2026-08-13
   The page used to render 269 towns as <li> bullets in one narrow column,
   about 4,000px of list. Same towns, same words, as pills grouped by county.
   -------------------------------------------------------------------------- */
.tier2-main .sa-chips {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem;
	list-style: none;
	margin: 0 0 0.5rem;
	padding: 0;
}
.tier2-main .sa-chips li {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	margin: 0;
	padding: 0.3rem 0.7rem;
	border-radius: 999px;
	border: 1px solid rgba(29, 33, 69, 0.12);
	background: #fff;
	color: #1d2145;
	font-size: 0.82rem;
	font-weight: 600;
	line-height: 1.2;
	white-space: nowrap;
}
.tier2-main .sa-chips li::before {
	content: "";
	width: 0.36rem;
	height: 0.36rem;
	border-radius: 50%;
	background: #ed1c24;
	flex: 0 0 auto;
}
/* The ten towns PCRS's own copy names as its core area carry more weight. */
.tier2-main .sa-chips.is-core li {
	background: #1d2145;
	border-color: #1d2145;
	color: #fff;
	font-size: 0.9rem;
	padding: 0.42rem 0.9rem;
}
.tier2-main .sa-chips.is-core li::before {
	background: #ed1c24;
}
.sa-counties {
	display: grid;
	gap: 1.25rem;
}
@media (min-width: 700px) {
	/* Two columns from 700px. The counties differ wildly in size (19 towns to
	   49), so a fixed 2-up grid would leave a tall ragged gap — masonry-ish
	   column flow packs them instead. Breakpoint lowered from 900px when the
	   layout went single-column: the content column is 860px, so a 900px
	   trigger never fired and the counties stacked into one very tall list. */
	.sa-counties {
		display: block;
		column-count: 2;
		column-gap: 1.5rem;
	}
	.sa-county {
		break-inside: avoid;
		display: inline-block;
		width: 100%;
	}
}
.sa-county {
	margin-bottom: 1.25rem;
	padding: 1rem 1.1rem;
	border-radius: 14px;
	background: #f1f5fb;
}
.tier2-main .sa-county h3 {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 0.75rem;
	margin: 0 0 0.7rem;
	font-size: 1.02rem;
}
.sa-count {
	font-size: 0.72rem;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: #5c6484;
	white-space: nowrap;
}

/* wpautop emits empty <p> elements around standalone HTML comments and around
   block boundaries it does not understand. `.tier2-wrap` is a two-column grid,
   so one empty paragraph between <main> and <aside> becomes a third grid item
   and drops the sidebar onto its own row under the content. Removing it from
   layout (not merely hiding it) puts the sidebar back in column two. */
.tier2-wrap > p:empty,
.tier2-main > p:empty {
	display: none;
}

/* --------------------------------------------------------------------------
   tier2 in-content contact block — 2026-08-13
   Replaces the one part of the removed sidebar that was doing real work: the
   phone number and address inside the page body. Full width of the single
   column, not a side column.
   -------------------------------------------------------------------------- */
.tier2-contact {
	margin: 2.5rem 0 0;
	padding: 1.6rem 1.8rem;
	border-radius: 16px;
	background: #f1f5fb;
	border: 1px solid rgba(29, 33, 69, 0.10);
	text-align: center;
}
.tier2-main .tier2-contact h2 {
	margin: 0 0 0.4rem;
	font-size: 1.25rem;
}
.tier2-contact p { margin: 0 0 0.6rem; }
.tier2-contact-phone {
	font-family: var(--wp--preset--font-family--display, inherit);
	font-weight: 800;
	font-size: clamp(1.6rem, 3.5vw, 2.1rem);
	line-height: 1.1;
}
.tier2-contact-phone a { color: #ed1c24; text-decoration: none; }
.tier2-contact-phone a:hover { text-decoration: underline; }

/* --------------------------------------------------------------------------
   Blog posts — single column, readable measure (2026-08-13)

   The 18 posts imported from the client's live feed render through Divi's post
   template, not through .tier2-wrap, so the single-column measure set for the
   pages does not reach them. Full width here is 1280px: at ~200 characters a
   line, an 1,800-word article is unreadable.

   `.et_full_width_page` is now the default layout, so the old right sidebar is
   gone; this only constrains what is left.
   -------------------------------------------------------------------------- */
.single-post .entry-content,
.single-post .entry-title,
.single-post .post-meta {
	max-width: 860px;
	margin-inline: auto;
}
.single-post .entry-content img {
	max-width: 100%;
	height: auto;
	border-radius: 14px;
}
.single-post .entry-content h2 {
	margin-top: 2rem;
}
.single-post .entry-content p {
	margin-bottom: 1.1rem;
}
/* The archive listing gets the same treatment so /blog/ matches a post. */
.blog #left-area,
.archive #left-area,
.blog .et_pb_post,
.archive .et_pb_post {
	max-width: 860px;
	margin-inline: auto;
	float: none;
}

/* Blog / archive heading — supplied by pcrs_archive_heading() in functions.php,
   because a posts page never renders its own content. */
.pcrs-archive-head {
	max-width: 860px;
	margin: 0 auto 1.5rem;
	padding-top: 2rem;
}
.pcrs-archive-head h1 {
	margin: 0;
}

/* --------------------------------------------------------------------------
   Project Capture Story before/after pair — 2026-08-17
   Reused inside `.pcs-story` on a converted story's single (post_content
   built by convert_pcs_to_posts.py) — the only survivor of the old CPT-era
   gallery block, because it is still on screen. Everything else that block
   held (`.pcs-grid`, `.pcs-card`, `.pcs-pager`…) rendered the hand-rolled
   [pcs_gallery] card grid, which no longer exists; a real Divi Blog module
   renders the grid now (`.pcrs-stories-grid`, below), and its own markup
   doesn't use any of those classes.
   -------------------------------------------------------------------------- */
.pcs-story { margin-bottom: 1.5rem; }

/* Both frames share one aspect-ratio so the comparison cannot flatter itself:
   a taller "after" reads as more improvement than actually happened. */
.pcs-ba {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 6px;
	margin-bottom: 0.85rem;
}
.pcs-ba figure { position: relative; margin: 0; }
.pcs-ba img {
	display: block;
	width: 100%;
	aspect-ratio: 4 / 5;
	object-fit: cover;
	border-radius: 10px;
	background: rgba(29, 33, 69, 0.08);
}
.pcs-ba figcaption {
	position: absolute;
	inset-inline-start: 8px;
	inset-block-start: 8px;
	margin: 0;
	padding: 0.16rem 0.5rem;
	border-radius: 999px;
	font-size: 0.6rem;
	font-weight: 800;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	background: rgba(29, 33, 69, 0.78);
	color: #fff;
}
.pcs-ba figure.is-after figcaption { background: #ed1c24; }

/* --------------------------------------------------------------------------
   Story grid — the Divi Blog module, everywhere — 2026-08-17
   pcrs_blog_module.py's blog_section() sets module_class="pcrs-stories-grid"
   on every [et_pb_blog] it emits (home, every service page, every Happy
   Customer Stories tab), so one ruleset styles the grid site-wide.

   Divi's own grid mode ("CSS Grid" in the builder) positions cards with
   salvattore.js — real masonry, packing each card up into whatever gap the
   shortest neighbour left, which is why two 3-line and one 6-line excerpt in
   the same row come out three different heights. Forcing CSS Grid instead
   (not "the CSS Grid option", the actual `display: grid`) makes every card
   in a row the same height by construction; `position: static !important`
   overrides salvattore's inline `position: absolute; top/left` (an
   !important class rule beats an inline style of equal-or-lower specificity
   only when importance also wins, which it does here).
   -------------------------------------------------------------------------- */
.pcrs-stories-grid .et_pb_blog_grid,
.pcrs-stories-grid .et_pb_salvattore_content {
	column-count: unset !important;
	columns: unset !important;
}
.pcrs-stories-grid .et_pb_salvattore_content {
	display: grid !important;
	grid-template-columns: repeat(3, 1fr);
	gap: clamp(1.25rem, 2.5vw, 1.75rem);
}
/* Divi's blog-grid mode doesn't lay the posts out itself — it hands the DOM
   to salvattore.js, which (found by live DOM inspection, not guessed: static
   HTML never shows it, it's JS-injected) wraps every `.et_pb_post` in its own
   `<div class="column size-1ofN">` and JS-sizes THAT wrapper, not the post.
   `data-columns` is emitted blank here (no `posts_number`-driven column count
   wired up for grid mode), so every wrapper gets `size-1of1` and salvattore's
   own width math, ignoring the 3-column grid entirely — the cause of the
   "one narrow sliver per row" layout this replaces. Overriding `.et_pb_post`
   alone did nothing, because `.et_pb_post` was never the missized element.
   Both the wrapper AND the post need `width: 100% !important` — the wrapper
   against the grid TRACK, the post against the now-full-width wrapper. */
.pcrs-stories-grid.pcrs-stories-grid .et_pb_salvattore_content > .column {
	position: static !important;
	float: none !important;
	width: 100% !important;
	margin: 0 !important;
}
.pcrs-stories-grid.pcrs-stories-grid .et_pb_post.et_pb_post {
	position: static !important;
	top: auto !important;
	left: auto !important;
	right: auto !important;
	bottom: auto !important;
	float: none !important;
	width: 100% !important;
	max-width: none !important;
	margin: 0 !important;
	display: flex !important;
	flex-direction: column;
	min-width: 0;
	border-radius: 18px;
	overflow: hidden;
	background: var(--wp--preset--color--paper, #fff);
	color: var(--wp--preset--color--ink, #1d2145);
	box-shadow: 0 12px 30px -18px rgba(29, 33, 69, 0.4);
	transition: transform .2s cubic-bezier(.22, .7, .3, 1);
}
.pcrs-stories-grid .et_pb_post:hover { transform: translateY(-4px); }
@media (max-width: 980px) {
	.pcrs-stories-grid .et_pb_salvattore_content { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
	.pcrs-stories-grid .et_pb_salvattore_content { grid-template-columns: 1fr; }
}

.pcrs-stories-grid .et_pb_image_container { margin: 0; }
.pcrs-stories-grid .et_pb_image_container img {
	display: block;
	width: 100%;
	aspect-ratio: 4 / 3;
	object-fit: cover;
}
.pcrs-stories-grid .entry-title {
	margin: 1rem 1.25rem 0.4rem;
	font-size: 1.05rem;
	line-height: 1.3;
}
.pcrs-stories-grid .entry-title a { color: inherit; text-decoration: none; }
.pcrs-stories-grid .entry-title a:hover { text-decoration: underline; }
.pcrs-stories-grid .post-content { margin: 0 1.25rem; flex: 1 0 auto; }
/* Divi's blog module doesn't truncate post_content the way it truncates a
   proper excerpt, so a long first paragraph ran past the card's own bottom
   edge into whatever sat below it. Clamped to 4 lines with an ellipsis
   instead of trusting content length to already be short. */
.pcrs-stories-grid .post-content-inner p {
	margin: 0;
	font-size: 0.88rem;
	line-height: 1.55;
	opacity: 0.85;
	display: -webkit-box;
	-webkit-line-clamp: 4;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
/* Divi's own .more-link styling is a solid-red button (`background-color`
   from the theme's accent-colour option) — this site's accent is the SAME
   red, so the label rendered invisible: red text on a red box. Strip the
   background back to a plain text link instead of chasing a second colour
   that would just be a new way to collide with a future accent change. */
.pcrs-stories-grid .more-link {
	display: inline-block;
	background: transparent !important;
	margin: 0.6rem 1.25rem 1.25rem;
	padding: 0 !important;
	font-size: 0.8rem;
	font-weight: 800;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	text-decoration: none;
	color: var(--wp--preset--color--accent, #ed1c24);
}
.pcrs-stories-grid .more-link:hover { text-decoration: underline; }

/* WP-PageNavi — numeric pager, capped at 4 numbers by the plugin's own
   options (see BUILD-NOTES stream 3). Divi wraps whatever it renders in
   .et_pb_ajax_pagination_container and swaps the grid via its own AJAX, so
   this only needs to look right, not do the swapping itself. */
.wp-pagenavi {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: 0.4rem;
	margin-top: 1.75rem;
	clear: both;
}
.wp-pagenavi a,
.wp-pagenavi span {
	min-width: 2.1rem;
	padding: 0.35rem 0.6rem;
	border-radius: 8px;
	text-align: center;
	font-weight: 700;
	font-size: 0.85rem;
	text-decoration: none;
	border: 1px solid currentColor;
	opacity: 0.7;
	color: inherit;
}
.wp-pagenavi a:hover { opacity: 1; }
.wp-pagenavi span.current {
	opacity: 1;
	background: var(--wp--preset--color--accent, #ed1c24);
	color: #fff;
	border-color: var(--wp--preset--color--accent, #ed1c24);
}
.wp-pagenavi span.extend { border: none; opacity: 0.5; }

/* --------------------------------------------------------------------------
   Happy Customer Stories tabs (build_stories_page.py) — et_pb_tabs in the
   site palette rather than Divi's default blue/grey.
   -------------------------------------------------------------------------- */
.et_pb_tabs .et_pb_tabs_controls {
	border-bottom-color: rgba(29, 33, 69, 0.15);
}
.et_pb_tabs .et_pb_tabs_controls li a {
	font-weight: 700;
	color: var(--wp--preset--color--ink, #1d2145);
}
.et_pb_tabs .et_pb_tabs_controls li.et_pb_tab_active a {
	color: #fff;
}

/* --------------------------------------------------------------------------
   Service pages on the pillar band design — 2026-08-13
   The tier2 pages carried their own plain type scale; inside a ccmm band the
   prose needs a measure and the band's own colour inheritance.
   -------------------------------------------------------------------------- */
.ccmm-sec .ccmm-prose {
	max-width: 74ch;
	margin-inline: auto;
}
.ccmm-sec .ccmm-prose > *:first-child { margin-top: 0; }
.ccmm-sec .ccmm-prose > *:last-child { margin-bottom: 0; }
.ccmm-sec .ccmm-prose ul,
.ccmm-sec .ccmm-prose ol { padding-inline-start: 1.4em; }
.ccmm-sec .ccmm-prose li { margin-bottom: .4rem; }

/* Hero media inside the opening dark band. */
.ccmm-sec .ccmm-hero-media { margin-top: var(--wp--preset--spacing--40, 2rem); }
.ccmm-sec .ccmm-hero-media figure { margin: 0; }
.ccmm-sec .ccmm-hero-media img {
	display: block;
	width: 100%;
	aspect-ratio: 21 / 9;
	object-fit: cover;
	border-radius: 16px;
}
.ccmm-sec .ccmm-hero-media figcaption {
	margin-top: .6rem;
	font-size: .85rem;
	opacity: .8;
	text-align: center;
}
@media (max-width: 760px) {
	.ccmm-sec .ccmm-hero-media img { aspect-ratio: 3 / 2; }
}

/* FAQ inside a band — inherits the band's colour instead of the tier2 palette. */
.ccmm-sec .ccmm-faq { max-width: 74ch; margin-inline: auto; }
.ccmm-sec .ccmm-faq details {
	border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
	border-radius: 8px;
	padding: .2rem 1.1rem;
	margin-bottom: .8rem;
	background: color-mix(in srgb, currentColor 4%, transparent);
}
.ccmm-sec .ccmm-faq summary { cursor: pointer; padding: .85rem 0; font-weight: 700; }
.ccmm-sec .ccmm-faq details p { margin: 0 0 .9rem; }

/* The gallery sits inside bands now, so its cards must read on dark too. */
.ccmm-sec .pcs-gallery { max-width: none; }
.ccmm-sec.is-dark .pcs-meta,
.ccmm-sec.is-darkalt .pcs-meta,
.ccmm-sec.is-mid .pcs-meta { opacity: .85; }

/* ---------------------------------------------------------------------------
 * Build placeholders are internal notes, not content.
 *
 * 64 of them were rendering to visitors across 20 pages — two of them live in
 * the header and footer globals, so "GOOGLE RATING BADGE — pending client's
 * Google profile data" and "OFFICE HOURS — pending from client" appeared on
 * every single page of the site.
 *
 * They are hidden rather than deleted: each one is a real record of something
 * still owed by the client, and losing that record would mean rediscovering the
 * gap later. Append ?todo=1 to any URL to see them again (see
 * pcrs_show_todo_body_class() in functions.php).
 *
 * display:none, not visibility/opacity — a placeholder that still occupies
 * layout leaves an unexplained gap, which reads as a broken page rather than a
 * finished one.
 * ------------------------------------------------------------------------- */
.ccmm-placeholder { display: none !important; }
body.pcrs-show-todo .ccmm-placeholder {
	display: block !important;
	outline: 2px dashed #ed1c24;
	outline-offset: 3px;
}
/* The topbar mark list uses flex; a hidden pill must not leave a gap column. */
body:not(.pcrs-show-todo) .ccmm-topbar-marks .ccmm-placeholder { display: none !important; }

/* ---------------------------------------------------------------------------
 * 12. Sticky site header (Gus, 2026-08-17: "the navigation bar does not
 *     follow when I scroll down").
 *
 * The estate keeps its whole header fixed (thesteamplus: #main-header,
 * `et_fixed_nav`). The mockups declared `.ccmm-topbar { position: sticky }`,
 * but on the built site that element sits inside
 * #custom_header > .et_pb_section > .et_pb_module > .et_pb_code_inner, and a
 * sticky element only sticks within its PARENT's box — a box exactly as tall
 * as the header itself. So it never moved. The element that has the whole
 * page as its parent is #custom_header / #custom_header_mobile (direct
 * children of #page-container, which Divi does NOT overflow-clip — checked
 * against Divi/style.css). Stick those, top bar and nav together.
 * ------------------------------------------------------------------------- */
#custom_header { position: sticky; top: 0; z-index: 900; }
body.admin-bar #custom_header { top: 32px; }
/* The MOBILE header (global 1173) is the same wrapped nav with no hamburger:
   221px tall at 480px wide. Sticking that would eat a third of a phone
   screen, so below the desktop breakpoint the header scrolls with the page
   until the mobile nav gets its hamburger (pcrs-mobile-nav.js, see §15). */
#custom_header_mobile { position: sticky; top: 0; z-index: 900; }
body.admin-bar #custom_header_mobile { top: 32px; }
@media (max-width: 782px) { body.admin-bar #custom_header_mobile { top: 46px; } }
/* Below 1025px the list is behind the hamburger (§15), so the stuck header is
   just top bar + logo row. Without the JS the list would show and the header
   would be tall again — so only stick it when the JS has flagged itself in. */
@media (max-width: 1024px) {
	html:not(.pcrs-mnav) #custom_header_mobile { position: static; }
}
/* In-page anchors must land below the stuck header. */
html { scroll-padding-top: 9.5rem; }
/* The header casts a light shadow so it reads as a layer over the page. */
#custom_header .ccmm-header,
#custom_header_mobile .ccmm-header {
	box-shadow: 0 10px 24px -18px rgba(9, 12, 45, .55);
}

/* ---------------------------------------------------------------------------
 * 13. Nav density (Gus, 2026-08-17: "so tight and cramped").
 *
 * The bar was designed for seven top-level items and still wrapped labels
 * onto two lines ("ABOUT / US") because the <a> could break. The menu is now
 * six items with short labels (template shape); a label must never break,
 * and the row breathes with the viewport instead of the fixed 1440/1240
 * steps in the mockup CSS.
 * ------------------------------------------------------------------------- */
.ccmm-header .ccmm-nav > ul > li > a { white-space: nowrap; }
.ccmm-header .ccmm-nav ul { gap: clamp(1rem, 1.7vw, 2rem); }
.ccmm-header .ccmm-nav a { font-size: .84rem; letter-spacing: .04em; }
.ccmm-header .ccmm-header-inner { gap: clamp(1rem, 2vw, 2.5rem); }
@media (max-width: 1440px) {
	.ccmm-header .ccmm-nav ul { gap: clamp(.8rem, 1.4vw, 1.5rem); }
	.ccmm-header .ccmm-nav a { font-size: .8rem; }
}
@media (max-width: 1240px) {
	.ccmm-header .ccmm-nav ul { gap: .75rem; }
	.ccmm-header .ccmm-nav a { font-size: .74rem; letter-spacing: .02em; }
}
/* Fifteen services in one panel: two columns above tablet so the panel stays
   on screen. */
.ccmm-nav .ccmm-sub { min-width: 15rem; }
@media (min-width: 1025px) {
	.ccmm-nav .ccmm-has-sub.ccmm-sub-wide > .ccmm-sub,
	.ccmm-nav li:has(> .ccmm-sub > li:nth-child(10)) > .ccmm-sub {
		min-width: 34rem;
		columns: 2;
		column-gap: 0;
		padding-block: .6rem;
	}
	.ccmm-nav li:has(> .ccmm-sub > li:nth-child(10)) > .ccmm-sub li {
		break-inside: avoid;
	}
}

/* ---------------------------------------------------------------------------
 * 14. Service PILLAR pages (scripts/build_pillar.py — MODULE-3C's 12 sections
 *     on the PCRS design). Only what the design system did not already have.
 * ------------------------------------------------------------------------- */
/* 2 · trust strip: a thin band, badges centred as check pills */
.ccmm-sec.pcrs-trust-band { padding-block: var(--wp--preset--spacing--30); }
.ccmm-sec.pcrs-trust-band .ccmm-check-list { margin: 0; justify-content: center; gap: var(--wp--preset--spacing--30); }
.ccmm-sec.pcrs-trust-band .ccmm-check-list li {
	padding: .45rem .95rem;
	border-radius: 999px;
	background: var(--wp--preset--color--paper);
	box-shadow: 0 8px 22px -16px color-mix(in srgb, var(--wp--preset--color--inkdeep) 55%, transparent);
	font-family: var(--wp--preset--font-family--display);
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: .03em;
	font-size: .78rem;
}
/* 5 · ten process steps: five across, two rows; four across on laptops */
.pcrs-steps { row-gap: calc(var(--wp--preset--spacing--30) + 22px); }
@media (max-width: 1240px) { .pcrs-steps.ccmm-grid5 { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 900px)  { .pcrs-steps.ccmm-grid5 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px)  { .pcrs-steps.ccmm-grid5 { grid-template-columns: 1fr; } }
.pcrs-paa {
	margin-top: var(--wp--preset--spacing--50);
	padding-top: var(--wp--preset--spacing--40);
	border-top: 1px solid rgba(255, 255, 255, .14);
}
.pcrs-paa h4 { font-size: var(--wp--preset--font-size--card); margin-bottom: var(--wp--preset--spacing--10); }
.pcrs-paa p { font-size: var(--wp--preset--font-size--small); line-height: 1.65; }
/* 7 · comparison table */
.pcrs-table-wrap { overflow-x: auto; margin-block: var(--wp--preset--spacing--30); }
table.ccmm-compare {
	width: 100%;
	border-collapse: separate;
	border-spacing: 0;
	background: var(--wp--preset--color--paper);
	border-radius: 18px;
	overflow: hidden;
	box-shadow: 0 12px 30px -18px color-mix(in srgb, var(--wp--preset--color--dark) 40%, transparent);
	font-size: var(--wp--preset--font-size--small);
}
table.ccmm-compare th, table.ccmm-compare td {
	padding: .95rem 1.1rem;
	text-align: left;
	vertical-align: top;
	border-bottom: 1px solid var(--wp--preset--color--line);
}
table.ccmm-compare thead th {
	background: var(--wp--preset--color--inkdeep);
	color: var(--wp--preset--color--paper);
	font-family: var(--wp--preset--font-family--display);
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: .03em;
	font-size: .74rem;
}
table.ccmm-compare thead th:nth-child(2) { background: var(--wp--preset--color--accent); }
table.ccmm-compare tbody th { font-weight: 800; color: var(--wp--preset--color--dark); width: 20%; }
table.ccmm-compare tbody td:nth-child(2) { font-weight: 700; color: var(--wp--preset--color--dark); background: color-mix(in srgb, var(--wp--preset--color--accent) 5%, transparent); }
table.ccmm-compare tbody td:nth-child(3) { color: var(--wp--preset--color--muted, #5c6484); }
table.ccmm-compare tbody tr:last-child th, table.ccmm-compare tbody tr:last-child td { border-bottom: 0; }
.pcrs-center { text-align: center; max-width: 48rem; margin-inline: auto; }
/* 9 · before/after grid: two sets per row, caption under each */
.pcrs-ba-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: var(--wp--preset--spacing--40);
}
@media (max-width: 760px) { .pcrs-ba-grid { grid-template-columns: 1fr; } }
figure.pcrs-ba-set { margin: 0; }
.pcrs-ba-cap {
	margin-top: var(--wp--preset--spacing--10);
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--dark);
	text-align: center;
}
/* 9b/9c · Divi Gallery + Divi Blog sections must read as design bands. These
   are REGULAR Divi sections (not fullwidth code), so they carry Divi's own
   row/section box model; restate the band tone here. */
.et_pb_section.pcrs-gallery-band.pcrs-gallery-band,
.et_pb_section.pcrs-stories-band.pcrs-stories-band {
	background: var(--wp--preset--color--wash);
	padding-block: var(--wp--preset--spacing--70) !important;
}
.et_pb_section.pcrs-stories-band.pcrs-stories-band { background: var(--wp--preset--color--paper); }
.pcrs-gallery-band .et_pb_row,
.pcrs-stories-band .et_pb_row { width: min(100% - 2 * var(--wp--preset--spacing--30), 1180px); max-width: none; padding: 0; }
.pcrs-gallery-band .et_pb_code, .pcrs-stories-band .et_pb_code { margin-bottom: var(--wp--preset--spacing--40); }
.pcrs-gallery-band .ccmm-head, .pcrs-stories-band .ccmm-head { text-align: center; }
.pcrs-gallery-band .ccmm-head h2, .pcrs-stories-band .ccmm-head h2 { color: var(--wp--preset--color--dark); }
/* Divi gallery slider: rounded frame, arrows visible on the design palette */
.pcrs-gallery-slider.et_pb_gallery { border-radius: 18px; overflow: hidden; box-shadow: 0 20px 44px -22px color-mix(in srgb, var(--wp--preset--color--inkdeep) 60%, transparent); }
.pcrs-gallery-slider .et_pb_slide { background-size: cover !important; background-position: center !important; }
.pcrs-gallery-slider .et_pb_gallery_image, .pcrs-gallery-slider .et_pb_slide_image { background: var(--wp--preset--color--inkdeep); }
.pcrs-gallery-slider .et_pb_slide { height: clamp(320px, 52vw, 640px); }
.pcrs-gallery-slider .et-pb-arrow-prev, .pcrs-gallery-slider .et-pb-arrow-next {
	color: #fff; background: color-mix(in srgb, var(--wp--preset--color--inkdeep) 55%, transparent);
	border-radius: 50%; width: 48px; height: 48px; display: grid; place-items: center; opacity: 1;
}
.pcrs-gallery-slider .et-pb-arrow-prev { left: 16px; } .pcrs-gallery-slider .et-pb-arrow-next { right: 16px; }
.pcrs-gallery-slider .et-pb-arrow-prev::before, .pcrs-gallery-slider .et-pb-arrow-next::before { font-size: 26px; }
/* 12 · big phone in the close band */
.pcrs-big-phone { text-align: center; margin: var(--wp--preset--spacing--20) 0; }
.pcrs-big-phone a {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 900;
	font-size: clamp(1.8rem, 4vw, 3rem);
	letter-spacing: .01em;
	color: var(--wp--preset--color--dark);
	text-decoration: none;
}
.pcrs-big-phone a:hover { color: var(--wp--preset--color--accent); }
/* FAQ: the design's pill rows expect .ccmm-faq-item on each <details>; the
   answer wrapper gets the same inner padding as the summary. */
.ccmm-faq .ccmm-faq-item + .ccmm-faq-item { margin-top: var(--wp--preset--spacing--10); }
.ccmm-faq .ccmm-faq-item.is-open, .ccmm-faq .ccmm-faq-item[open] { border-radius: 22px; }
/* Light cards on a DARK band (pillar section 4 sits on .is-mid): the card is
   paper-coloured but inherited the band's white text, so headings and copy
   vanished. A light card is always ink-on-paper, whatever band it sits on. */
.ccmm-sec.is-mid .ccmm-card-light, .ccmm-sec.is-dark .ccmm-card-light,
.ccmm-sec.is-mid a.ccmm-card-light, .ccmm-sec.is-dark a.ccmm-card-light,
.ccmm-sec.is-mid .ccmm-card-light h3, .ccmm-sec.is-dark .ccmm-card-light h3,
.ccmm-sec.is-mid .ccmm-card-light p, .ccmm-sec.is-dark .ccmm-card-light p {
	color: var(--wp--preset--color--ink) !important;
}
.ccmm-sec.is-mid .ccmm-card-light h3, .ccmm-sec.is-dark .ccmm-card-light h3 { color: var(--wp--preset--color--dark) !important; }
/* Divi's fullwidth gallery slides are the gallery items themselves and take
   the image's natural height — a 1536×2048 job photo made a 1,600px slide.
   Fix the frame height and let the photo cover it. */
.pcrs-gallery-slider .et_pb_gallery_item,
.pcrs-gallery-slider .et_pb_gallery_image,
.pcrs-gallery-slider .et_pb_gallery_image img {
	height: clamp(320px, 46vw, 620px) !important;
	max-height: none !important;
}
.pcrs-gallery-slider .et_pb_gallery_image { overflow: hidden; }
.pcrs-gallery-slider .et_pb_gallery_image img {
	width: 100% !important;
	object-fit: cover;
	object-position: center;
	position: static !important;
	transform: none !important;
	margin: 0 !important;
}
/* Blog roll global 19321: heading band + 3-card Divi Blog grid on wash. */
.et_pb_section.pcrs-blogroll-band.pcrs-blogroll-band { background: var(--wp--preset--color--wash); padding-block: 0 var(--wp--preset--spacing--70) !important; }
.pcrs-blogroll-band .et_pb_row { width: min(100% - 2 * var(--wp--preset--spacing--30), 1180px); max-width: none; padding: 0; }
.pcrs-blogroll-band .et_pb_code { margin-bottom: 0; }
.pcrs-blogroll-band .ccmm-sec.pcrs-blogroll-head { padding-bottom: var(--wp--preset--spacing--40); }
.pcrs-blogroll-band .ccmm-head { text-align: center; }

/* ---------------------------------------------------------------------------
 * 15. Mobile hamburger (pcrs-mobile-nav.js). Below 1025px the nav list hides
 *     behind a toggle; the open panel stacks items and their sub-menus.
 * ------------------------------------------------------------------------- */
.pcrs-nav-toggle { display: none; }
@media (max-width: 1024px) {
	.ccmm-header .ccmm-header-inner { flex-wrap: wrap; align-items: center; }
	.pcrs-nav-toggle {
		display: inline-flex;
		flex-direction: column;
		justify-content: center;
		gap: 5px;
		order: 2;
		margin-left: auto;
		width: 46px;
		height: 44px;
		padding: 0 10px;
		border: 1px solid var(--wp--preset--color--line);
		border-radius: 10px;
		background: var(--wp--preset--color--paper);
		cursor: pointer;
	}
	.pcrs-nav-toggle-bar { display: block; height: 2px; width: 100%; background: var(--wp--preset--color--dark); border-radius: 2px; transition: transform .18s ease, opacity .18s ease; }
	.ccmm-header.is-open .pcrs-nav-toggle-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
	.ccmm-header.is-open .pcrs-nav-toggle-bar:nth-child(2) { opacity: 0; }
	.ccmm-header.is-open .pcrs-nav-toggle-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
	.ccmm-header .ccmm-header-phone { order: 3; }
	html.pcrs-mnav .ccmm-header .ccmm-nav { order: 4; flex: 1 0 100%; }
	html.pcrs-mnav .ccmm-header .ccmm-nav > ul { display: none; }
	html.pcrs-mnav .ccmm-header.is-open .ccmm-nav > ul {
		display: flex;
		flex-direction: column;
		align-items: stretch;
		gap: 0;
		padding: .5rem 0 .75rem;
		border-top: 1px solid var(--wp--preset--color--line);
		margin-top: .75rem;
	}
	.ccmm-header.is-open .ccmm-nav > ul > li > a { display: block; padding: .8rem .25rem; font-size: .95rem; }
	.ccmm-header.is-open .ccmm-nav > ul > li + li { border-top: 1px solid var(--wp--preset--color--line); }
	/* sub-menus: static, stacked, revealed by the parent tap */
	.ccmm-header.is-open .ccmm-nav .ccmm-sub {
		display: none;
		position: static;
		min-width: 0;
		max-width: none;
		margin: 0 0 .5rem;
		padding: 0 0 0 .9rem;
		border: 0;
		box-shadow: none;
		background: transparent;
		opacity: 1;
		visibility: visible;
		transform: none;
		columns: auto;
	}
	.ccmm-header.is-open .ccmm-nav .ccmm-has-sub.is-expanded > .ccmm-sub { display: block; }
	.ccmm-header.is-open .ccmm-nav .ccmm-sub a { padding: .5rem .25rem; font-size: .88rem; white-space: normal; }
	.ccmm-nav .ccmm-has-sub.is-expanded > a::after { transform: rotate(180deg); display: inline-block; }
}

/* Rows that actually align (2026-08-17, coordinator). salvattore hands each
   grid COLUMN a wrapper holding posts 1,4,7 / 2,5,8 / 3,6,9, so making the
   wrappers grid items still stacks unequal cards inside each column and the
   rows stagger. Dissolve the wrappers (display:contents) so the posts
   themselves are the grid items, then restore reading order with `order`
   (column k, position i -> (i-1)*cols + k). Row heights now come from CSS
   grid, so every card in a row is the same height. */
.pcrs-stories-grid .et_pb_salvattore_content > .column { display: contents !important; }
.pcrs-stories-grid .et_pb_salvattore_content { grid-auto-rows: 1fr; align-items: stretch; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(1) > .et_pb_post:nth-child(1) { order: 1; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(1) > .et_pb_post:nth-child(2) { order: 4; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(1) > .et_pb_post:nth-child(3) { order: 7; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(1) > .et_pb_post:nth-child(4) { order: 10; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(2) > .et_pb_post:nth-child(1) { order: 2; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(2) > .et_pb_post:nth-child(2) { order: 5; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(2) > .et_pb_post:nth-child(3) { order: 8; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(2) > .et_pb_post:nth-child(4) { order: 11; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(3) > .et_pb_post:nth-child(1) { order: 3; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(3) > .et_pb_post:nth-child(2) { order: 6; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(3) > .et_pb_post:nth-child(3) { order: 9; }
.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(3) > .et_pb_post:nth-child(4) { order: 12; }
@media (max-width: 980px) {
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(1) > .et_pb_post:nth-child(1) { order: 1; }
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(1) > .et_pb_post:nth-child(2) { order: 3; }
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(1) > .et_pb_post:nth-child(3) { order: 5; }
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(1) > .et_pb_post:nth-child(4) { order: 7; }
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(1) > .et_pb_post:nth-child(5) { order: 9; }
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(1) > .et_pb_post:nth-child(6) { order: 11; }
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(2) > .et_pb_post:nth-child(1) { order: 2; }
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(2) > .et_pb_post:nth-child(2) { order: 4; }
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(2) > .et_pb_post:nth-child(3) { order: 6; }
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(2) > .et_pb_post:nth-child(4) { order: 8; }
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(2) > .et_pb_post:nth-child(5) { order: 10; }
	.pcrs-stories-grid .et_pb_salvattore_content > .column:nth-child(2) > .et_pb_post:nth-child(6) { order: 12; }
}
@media (max-width: 620px) {
	.pcrs-stories-grid .et_pb_salvattore_content > .column > .et_pb_post { order: 0 !important; }
}
/* Card typography: the grid's h2 titles inherited the band-heading scale
   (uppercase display size); a card title is a card title. */
.pcrs-stories-grid .et_pb_post .entry-title,
.pcrs-stories-grid .et_pb_post .entry-title a {
	font-size: 1.05rem !important;
	line-height: 1.25 !important;
	text-transform: none !important;
	letter-spacing: 0 !important;
}
.pcrs-stories-grid .et_pb_post .entry-title { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
/* Heading rows inside the Divi-native bands (gallery / stories / blog roll)
   sit outside any .ccmm-sec, so the lead paragraph picked up the kicker's
   uppercase treatment. Restate the lead as a lead. */
.pcrs-stories-band .ccmm-head .has-lead-font-size,
.pcrs-gallery-band .ccmm-head .has-lead-font-size,
.pcrs-blogroll-band .ccmm-head .has-lead-font-size {
	text-transform: none !important;
	font-weight: 400 !important;
	letter-spacing: 0 !important;
	font-size: var(--wp--preset--font-size--lead) !important;
	max-width: 48rem;
	margin-inline: auto;
}
/* Story singles (posts in the Projects tree): the body already opens with the
   before/after pair, so Divi's duplicate featured "after" image and the
   empty "by |" meta line go; the column reads centred and full width. */
body.single-post.category-projects article.et_pb_post > .et_post_meta_wrapper > img,
body.single-post.category-projects article.et_pb_post > .et_post_meta_wrapper > .post-meta { display: none !important; }
body.single-post.category-projects #left-area { width: 100% !important; max-width: 860px; margin-inline: auto; float: none; padding-right: 0 !important; }
body.single-post.category-projects #sidebar { display: none !important; }
body.single-post.category-projects #main-content .container::before { display: none !important; }
body.single-post.category-projects .entry-title { text-align: center; margin-bottom: 1.5rem; }
body.single-post.category-projects .pcs-story .wp-block-buttons { justify-content: center; }
/* Divi-native bands: kill Divi's row padding (2% top/bottom on every row was
   doubling the gap between the heading and the grid — Gus: "too much"), and pad
   the bottom by the design's sweep so the NEXT band's dome does not eat the
   last row of cards ("contents are covered by the next section"). */
.pcrs-stories-band .et_pb_row, .pcrs-gallery-band .et_pb_row, .pcrs-blogroll-band .et_pb_row {
	padding-top: 0 !important; padding-bottom: 0 !important;
}
.pcrs-stories-band .et_pb_code, .pcrs-gallery-band .et_pb_code, .pcrs-blogroll-band .et_pb_code { margin-bottom: var(--wp--preset--spacing--30) !important; }
.pcrs-stories-band .et_pb_blog_grid_wrapper, .pcrs-blogroll-band .et_pb_blog_grid_wrapper, .pcrs-stories-band .et_pb_blog, .pcrs-blogroll-band .et_pb_blog, .pcrs-gallery-band .et_pb_gallery { margin-bottom: 0 !important; }
.et_pb_section.pcrs-gallery-band.pcrs-gallery-band, .et_pb_section.pcrs-stories-band.pcrs-stories-band, .et_pb_section.pcrs-blogroll-band.pcrs-blogroll-band {
	padding-bottom: calc(var(--wp--preset--spacing--70) + clamp(2.2rem, 4.4vw, 4rem)) !important;
}
.pcrs-blogroll-band .ccmm-sec.pcrs-blogroll-head { padding-bottom: 0; padding-top: var(--wp--preset--spacing--60); }

/* ---------------------------------------------------------------------------
 * 16. Service-area redesign (scripts/build_area_pages.py) — the seven city
 *     pages + the /service-area/ hub. Only what §14's pillar atoms did not
 *     already cover.
 * ------------------------------------------------------------------------- */
/* Hero photo credit line — the hub keeps the old page's figcaption
   ("Our vans on the road in Connecticut") as a small caption under the CTA
   row, since the redesigned hero is full-bleed and has no <figcaption> slot.
   In normal flow (not absolutely positioned over the photo) so it cannot be
   lost behind the hero's own stacking context. */
.ccmm-hero-credit {
	margin: var(--wp--preset--spacing--20) 0 0;
	color: var(--wp--preset--color--support);
	opacity: .8;
	font-size: .72rem;
	letter-spacing: .02em;
}
/* A one-pair before/after ("Real work in Derby" etc.) reads as an accident in
   a two-column grid; centre it and cap its width instead. */
.pcrs-ba-grid.is-single { grid-template-columns: minmax(0, 32rem); justify-content: center; }
/* Hub "Every town we serve": county cards, two per row, chip grid inside. */
.ccmm-grid2.pcrs-counties { align-items: start; }
.pcrs-county-card h3 {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--10);
}
.pcrs-county-card .ccmm-count {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: .72rem;
	text-transform: uppercase;
	letter-spacing: .04em;
	color: var(--wp--preset--color--muted, #5c6484);
	white-space: nowrap;
}
.pcrs-county-card .ccmm-areas { justify-content: flex-start; margin-top: var(--wp--preset--spacing--20); }
@media (max-width: 760px) { .ccmm-grid2.pcrs-counties { grid-template-columns: 1fr; } }

/* Divi's inline responsive CSS (`.et_pb_section_N.et_pb_section { padding-bottom:0 !important }`
   from custom_padding) out-ranked the band padding on order; the band
   selectors above are now (0,3,0) so they win on specificity. */
/* Hero CTA row was landing 4–6px into the next band's dome (the hero paints
   above the dome by design, so the buttons visibly hung over it). Reserve the
   dome's height under the row. Measured on carpet/mold/derby at 1440 & 1024. */
.ccmm-sec.ccmm-hero .ccmm-hero-inner > .ccmm-cta-row:last-child,
.ccmm-sec.ccmm-hero .ccmm-hero-copy > .ccmm-cta-row:last-child {
	margin-bottom: calc(var(--ccmm-sweep, 3rem) + .75rem);
}

/* ─────────────────────────────────────────────────────────────────────────
   §17 · MOTION, EVERYWHERE (2026-08-18)

   Gus: "overall, you have been lazy with the animations it needs more on
   other pages." The design ships four entrance variants and four delay
   steps, and only our own renderers used them. pcrs-motion-plus.js now tags
   the rest of the site at runtime; this section supplies what that needs:
   two more delay steps, a left/fade variant, and the small always-on
   pieces the mockups never got — hover lift on cards, a slow drift on hero
   photos, a link underline sweep. Everything here is inside the design's own
   timing curve, and everything is off under prefers-reduced-motion.
   ───────────────────────────────────────────────────────────────────────── */

html.js .ccmm-d5 { animation-delay: .5s; }
html.js .ccmm-d6 { animation-delay: .62s; }

@keyframes pcrs-fade-left { from { opacity: 0; transform: translate3d(-22px, 0, 0); } to { opacity: 1; transform: none; } }
@keyframes pcrs-fade-in   { from { opacity: 0; } to { opacity: 1; } }
@keyframes pcrs-rise-soft { from { opacity: 0; transform: translate3d(0, 14px, 0) scale(.985); } to { opacity: 1; transform: none; } }

html.js .ccmm-a--left { animation-name: pcrs-fade-left; animation-duration: .75s; }
html.js .ccmm-a--fade { animation-name: pcrs-fade-in;   animation-duration: .6s; }
html.js .ccmm-a--soft { animation-name: pcrs-rise-soft; animation-duration: .7s; }

/* Rows of a comparison table and list items animate as lines, not blocks:
   a block transform on a <tr> breaks the table layout in Safari. */
html.js .ccmm-compare tbody tr.ccmm-a { animation-name: pcrs-fade-in; animation-duration: .5s; }

/* Cards get a hand: they lift a little under the pointer. The shadow is the
   design's own dome shadow, not a new colour. */
.ccmm-card,
.ccmm-card-light,
.pcrs-county-card,
.et_pb_post {
	transition: transform .35s cubic-bezier(.22, .7, .3, 1), box-shadow .35s cubic-bezier(.22, .7, .3, 1);
}
.ccmm-card:hover,
.ccmm-card-light:hover,
.pcrs-county-card:hover,
.et_pb_post:hover {
	transform: translateY(-5px);
	box-shadow: 0 22px 44px -26px color-mix(in srgb, var(--wp--preset--color--inkdeep, #101433) 70%, transparent);
}

/* Photo tiles: the frame holds still, the photograph moves. */
.ccmm-card-light.is-photo-tile > a,
.ccmm-card-light.is-photo-tile { overflow: hidden; }
.ccmm-card-light.is-photo-tile .ccmm-card-photo,
.ccmm-card .ccmm-card-photo {
	transition: transform .6s cubic-bezier(.22, .7, .3, 1);
	will-change: transform;
}
.ccmm-card-light.is-photo-tile:hover .ccmm-card-photo,
.ccmm-card:hover .ccmm-card-photo { transform: scale(1.045); }

/* Hero photograph drifts once on arrival — 14s, 3% — so the banner is not a
   flat plate. object-position is set per page inline, so scale only. */
@keyframes pcrs-hero-drift { from { transform: scale(1); } to { transform: scale(1.03); } }
html.js .ccmm-sec.ccmm-hero .ccmm-hero-media img {
	animation: pcrs-hero-drift 14s cubic-bezier(.3, .1, .3, 1) both;
	will-change: transform;
}

/* Prose links sweep an underline instead of blinking one on. */
.ccmm-prose a:not(.wp-block-button__link),
.ccmm-card p a:not(.wp-block-button__link),
.ccmm-card-light p a:not(.wp-block-button__link) {
	background-image: linear-gradient(currentColor, currentColor);
	background-size: 0 1px;
	background-position: 0 100%;
	background-repeat: no-repeat;
	text-decoration: none;
	transition: background-size .35s cubic-bezier(.22, .7, .3, 1);
}
.ccmm-prose a:not(.wp-block-button__link):hover,
.ccmm-card p a:not(.wp-block-button__link):hover,
.ccmm-card-light p a:not(.wp-block-button__link):hover { background-size: 100% 1px; }

/* Buttons already have the design's hover; add the press. */
.wp-block-button__link:active { transform: translateY(1px); }

/* FAQ rows open with a hinge rather than a jump. */
.ccmm-faq > details > summary { transition: color .25s ease, background-color .25s ease; }
.ccmm-faq > details[open] > summary ~ * { animation: pcrs-rise-soft .45s cubic-bezier(.22, .7, .3, 1) both; }

@media (prefers-reduced-motion: reduce) {
	.ccmm-card, .ccmm-card-light, .pcrs-county-card, .et_pb_post,
	.ccmm-card-light.is-photo-tile .ccmm-card-photo, .ccmm-card .ccmm-card-photo,
	html.js .ccmm-sec.ccmm-hero .ccmm-hero-media img {
		transition: none !important;
		animation: none !important;
		transform: none !important;
	}
}

/* ─────────────────────────────────────────────────────────────────────────
   §18 · Financing + Happy Customer Stories round-4 (2026-08-18)
   scripts/build_financing_page.py, scripts/build_stories_page.py.
   ───────────────────────────────────────────────────────────────────────── */
/* pcrs-design.css's own repeat(5,1fr) grid already gets a 3-up modifier
   (`.ccmm-grid5.ccmm-three`, §pcrs-design.css process cards); Financing's
   "How Financing Works" has 4 steps, which left a bare 1fr column of empty
   track on the right at every width. Never editing pcrs-design.css itself
   (project rule) — the same modifier pattern, one more class. */
.ccmm-grid5.ccmm-four { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .ccmm-grid5.ccmm-four { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .ccmm-grid5.ccmm-four { grid-template-columns: 1fr; } }

/* Happy Customer Stories tabs: 10 titles ("All Projects" + up to 9 services)
   in one `et_pb_tabs_controls` row wrapped to 2 lines cleanly at 1440 but
   crushed each `<a>` into a near-unreadable stack at 480 — no wrap gap, tap
   targets under the 24px minimum. Wrap with breathing room instead of
   letting Divi's own single-line flex row squeeze them. */
@media (max-width: 767px) {
	.et_pb_tabs .et_pb_tabs_controls {
		flex-wrap: wrap;
		gap: var(--wp--preset--spacing--10, .5rem);
	}
	.et_pb_tabs .et_pb_tabs_controls li {
		flex: 0 0 auto;
	}
	.et_pb_tabs .et_pb_tabs_controls li a {
		padding: .5em .9em;
		font-size: .82rem;
	}
}

/* ─────────────────────────────────────────────────────────────────────────
   §18b · BLOG PAGE REVAMP (scripts/build_blog_page.py, 2026-08-18)
   (§18 was claimed twice on the same day by two parallel streams; kept as
   18b rather than renumbering §19 out from under build_quote_page.py.)

   Gus, round 4: "Blog and Financing pages are shit. revamp them." The
   "browse by topic" row reuses `.ccmm-area-chip`'s pill (dark-band and
   light-band treatments already correct), but that chip was built as a
   static `<p>` label with a location pin dot (§ design.css ~1006) — wrong
   mark for a topic, and it never had a link hover because it was never a
   link. `.pcrs-topic-chip` is the same pill without the pin, plus a real
   hover state.
   ───────────────────────────────────────────────────────────────────────── */
.pcrs-topic-chip { padding-left: 1.2rem; text-decoration: none; transition: transform .25s ease, background-color .25s ease, border-color .25s ease, color .25s ease; }
.pcrs-topic-chip::before { content: none; }
.pcrs-topic-chip:hover {
	transform: translateY(-2px);
	background: var(--wp--preset--color--accent);
	border-color: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--onaccent);
}
@media (prefers-reduced-motion: reduce) {
	.pcrs-topic-chip { transition: none !important; }
	.pcrs-topic-chip:hover { transform: none !important; }
}

/* ─────────────────────────────────────────────────────────────────────────
   §19 · GET A QUOTE PAGE (scripts/build_quote_page.py, 2026-08-18)

   Gus: "Get a quote page is shit. revamp it." The one new thing this page
   needed that no other page does is a real, styled Divi contact form
   (et_pb_contact_form/et_pb_contact_field) sitting inside a .ccmm-sec band —
   Divi's own contact-form CSS is plain black-on-white input boxes, which
   read as a foreign object dropped into the design system. Everything below
   re-skins it onto the site's own tokens; nothing here changes Divi's
   markup or behaviour, only its paint. (§18 above already added
   `.ccmm-grid5.ccmm-four` for Financing's 4-step process — this page's own
   "What happens next" reuses that same class, nothing new needed for it.)
   ───────────────────────────────────────────────────────────────────────── */

/* Empty <h1 class="et_pb_contact_main_title"> — the module always prints
   the tag even with no title text set (the page's own <h2> heading above
   the form covers that job), and an empty h1 still carries margin. */
.et_pb_contact_main_title:empty { display: none; }

/* Row/column padding: same zero-then-reserve pattern §15/16 use for native
   Divi bands, so the form sits inside the tone band without Divi's own 2%
   row padding doubling the gap under the heading. */
.pcrs-quote-form-band .et_pb_row { padding-top: 0 !important; padding-bottom: 0 !important; }
.pcrs-quote-form-band .et_pb_code { margin-bottom: var(--wp--preset--spacing--40) !important; }
.et_pb_section.pcrs-quote-form-band.pcrs-quote-form-band {
	padding-bottom: calc(var(--wp--preset--spacing--70) + clamp(2.2rem, 4.4vw, 4rem)) !important;
}

/* Field grid: Divi floats half-width fields at 48%/48% with its own gutter;
   the site's own field/label type replaces Divi's defaults on top of that
   layout rather than fighting it. */
.pcrs-quote-form .et_pb_contact_field {
	margin: 0 0 var(--wp--preset--spacing--30);
}
.pcrs-quote-form .et_pb_contact_form_label {
	display: block;
	margin-bottom: .4em;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: .78rem;
	text-transform: uppercase;
	letter-spacing: .03em;
	color: var(--wp--preset--color--ink);
}
.pcrs-quote-form .input,
.pcrs-quote-form select.input,
.pcrs-quote-form textarea.input {
	width: 100%;
	border: 1px solid var(--wp--preset--color--line);
	border-radius: 10px;
	padding: .75rem .9rem;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink);
	background: var(--wp--preset--color--paper);
	transition: border-color .2s ease, box-shadow .2s ease;
}
.pcrs-quote-form textarea.input { min-height: 7.5rem; resize: vertical; }
.pcrs-quote-form .input:focus {
	outline: none;
	border-color: var(--wp--preset--color--mid);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--wp--preset--color--mid) 20%, transparent);
}
/* SMS-consent checkbox: Divi renders it as a hidden handle + a real
   checkbox in an options list, not through .et_pb_contact_form_label. */
.pcrs-quote-form .et_pb_contact_field_options_wrapper {
	display: flex;
	align-items: flex-start;
	gap: .6rem;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--soft);
}
.pcrs-quote-form .et_pb_contact_field_checkbox { display: flex; align-items: flex-start; gap: .6rem; }
.pcrs-quote-form .et_pb_contact_field_checkbox input[type="checkbox"] { margin-top: .2rem; flex: none; }
.pcrs-quote-form .et_pb_contact_field_options_title:empty { display: none; }
/* The site's migrated-in legacy Divi Theme Options custom CSS (wp_option
   divi_custom_css, not a file this build owns) carries an old rule —
   `.et_pb_contact_form_0.et_pb_contact_form_container .input[type="checkbox"]
   +label{color:#fff}` — written for a dark-band contact form elsewhere on
   the original site. It made the SMS-consent checkbox's own label white on
   this band's white background: present in the DOM, invisible on screen.
   That option loads after this stylesheet, so !important is the only way
   to win here; scoped tight to this form so it cannot flip any other page's
   checkbox back to white. */
.pcrs-quote-form .et_pb_contact_field_checkbox label {
	color: var(--wp--preset--color--soft) !important;
	text-transform: none !important;
	font-size: var(--wp--preset--font-size--small) !important;
	line-height: 1.5;
}
/* Submit button matches the site's own pill button rather than Divi's
   square grey default. */
.pcrs-quote-form .et_pb_contact_submit,
.pcrs-quote-form button.et_pb_button {
	display: inline-block;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 800;
	text-transform: uppercase;
	letter-spacing: .03em;
	border-radius: 999px;
	border: 2px solid transparent;
	padding: .9rem 1.9rem;
	background: var(--wp--preset--gradient--accent-btn);
	color: var(--wp--preset--color--onaccent);
	cursor: pointer;
	transition: transform .2s cubic-bezier(.22, .7, .3, 1);
}
.pcrs-quote-form .et_pb_contact_submit:hover { transform: translateY(-2px); }
.pcrs-form-note p { color: var(--wp--preset--color--soft); margin-top: calc(var(--wp--preset--spacing--30) * -1); }

/* Talk-to-us band's NAP line under the big phone number. */
.pcrs-nap {
	text-align: center;
	color: var(--wp--preset--color--onaccent, #fff);
	opacity: .85;
	font-size: var(--wp--preset--font-size--small);
	line-height: 1.6;
}

/* --------------------------------------------------------------------------
   1c-iii. Wordiness work-arounds (2026-08-19).
   The SEO copy delivered 2026-08-18 is far longer per section than the band
   design assumed, and the copy is fixed - it cannot be shortened. So the
   layout has to absorb the volume instead. Measured on /cleaning-services/
   carpet-cleaning/ before this block:

     authority band   131-word paragraph in a 536px column = 13 unbroken lines
     importance band   67-word paragraphs, same 536px column =  8 lines each
     buying guide     223 words as ONE centred paragraph at 701px = 12 lines

   Three CSS-only moves, no markup, no builder and no copy changes:

   (a) Rebalance the two-column bands. The photo does not need half the row
       when the prose beside it runs multiple paragraphs. :has() keeps this
       targeted - single-paragraph bands keep the original 1fr 1fr balance, so
       the ~35 template pages and the short bands are untouched.
   (b) Separate stacked paragraphs properly, so two paragraphs read as two
       blocks rather than one grey slab. Line-height is deliberately NOT
       tightened - 1.62 is doing real readability work at this measure.
   (c) Flow the wide centred prose blocks into two columns. These are the
       buying-guide and service-area bands: a single long paragraph with no
       list, card or image to break it up, and the full row width going unused.

   Everything here is reversible by deleting this block. Verify by measuring
   rendered paragraph line counts, not by eye.
   -------------------------------------------------------------------------- */
@media (min-width: 901px) {
	/* REVERTED 2026-08-19. A 1.5fr/1fr rebalance here gave the prose ~107px more
	   width (13 lines -> 11 on the worst paragraph) but narrowed every photo
	   column from 50% to 40%. Those photos are object-fit: cover, so a narrower
	   box crops them harder - the team lineup, the owner portraits and the
	   individual headshots all visibly lost framing. Two lines of prose is not
	   worth degrading every image in the band. Left at the design's 1fr 1fr. */
	/* Widen ONLY when the block is a single paragraph that will actually be
	   columned. A centred multi-paragraph block widened to 96ch just becomes
	   long centred lines, which is harder to read than the 74ch default it
	   replaced - that is what happened to the "Areas We Serve" band. */
	.ccmm-sec .ccmm-prose.pcrs-center:has(> p:only-child) {
		max-width: 96ch;
	}
	.ccmm-sec .ccmm-prose.pcrs-center > p:only-child {
		columns: 2;
		column-gap: var(--wp--preset--spacing--50, 3rem);
		text-align: left;
	}
}
.ccmm-sec .ccmm-prose p + p {
	margin-top: var(--wp--preset--spacing--30, 1.4rem);
}

/* --------------------------------------------------------------------------
   1c-iv. Process grid: 5-up is wrong for the delivered copy (2026-08-19).
   `.ccmm-grid5` is repeat(5,1fr) and `.pcrs-steps` only narrows it BELOW
   1240px, so at desktop the 8-step process rendered five 205px cards with
   161px of text measure. The SEO copy runs 70-85 words per step, which turned
   every card into a 23-29 line ribbon - measured on / and on
   /cleaning-services/carpet-cleaning/.

   pcrs-design.css already records this exact failure for the 3-step case:
   "three cards in five columns left each one a narrow ribbon of wrapped text",
   and answers it with a column-count modifier (.ccmm-three / .ccmm-four).
   Same answer here, but applied by CSS rather than a new class, because the
   markup is generated by build_pillar.py for 13 pages and this needs no
   builder change.

   4-up, not 3-up: eight steps divide evenly into 4+4 with no orphan row, and
   ~260px cards cut the ribbon from 29 lines to about 15. The existing
   <=900px (2-up) and <=560px (1-up) rules above still govern below 901px.
   -------------------------------------------------------------------------- */
@media (min-width: 901px) {
	.pcrs-steps.ccmm-grid5 { grid-template-columns: repeat(4, 1fr); }
}

/* Full-width prose blocks (a .ccmm-prose that is a direct child of .ccmm-wrap,
   i.e. NOT sharing a row with a photo) also flow into two columns. The buying-
   guide band on / is a single 197-word paragraph that rendered 18 lines deep at
   701px with no list, card or image to break it. Excludes .ccmm-grid2 prose,
   which is handled by the ratio rule above, and anything already columned. */
@media (min-width: 901px) {
	.ccmm-sec .ccmm-wrap > .ccmm-prose:has(> p:only-child) { max-width: 96ch; }
	.ccmm-sec .ccmm-wrap > .ccmm-prose > p:only-child {
		columns: 2;
		column-gap: var(--wp--preset--spacing--50, 3rem);
	}
}

/* --------------------------------------------------------------------------
   1c-v. Label + description list items (2026-08-19).
   `.ccmm-check-list` is a check-mark badge component built for short items
   ("IICRC-Certified Firm", "Family-Owned Since 1987"). The homepage's secondary
   services list reuses it for `<strong>Label:</strong> <25-word description>`,
   which it was never designed to hold: the labels do not line up and each item
   reads as one run-on line.

   Give only those items a two-column definition layout - label in a fixed
   column, description in the rest - so the labels align and the descriptions
   are scannable. The :has() guard means genuine short badge items in the same
   component are untouched.
   -------------------------------------------------------------------------- */
.ccmm-sec .ccmm-check-list li:has(> strong:first-child) {
	display: grid;
	grid-template-columns: minmax(10rem, 15rem) 1fr;
	column-gap: var(--wp--preset--spacing--30, 1.4rem);
	align-items: baseline;
}
.ccmm-sec .ccmm-check-list li:has(> strong:first-child) > strong {
	text-wrap: balance;
}
@media (max-width: 700px) {
	.ccmm-sec .ccmm-check-list li:has(> strong:first-child) {
		grid-template-columns: 1fr;
		row-gap: .25rem;
	}
}

/* Headshots keep their native 3/4 portrait aspect. `.is-photo-tile` forces
   4/3 + object-fit: cover, which is right for landscape job photos but threw
   away ~44% of every portrait on the team roster and cut through faces. */
.ccmm-card-light.is-photo-tile.is-person img {
	aspect-ratio: 3 / 4;
	object-position: 50% 25%;
}

/* --------------------------------------------------------------------------
   1c-vi. Photo tiles match the library, not the other way round (2026-08-19).
   Measured across /about-us/: 13 of 31 images were losing 15%+ of the source
   and most were losing 44-55%. Every photo-tile source on the page is PORTRAIT
   - 958x1600 (0.60), 969x1600 (0.61), 1170x1585 (0.74), 1200x1600 (0.75),
   1600x2133 (0.75). They are phone photos taken on job sites. `.is-photo-tile`
   forced `aspect-ratio: 4/3` landscape with object-fit: cover, so roughly half
   of nearly every image on the site was thrown away.

   4/5 (0.8) is the best fit for this library: a 0.75 source now loses ~6%
   instead of 44%, and the tiles stay shorter than a full 3/4 would make them.
   `is-person` keeps its own portrait rule below.

   If a genuinely landscape photo is ever added to a tile it will crop instead -
   the right answer then is a per-image aspect from attachment-dims.json at
   build time, not a different blanket value here. */
.ccmm-card-light.is-photo-tile img {
	aspect-ratio: 4 / 5;
}
/* Owner and roster portraits are natively 0.8 (900x1125), so 4/5 is exact -
   3/4 was costing them 6% for no reason. */
.ccmm-card-light.is-photo-tile.is-person img {
	aspect-ratio: 4 / 5;
	object-position: 50% 20%;
}

/* Cards in a two-up row must share a baseline. `.ccmm-grid2` centres its items,
   so the Amy/Curtis cards (883px vs 924px, because one bio is longer) sat
   visibly unaligned. Stretch only rows that actually contain cards. */
.ccmm-sec .ccmm-grid2:has(> .ccmm-card-light) { align-items: stretch; }
.ccmm-sec .ccmm-grid3:has(> .ccmm-card-light) { align-items: stretch; }
