/**
 * Pure Gutter Solutions — child theme custom CSS.
 *
 * Scope: styling that Elementor controls cannot express. Right now that is the two
 * CPT-driven card lists rendered by inc/shortcodes.php — their markup comes from PHP, so
 * Elementor has no widget controls over it.
 *
 * Every rule below is lifted from the approved source build's stylesheet
 * (pure-gutter-solutions-html/assets/css/style.css) rather than re-authored, so the rendered
 * result matches the design instead of approximating it. Breakpoints follow the source's
 * desktop-first ladder: 1199 / 991 / 767 / 575.
 */

/* ---------------------------------------------------------------------------
 * Tokens the lifted rules depend on.
 *
 * Scoped to the two card blocks rather than :root so they cannot leak into or fight with
 * Elementor's own global variables elsewhere on the page.
 *
 * NOTE on --clr-grey-decor-soft: the project carries a WCAG correction mapping #A8B7C2 to
 * #6B757C, but that correction is specifically for "lighter grey on white". Card text here
 * sits on a near-opaque navy scrim, where #A8B7C2 has ample contrast — applying the
 * white-background correction would darken text against a dark background and make it worse.
 * ------------------------------------------------------------------------ */
.s5__carousel,
.s10-cards {
	--clr-white: #ffffff;
	--clr-navy: #031f2d;
	--clr-grey-decor-soft: #a8b7c2;
	--sh-card: 0 4px 20px rgba( 3, 31, 45, 0.08 );
	--sh-card-hover: 0 10px 30px rgba( 3, 31, 45, 0.14 );
	--t-base: 250ms ease;
}

/* ---------------------------------------------------------------------------
 * Services carousel (s5)
 * ------------------------------------------------------------------------ */
.s5__carousel {
	width: 100%;
}

.s5-card {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	height: 628px;
	border-radius: 30.82px;
}

.s5-card--flush-l {
	border-radius: 0 30.82px 30.82px 0;
}

.s5-card--flush-r {
	border-radius: 30.82px 0 0 30.82px;
}

/**
 * Double-classed for the same reason as the title below.
 *
 * A theme/kit rule reaching plain `img` at (0,1,1) — e.g. `.elementor-kit-12 img` — beats a
 * single-class `.s5-card__img` (0,1,0) on `height`, so `height:100%` silently lost and every
 * card image fell back to its natural aspect ratio at the card's width. Measured result:
 * 616 / 609 / 841 / 586px tall against a 628px card, i.e. card 3 overshooting by 213px, which
 * was 36% of the whole page's diff pixels at 1920. `width` happened to survive because 100% and
 * the fallback agree; `height` did not. Restated at (0,2,0).
 */
.s5-card .s5-card__img {
	position: absolute;
	inset: 0;
	z-index: -2;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform var( --t-base );
}

.s5-card .s5-card__scrim {
	position: absolute;
	inset: 0;
	z-index: -1;
	background-image: linear-gradient(
		180deg,
		rgba( 3, 31, 45, 0 ) 38%,
		rgba( 3, 31, 45, 0.55 ) 64%,
		rgba( 3, 31, 45, 0.96 ) 100%
	);
}

.s5-card__body {
	position: absolute;
	inset-inline: 0;
	bottom: 0;
	display: flex;
	flex-direction: column;
	gap: 14px;
	padding: 40px;
}

/**
 * Card title / text are deliberately double-classed.
 *
 * The heading here is a real <h3>, so Elementor's Theme Style rule `.elementor-kit-12 h3`
 * (specificity 0,1,1) beats a single-class `.s5-card__title` (0,1,0) and was overriding BOTH
 * the colour (to navy #031F2D, illegible on the photo) and the size (to the kit's 24px h3
 * clamp). `.s5-card .s5-card__title` is (0,2,0), which wins on class count without needing
 * !important. Every typographic property the kit could reach is restated for the same reason.
 */
.s5-card .s5-card__title {
	margin: 0;
	font-size: 30px;
	line-height: 39.77px;
	font-weight: 600;
	letter-spacing: -0.78px;
	color: var( --clr-white );
	transition: transform var( --t-base );
}

.s5-card .s5-card__text {
	width: 380px;
	max-width: 100%;
	margin: 0;
	font-size: 18px;
	line-height: 24px;
	font-weight: 400;
	color: var( --clr-grey-decor-soft );
}

.s5__dots {
	display: none;
}

/* ---------------------------------------------------------------------------
 * Service-area city cards (s10)
 * ------------------------------------------------------------------------ */
/**
 * `minmax( 0, 343px )`, NOT a flat `343px`.
 *
 * The source ships `repeat( 2, 343px )` here, and lifting it verbatim also lifted its bug: two
 * fixed 343px columns plus a 20px gap need 706px, and between 1200 and 1439 the parent column is
 * narrower than that, so the grid overflowed its container — measured +160px of horizontal page
 * overflow at 1280, which fails the build's zero-overflow hard gate (BUILD-SPEC §16.2). The
 * source's own ≤1199 media query fixes it below 1200 but leaves the 1200-1439 band broken.
 *
 * minmax( 0, 343px ) caps the column at the design's 343px where there is room and lets it shrink
 * where there is not, so every width is correct with no extra breakpoint.
 */
.s10-cards {
	display: grid;
	grid-template-columns: repeat( 2, minmax( 0, 343px ) );
	gap: 20px;
	margin: 0 0 24px;
	padding: 0;
	list-style: none;
}

.s10-cards > li {
	margin: 0;
}

.s10-card {
	display: flex;
	align-items: center;
	gap: 15px;
	height: 84px;
	padding: 19px 20.7px;
	border-radius: 14px;
	background-color: var( --clr-white );
	box-shadow: var( --sh-card );
	transition: box-shadow var( --t-base );
	text-decoration: none;
}

.s10-card img {
	flex: 0 0 auto;
	width: 44px;
	height: 44px;
}

.s10-card__name {
	font-size: 20px;
	line-height: 26px;
	font-weight: 600;
	color: var( --clr-navy );
}

/* ---------------------------------------------------------------------------
 * Hover — pointer devices only, so a tap on touch does not leave a stuck state.
 * ------------------------------------------------------------------------ */
@media ( hover: hover ) and ( pointer: fine ) {

	.s5-card:hover .s5-card__img {
		transform: scale( 1.04 );
	}

	.s5-card:hover .s5-card__title {
		transform: translateX( 4px );
	}

	.s10-card:hover {
		box-shadow: var( --sh-card-hover );
	}
}

/* ---------------------------------------------------------------------------
 * Responsive ladder (desktop-first, matching the source)
 * ------------------------------------------------------------------------ */
@media ( max-width: 1199px ) {

	.s5-card {
		height: 560px;
	}

	.s5-card__body {
		padding: 32px;
	}

	.s10-cards {
		grid-template-columns: repeat( 2, minmax( 0, 1fr ) );
	}
}

@media ( max-width: 991px ) {

	.s5-card {
		height: 500px;
	}

	.s5-card--flush-l,
	.s5-card--flush-r {
		border-radius: 30.82px;
	}
}

@media ( max-width: 767px ) {

	.s5-card {
		height: 440px;
	}

	.s5-card__body {
		padding: 24px;
	}

	.s5-card .s5-card__title {
		font-size: 26px;
		line-height: 32px;
	}

	.s5__dots {
		display: block;
		position: static;
		margin-top: 24px;
	}

	.s10-cards {
		grid-template-columns: minmax( 0, 1fr );
	}
}

@media ( max-width: 575px ) {

	.s5-card {
		height: 400px;
	}
}

/* ---------------------------------------------------------------------------
 * Reduced motion — the source honours this globally; the card transitions are the
 * only motion this stylesheet introduces.
 * ------------------------------------------------------------------------ */
@media ( prefers-reduced-motion: reduce ) {

	.s5-card__img,
	.s5-card .s5-card__title,
	.s10-card {
		transition: none;
	}

	.s5-card:hover .s5-card__img,
	.s5-card:hover .s5-card__title {
		transform: none;
	}
}

/* ---------------------------------------------------------------------------
 * Screen-reader-only heading (rung d).
 *
 * The source keeps an off-screen <h2> on section2 (id="s2-heading") and s9
 * (id="s9-heading") purely so the section has an accessible name via
 * aria-labelledby — never intended to paint. Elementor's Heading widget has
 * no "visually hidden" control, so these headings are pushed with
 * `_css_classes: 'pgs-sr-only'` and rely on this rule to hide them. Lifted
 * verbatim from the source's own `.visually-hidden` (style.css line 216).
 * ------------------------------------------------------------------------ */
.pgs-sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect( 0, 0, 0, 0 );
	white-space: nowrap;
	border: 0;
}

/* ---------------------------------------------------------------------------
 * section2 trust-card decorative glow blur (rung d).
 *
 * A Container's css_filters_* group is registered against
 * `{{WRAPPER}} > .elementor-background-overlay`, never the element itself, so there is no
 * Elementor control that can blur a Container's own background. Values are the source's
 * measured blur radii (pages/homepage/build/tree-section2.php $blur column).
 * ------------------------------------------------------------------------ */
.pgs-glow-a {
	filter: blur( 49.97px );
}

.pgs-glow-b {
	filter: blur( 43.24px );
}

/* ---------------------------------------------------------------------------
 * Footer contact email — prevent a single unbreakable token from overflowing.
 *
 * "zwertz@pureguttersolutions.com" has no break opportunity, so at 1024 it pushed the footer
 * column wider than its container and produced 15px of horizontal page overflow — a
 * zero-overflow hard-gate failure (BUILD-SPEC §16.2). No Elementor control exposes
 * overflow-wrap, so it belongs here.
 *
 * `anywhere` rather than `break-word`: break-word does not shrink the element's min-content
 * contribution, so a flex/grid parent still reserves the full token width and the overflow
 * survives. Scoped to the footer template's contact block so it cannot alter wrapping elsewhere.
 * ------------------------------------------------------------------------ */
.elementor-location-footer a[href^="mailto:"],
.elementor-location-footer .elementor-widget-heading a {
	overflow-wrap: anywhere;
	word-break: normal;
}

/* ===========================================================================
   SITE HEADER
   Transcribed from the source stylesheet's own contract:
     --header-h        125px  ->  115px (<=1439)  ->  96px (<=991)
     --header-h-stuck   72px  ->                      68px (<=991)
     logo img          width 161px  ->  92px when stuck (aspect-ratio 7/5)
     scroll-padding-top = --header-h-stuck
   Elementor emits its own rules at (0,4,0) — e.g.
   `.elementor-69 .elementor-element.elementor-element-7480b7f:not(...)`,
   which sets background-color:rgba(0,0,0,0). Every rule below therefore reaches
   at least (0,5,0) WITHOUT naming `.elementor-69`, so nothing here breaks if the
   header template is ever re-saved under a different post ID.
   =========================================================================== */

/* Base — the unstuck bar sits transparent over the hero. */
.elementor-location-header .elementor-element.pgs-site-header.e-con .pgs-header-inner {
	min-height: 125px;
}

/* Stuck: solid navy + shadow. Without this the header stays transparent over
   white page content and the white logo, nav and phone number vanish. */
.elementor-location-header .elementor-element.pgs-site-header.e-con.elementor-sticky--effects {
	background-color: #176da6;
	box-shadow: 0 2px 16px rgba(0, 0, 0, .12);
}

.elementor-location-header .elementor-element.pgs-site-header.e-con.elementor-sticky--effects .pgs-header-inner {
	min-height: 72px;
}

.elementor-location-header .pgs-site-header.elementor-sticky--effects .elementor-element-76790fc img {
	width: 92px;
	height: auto;
}

.pgs-site-header,
.pgs-site-header .pgs-header-inner,
.pgs-site-header .elementor-element-76790fc img {
	transition: background-color .25s ease, box-shadow .25s ease,
	            min-height .25s ease, width .25s ease;
}

@media (prefers-reduced-motion: reduce) {

	.pgs-site-header,
	.pgs-site-header .pgs-header-inner,
	.pgs-site-header .elementor-element-76790fc img {
		transition: none;
	}
}

/* In-page anchors must clear the stuck header (BUILD-SPEC §5). */
html {
	scroll-padding-top: 72px;
}

@media (max-width: 1439px) {

	.elementor-location-header .elementor-element.pgs-site-header.e-con .pgs-header-inner {
		min-height: 115px;
	}

	.elementor-location-header .elementor-element.pgs-site-header.e-con.elementor-sticky--effects .pgs-header-inner {
		min-height: 72px;
	}
}

@media (max-width: 991px) {

	html {
		scroll-padding-top: 68px;
	}

	/* One row: logo left, actions right — matching the source, which never
	   stacks them. Left to itself Elementor wrapped them and the bar grew to
	   189px at 390. */
	.elementor-location-header .elementor-element.pgs-site-header.e-con .pgs-header-inner {
		min-height: 96px;
		flex-direction: row;
		flex-wrap: nowrap;
		align-items: center;
		justify-content: space-between;
		gap: 12px;
	}

	.elementor-location-header .elementor-element.pgs-site-header.e-con.elementor-sticky--effects .pgs-header-inner {
		min-height: 68px;
	}

	/* 161x115 -> 134x96, aspect ratio preserved, so the logo fits the 96px bar. */
	.elementor-location-header .pgs-site-header .elementor-element-76790fc img {
		width: 134px;
		height: auto;
	}

	.elementor-location-header .pgs-site-header.elementor-sticky--effects .elementor-element-76790fc img {
		width: 92px;
	}

	/* The actions group must not stretch to a full row of its own. */
	.elementor-location-header .pgs-site-header .elementor-element-b8a099e {
		flex: 0 0 auto;
		width: auto;
	}

	.elementor-location-header .pgs-site-header .elementor-element-76790fc {
		flex: 0 0 auto;
		width: auto;
	}
}

/* Below 767 the sticky bottom bar (BUILD-SPEC §7) already carries Call + Free
   Quote, so the header's duplicate CTA is dropped rather than wrapped. */
@media (max-width: 767px) {

	.elementor-location-header .pgs-site-header .elementor-element-743471a {
		display: none;
	}
}

/* Between 992 and 1439 the desktop nav is still shown, but the flex row squeezed
   the <ul> to 489px against 566px of items, so it wrapped to two rows and pushed
   the stuck bar from 72px to 90px. Let the nav claim its natural width and stop
   it wrapping. */
@media (max-width: 1439px) and (min-width: 992px) {

	.elementor-location-header .pgs-site-header .elementor-element-1961150 {
		flex: 0 0 auto;
		width: auto;
	}

	.elementor-location-header .pgs-site-header nav ul.elementor-nav-menu {
		flex-wrap: nowrap;
	}

	.elementor-location-header .pgs-site-header nav ul.elementor-nav-menu > li > a {
		margin-right: 6px;
	}
}

/* Below 1200 the row genuinely cannot hold logo + 6 nav items + phone + CTA:
   it needs 1079px against 1036px at 1100, and every child is shrink-0. The
   source hits the same collision and simply overflows (docW 1222 at 1199).
   Rather than copy that, drop the phone TEXT here (the source hides it at <=991
   anyway, and it returns above 1200), pin the flex gap, and let the actions
   group shrink. */
@media (max-width: 1199px) and (min-width: 992px) {

	.elementor-location-header .pgs-site-header .elementor-element-5adf518 {
		display: none;
	}

	.elementor-location-header .elementor-element.pgs-site-header.e-con .pgs-header-inner {
		gap: 12px;
	}

	.elementor-location-header .pgs-site-header .elementor-element-b8a099e {
		flex: 0 1 auto;
	}

	.elementor-location-header .pgs-site-header nav ul.elementor-nav-menu > li > a {
		margin-right: 2px;
	}

	/* Last 16px at 992: scale the logo to the same 134px used below 991. */
	.elementor-location-header .pgs-site-header .elementor-element-76790fc img {
		width: 134px;
		height: auto;
	}
}

/* ---------------------------------------------------------------------------
   Mobile drawer: release the body scroll lock on close.

   Elementor Pro's off-canvas widget locks scroll with TWO classes:
     .e-off-canvas__no-scroll            added on open, removed by enableScroll()
     .e-off-canvas__no-scroll-animation  added on close, removed by
                                         removeAnimationClasses()

   Both resolve to `overflow:hidden`. removeAnimationClasses() only strips the
   second one inside `if (isExitAnimation)`, which runs off the exit animation's
   `animationend` event. This drawer has no exit animation configured, so that
   event never fires, the class is never removed, and the page stays unscrollable
   after the menu is closed — wheel and touch both dead.

   Neutralise the animation-only lock whenever the real lock is absent (i.e. the
   drawer is shut). While the drawer is open .e-off-canvas__no-scroll is present,
   so the intended lock still applies. Values restore body's own default
   (overflow-x:clip / overflow-y:visible) rather than a blanket `visible`, which
   would re-enable horizontal scrolling.
   --------------------------------------------------------------------------- */
body.e-off-canvas__no-scroll-animation:not(.e-off-canvas__no-scroll) {
	overflow-x: clip;
	overflow-y: visible;
}

/* ---------------------------------------------------------------------------
   Hero photo alignment.

   The photo frame itself is already exact (677,207,1243x828 — Figma's own
   "Content Frame" bounds). The supplied asset, however, sits a constant 32px
   lower than the design render: the roof edge measures +32px at x=1550/1650/
   1750/1850. Figma's per-node export for this frame is a 225x161 thumbnail, so
   there is no higher-fidelity asset to swap in — the content is nudged instead.

   Scale 1.0387 (828 -> 860) covers the 32px the upward shift would otherwise
   leave blank at the bottom; the ~48px of extra width is clipped by the frame.
   --------------------------------------------------------------------------- */
.elementor-element-abba3f6 {
	overflow: hidden;
}

.elementor-element-abba3f6 img {
	transform: translateY(-32px) scale(1.0387);
	transform-origin: center top;
}

/* ---------------------------------------------------------------------------
   Hero estimate card — stack below the copy at <=991 instead of floating over it.

   The card is position:absolute with z-index:3 so it can sit to the right of the
   headline on desktop. Elementor's responsive Position control cannot undo that:
   an empty `position_tablet` INHERITS the desktop value rather than resetting to
   static, so the card kept floating and covered the "Get My Free Quote" /
   "Discover more" buttons at every width from 991 down.

   The source stacks them in a single-column grid at this breakpoint
   (.hero__grid {grid-template-columns:minmax(0,1fr)}), which is what this
   reproduces. Reaches (0,4,1) to outrank Elementor's own (0,4,0) rule without
   naming the page's post ID.
   --------------------------------------------------------------------------- */
@media (max-width: 991px) {

	body.elementor-page .e-con.elementor-element.elementor-element-a7e6d14 {
		position: static;
		margin-top: 32px;
	}
}

/* ---------------------------------------------------------------------------
   Section2 top arrow.

   The design has a small white triangle rising out of the section's top edge at
   the page centre — measured off the design render: apex (960, 965), base 28px
   wide meeting the edge at y=979, 45-degree sides (half-width grows exactly 1px
   per row). It was missing entirely; the live edge was flat.

   Drawn as a border triangle on ::before rather than an extra Elementor element,
   so it stays glued to the section edge at every breakpoint and adds no node to
   the tree.
   --------------------------------------------------------------------------- */
.elementor-element.elementor-element-eed87f5::before {
	content: "";
	position: absolute;
	left: 50%;
	top: -14px;
	width: 0;
	height: 0;
	margin-left: -14px;
	border-left: 14px solid transparent;
	border-right: 14px solid transparent;
	border-bottom: 14px solid #FFFFFF;
	pointer-events: none;
}

/* ===========================================================================
   S6 — BEFORE/AFTER COMPARISON (BEAF slider)
   The static two-pane + handle-image arrangement was replaced with the BEAF
   plugin's Elementor widget (beaf-slider -> [bafg id="139"]) because the drag
   reveal BUILD-SPEC 11.4 requires cannot be built from Elementor controls —
   plan/05-s6.md recorded it as "pointer-drag reveal | not built".

   BEAF renders its own markup, so three things have to be re-imposed here.
   None is reachable through an Elementor control, because the controls stop at
   the widget boundary and everything below it belongs to the plugin:

   1. HEIGHT. BEAF sizes .twentytwenty-container to the images' own aspect
      ratio (1400 x 420/431 = 1364px), which overrode the frame's designed
      min-height ladder (540/460/400/320/260). Taking the widget out of flow
      hands the height back to the frame and lets overflow:hidden + the 20px
      radius clip the slider as the design intends.
   2. FIT. BEAF emits object-fit:fill, which stretches a 431x420 source into a
      1400px-wide frame. The design crops rather than distorts, at the same
      50% 40% focal point the old panes used (the rung-(d) note in
      plan/05-s6.md).
   3. DESATURATION. The old before-pane carried css_filters_saturate:0. BEAF
      has no per-image filter setting, so it moves here, onto the image
      twentytwenty tags as .twentytwenty-before.

   The designed corner tabs are NOT recreated here: the two chip widgets
   (c8d907f / cf2e133) were kept and moved up to the frame, so they remain real
   Elementor elements with their own controls. BEAF's built-in labels render
   nothing in this configuration, so there is nothing to suppress.

   Everything is scoped to .pgs-ba-frame so no other BEAF slider on the site is
   affected.
   --------------------------------------------------------------------------- */
.pgs-ba-frame > .pgs-ba-beaf {
	position: absolute;
	inset: 0;
	z-index: 1;
}

.pgs-ba-frame > .pgs-ba-beaf > .elementor-widget-container,
.pgs-ba-frame .twentytwenty-wrapper,
.pgs-ba-frame .twentytwenty-container {
	height: 100%;
}

.pgs-ba-frame .twentytwenty-container > img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: 50% 40%;
}

.pgs-ba-frame .twentytwenty-container > img.twentytwenty-before {
	filter: saturate(0);
}

/* Slide title / description / read-more are unused: this section supplies its
   own eyebrow, h2 and lede as Elementor widgets. */
.pgs-ba-frame .bafg-slider-info-wraper {
	display: none;
}

/* ===========================================================================
   s7 "The honest version" — compare-row three-stop gradient
   ---------------------------------------------------------------------------
   Elementor's gradient control exposes only TWO colour stops (background_color
   + background_color_b), so the design's ramp cannot be authored from the UI:
       #FFF5F5 0%  ->  #FFFFFF 48%  ->  #F4FAF3 100%
   The built page therefore lost the white midpoint and read as a flat
   pink->green blend. Confirmed against the design render, where the row is
   #FFF5F5 at its left edge and reaches white around x=790 before the good
   column's opaque white panel covers the rest of the ramp.

   Specificity is 0,4,0 to match Elementor's own
     .elementor-60 .elementor-element.elementor-element-5d1816c:not(...)
   and win on load order — custom.css is enqueued after post-60.css (verified
   in the rendered head), so no !important is needed.
   =========================================================================== */
.elementor .e-con.elementor-element.pgs-s7-compare {
	background-image: linear-gradient(90deg, #FFF5F5 0%, #FFFFFF 48%, #F4FAF3 100%);
}

/* ---------------------------------------------------------------------------
   s7 offer form — ZIP field width
   The design's ZIP input measures 360px inside a 691px column (border box
   x320..679, verified off the design render), i.e. 53.4% of the 711px Elementor
   fields wrapper once its -10px gutter is accounted for. Elementor Pro's form
   field width control is a fixed preset list (100/80/75/66/60/50/40/33/25/20),
   so the nearest selectable value (50) leaves the input 24px short and pulls
   the submit button 23px left of its designed x=700. The field is set to 50 in
   the widget so the Elementor editor stays representative; this rule supplies
   the exact figure on desktop only, as a percentage so it stays fluid.
   Below 1024px the field's own width_mobile/width_mobile_extra = 100 take over
   and the row stacks, so this must not apply there.

   53.2%, not the arithmetic 53.4%: the submit group is content-sized at 332px
   (a 312px button + the 2x10px gutter) and the wrapper is 711px, so the ZIP
   group cannot exceed 379px without the button wrapping onto a second row.
   53.4% computed to 379.7px and did exactly that. 53.2% -> 378.3px keeps them
   on one line, putting the input at 358px and the button at x699 (design:
   360px / x700). The remaining ~2px is the live button rendering 312px wide
   against the design node's 310px.
   --------------------------------------------------------------------------- */
@media (min-width: 1024px) {
	.pgs-offer-form .elementor-field-group.elementor-field-group-offer-zip {
		width: 53.2%;
	}
}

/* ===========================================================================
   s8 "Featured projects" — Loop Carousel progress bar
   ---------------------------------------------------------------------------
   The design puts a 3px progress bar BELOW the cards, spanning the section's
   1400px boxed content (x260..1660 at 1920), with an #E2E8EE track and a green
   fill. Elementor's Loop Carousel exposes `pagination: progressbar` but NO
   position or colour controls for it (only bullets and fraction get
   `*_position` / spacing controls — verified against the widget's own control
   list), and Swiper's default progressbar is absolutely positioned at the TOP
   of the slider, full slider width, in black. All four of those have to be
   corrected here.

   The slider wrapper is deliberately 2028px wide (3 slides x 660 + 2 x 24 gap)
   so Elementor computes the design's exact 660px slide; the bar must NOT
   inherit that width, hence the explicit 1400px to match `boxed_width`.

   `padding-bottom: 51px` on the slider creates the design's 48px gap between
   the cards and the bar (51 - the bar's own 3px). The widget's own `_padding`
   is zeroed so this is the only source of that space.
   =========================================================================== */
.pgs-s8-carousel .swiper {
	padding-bottom: 51px;
}

/* Specificity 0,3,0 (three classes) deliberately: Swiper v8 ships
   `.swiper-pagination-progressbar.swiper-pagination-horizontal{width:100%;height:4px;top:0}`
   at 0,2,0 and hardcodes the 4px — there is no --swiper-pagination-progressbar-size
   variable in v8 to override. A matching 0,2,0 selector here is decided by load
   order, which is not reliable for a conditionally-enqueued plugin stylesheet, so
   the extra class removes the ambiguity. */
.pgs-s8-carousel .swiper-pagination.swiper-pagination-progressbar {
	top: auto;
	bottom: 0;
	left: 0;
	/* The bar must span the section's BOXED content at every breakpoint. It cannot
	   use `max-width: 100%`: its offset parent is the 2028px slider, so 100% is
	   2028 and the bar measured 1132/1031/854px at 430/390/320 - far wider than the
	   screen. Derived from the viewport minus the section's own padding ladder
	   instead (--s8-gutter below mirrors 9e0c090's padding exactly). */
	width: min(1400px, calc(100vw - 2 * var(--s8-gutter, 40px)));
	height: 3px;
	border-radius: 2px;
	background-color: #E2E8EE;
}

.pgs-s8-carousel .swiper-pagination.swiper-pagination-progressbar .swiper-pagination-progressbar-fill {
	border-radius: 2px;
	background-color: #27A800;
}

/* ---------------------------------------------------------------------------
   s8 — clip the carousel bleed at the VIEWPORT, not at the boxed content
   ---------------------------------------------------------------------------
   The design deliberately lets the next card bleed past the 1400px boxed
   content to the viewport edge (measured on the design render: card 3 is
   visible from x1628 all the way to x1919 at a 1920 canvas). Two wrong ways to
   get there, both tried and rejected:

   - The rail container's own `overflow: hidden` (as the original build had)
     clips at the 1400px box, cutting card 3 at x1660 - 259px short.
   - Removing that clip entirely lets the 3420px slide track grow the document
     to scrollWidth 2288 at a 1920 viewport. `body { overflow-x: clip }` stops
     the scrollbar but NOT the reported overflow, which is a real defect.

   The clip has to live on an element that is itself viewport-width. `.pgs-s8`
   is the outer section box (x0 w1920); Elementor's own overflow control emits
   onto the boxed `.e-con-inner` instead, so it cannot express this. `clip`
   rather than `hidden` so it establishes no scroll container and does not
   contribute to any ancestor's scrollable area, and so it can be x-only while
   overflow-y stays visible.
   --------------------------------------------------------------------------- */
.pgs-s8 {
	overflow-x: clip;
}

/* s8 section gutter ladder — mirrors container 9e0c090's responsive padding
   (40 / 32 / 24 / 20 / 16) so the progress bar can derive the boxed content width. */
.pgs-s8 { --s8-gutter: 40px; }
@media (max-width: 1199px) { .pgs-s8 { --s8-gutter: 32px; } }
@media (max-width: 1023px) { .pgs-s8 { --s8-gutter: 24px; } }
@media (max-width:  767px) { .pgs-s8 { --s8-gutter: 20px; } }
@media (max-width:  575px) { .pgs-s8 { --s8-gutter: 16px; } }

/* ===========================================================================
   s9 "Testimonial" — Nested Carousel next-arrow placement
   ---------------------------------------------------------------------------
   The design puts BOTH arrows in the left column (x259 and x~323 at 1920),
   ~459px to the left of the slide area they control. The nested-carousel's
   per-arrow position controls handle the PREV arrow natively: orientation
   `start` + a negative offset emits
     --e-n-carousel-arrow-prev-left-position: -459px
   and lands it exactly on x259.

   The NEXT arrow cannot be expressed the same way. Whatever its
   `*_horizontal_orientation` is set to, Elementor keeps emitting it
   right-anchored —
     --e-n-carousel-arrow-next-right-align: 100%
     --e-n-carousel-arrow-next-translate-x: 100%
     --e-n-carousel-arrow-next-right-position: -395px
   — so a negative offset pushes it further RIGHT (measured x1113 instead of
   the wanted x324). Re-anchoring it to the left edge is therefore a CSS-only
   fix: `right:auto` + a left offset measured from the widget's own left edge
   (718 - 394 = 324), and `transform:none` to drop the 100% translate.
   =========================================================================== */
/* 0,3,0 (three classes) deliberately: widget-nested-carousel.min.css is a
   conditionally-enqueued plugin stylesheet whose own rule is 0,2,0, and it can
   load AFTER custom.css. At a matching 0,2,0 only `left` won here while
   `right:auto` lost, leaving both offsets resolved and stretching the button to
   789px wide. The extra class removes the ambiguity. */
.pgs-s9-carousel .elementor-swiper-button.elementor-swiper-button-next {
	/* `left` needs no !important - Elementor never sets it on this element, so it
	   applies cleanly. `right` and `transform` DO need it: Elementor emits them
	   from --e-n-carousel-arrow-next-right-align / -right-position /
	   -translate-x in a rule that outranks even a 0,3,0 selector here, and with
	   `right` still resolved alongside `left` the button stretched to 789px and
	   the 100% translateX pushed it back off to x1113. Measured after the fix:
	   x324 y466 54x54. This is a below-the-widget-boundary override of a
	   third-party rule, the same justification as the s5 lazyload block. */
	/* -396.5, not -394: QA measured the inter-button gap at 13px against the
	   design's 10.5px, i.e. the next button sat 2.5px too far right. */
	left: -396.5px;
	right: auto !important;
	transform: none !important;
}

/* ---------------------------------------------------------------------------
   s9 — arrows must stay reachable once the two-column layout collapses
   ---------------------------------------------------------------------------
   The desktop arrows are pulled ~459px left of the slides so they land in the
   left column. Below 1024px the inner row stacks to a single column and that
   left column no longer exists, so the same offsets put both arrows OFF-SCREEN
   (measured x-217 at 1024 and x-443 at 390 — the carousel became unusable at
   every width below desktop while still reporting no page overflow, because
   the section clips them).

   Re-anchored under the quote instead. Elementor composes each arrow's position
   from its own custom properties (top: calc(top-align + top-position)), so
   setting the PROPERTIES is cleaner than fighting the declarations: top-align
   100% + 16px puts them just below the widget, and left 0 / 65px lines them up
   with the content edge. `padding-bottom` reserves the room they now occupy.
   The next arrow's `left` still needs the 0,3,0 selector for the same
   load-order reason as its desktop rule above.
   --------------------------------------------------------------------------- */
@media (max-width: 1024px) {
	/* 0,3,0 required: Elementor declares these same properties on
	   `.elementor-60 .elementor-element.elementor-element-s9car01` (0,3,0), so a
	   plain `.pgs-s9-carousel` (0,1,0) override loses and the prev arrow stayed
	   at x-443. */
	.elementor .elementor-element.pgs-s9-carousel {
		--e-n-carousel-arrow-prev-left-position: 0px;
		--e-n-carousel-arrow-prev-top-align: 100%;
		--e-n-carousel-arrow-prev-top-position: 16px;
		--e-n-carousel-arrow-next-top-align: 100%;
		--e-n-carousel-arrow-next-top-position: 16px;
		padding-bottom: 86px;
	}

	.pgs-s9-carousel .elementor-swiper-button.elementor-swiper-button-next {
		left: 65px;
	}
}

/* ---------------------------------------------------------------------------
   s9 decor — mix-blend-mode: luminosity
   ---------------------------------------------------------------------------
   `section-specs.json` records `mixBlendMode: "luminosity"` on BOTH decor nodes
   (`image 13` and `image 14`, each with opacity 0.1). Without it the wash keeps
   the photo's colour and lands a 2.0-3.7 level warm/brown cast on near-white,
   where the design render is exactly neutral (per-channel darkness spread 0.00
   in 4 of 5 sampled regions, i.e. greyscale).

   It has to live here: Elementor's Image widget exposes `opacity` and the
   `css_filters_*` group but NO blend-mode control, so this property cannot be
   authored from the UI at all.

   Note the pixel gate is structurally blind to this - a 2-4 level difference is
   under pixelmatch's ~25-level threshold and contributes 0 differing pixels. It
   was only found by direct per-channel colour measurement.
   --------------------------------------------------------------------------- */
/* The blend MUST sit on the widget wrapper, not the <img>. The wrapper is
   `position:absolute` with `_z_index: 0`, which makes it a stacking context, so
   a blend on the img inside it composites against the wrapper's own TRANSPARENT
   backdrop and does nothing to the hue — measured: still a blue-heavy cast,
   per-channel spread 2.4-5.1 where the design is 0.00. On the wrapper it
   composites against the section's white background, which is what turns the
   wash neutral. */
.pgs-s9-decor {
	mix-blend-mode: luminosity;
}

/* ===========================================================================
   s11 "Free quote"
   ===========================================================================*/

/* Texture wash over the section gradient.
   Design: layer "1 1", x-34 y-79 w1989 h1085, opacity 0.2, blend luminosity.
   Same trap as .pgs-s9-decor above: the image widget's own opacity control
   lands on the <img>, but the wrapper is position:absolute with a z-index,
   which makes it a stacking context - so a blend declared on the <img>
   composites against the wrapper's transparent backdrop and does nothing.
   The blend has to sit on the WRAPPER, whose backdrop is the section gradient. */
.pgs-s11-texture {
	mix-blend-mode: luminosity;
	pointer-events: none;
}

/* The "What can we help with?" control is the same 57px as the single-line
   inputs in both the Figma comp and the supplied reference image, so the
   textarea must not take Elementor's 4-row default height. The form's
   field line-height is 45px (that is what makes the text inputs 57 tall) and
   would look wrong for wrapped textarea copy, so it is reset here too. */
.pgs-quote-form textarea.elementor-field-textual {
	height: 57px;
	min-height: 80px;
	padding-top: 16px;
	padding-bottom: 16px;
	line-height: 1.5;
	/* Design shows no resize grabber on this control, and the field is sized as
	   a single-line input, so the handle would be the only thing in the section
	   that is not in the comp. */
	resize: none;
}

/* The texture layer is 1989px wide against a 1920 section (it bleeds -34px
   left and -79px top, and the section clips it). The image widget's own width
   control cannot express that: the theme's `img { max-width: 100% }` clamps the
   <img> to the 1920 wrapper, which measured as w=1920 instead of 1989. Stated
   as a percentage so the bleed stays proportional at every width rather than
   becoming a fixed 69px overhang on a phone. */
.pgs-s11-texture img {
	max-width: none;
	width: 103.6%;
}

/* Design splits the phone/ZIP row 289/273 inside a 595 row (gap 33), not 50/50.
   Elementor's field-width control only offers preset fractions, none of which
   is 48.57%, so the two field groups are sized here instead. Percentages are of
   the field-groups wrapper (627px), which includes the 16.5px padding each
   group carries to make up the 33px column gap:
     phone group 289 + 33 = 322  ->  51.276%
     zip   group 273 + 33 = 306  ->  48.724%
   Applied from 768px up, i.e. wherever the two fields are actually side by
   side - below that Elementor's own width_mobile_extra:100 stacks them. */
@media (min-width: 768px) {
	.pgs-quote-form .elementor-field-group-q-phone {
		width: 51.276%;
	}

	.pgs-quote-form .elementor-field-group-q-zip {
		width: 48.724%;
	}
}

/* Badge pills hug their label at every width. Measured at 390px the three pills
   each reported w=390 - full-bleed - because Elementor's container width
   resolves to 100% at the mobile breakpoints regardless of the widget's own
   `width: auto`. The .elementor .e-con ladder (0,3,0) is what it takes to win
   against Elementor's own container width rule. */
.elementor .e-con.pgs-s11-badge {
	width: auto;
	max-width: 100%;
	flex: 0 0 auto;
}

/* Fine print under the submit button. The inline link carries only
   text-decoration, so its colour comes from the theme's link colour; design
   sampled #174F75 on the underlined "privacy policy", which is the kit's
   Blue Deep. */
.pgs-s11-fineprint a {
	color: #12557F;
}

/* ===========================================================================
 * Seamless Gutter Installation (page 185) — section 1, hero
 *
 * Two rules only, both for things Elementor's controls cannot express.
 * ======================================================================== */

/* The hero foreground photo is a masked, cropped layer in Figma: an 818x686 box
   showing the source at object-position 11% 0%. Elementor's image widget has a
   width control but no height + object-fit + object-position trio, so the crop
   has to be declared here.

   object-position is 100% (right-aligned), NOT the `objectPosition: 11% 0%` the
   Figma export reports - that value is wrong. Solved from the baked-in iStock
   watermark: it sits at source x1255-1560,y980-1077 in the 2048x1536 original and
   at box x464-599,y438-480 in the design. Those two boxes only agree at cover
   scale 0.44661 with the source's RIGHT edge flush to the box's right edge, which
   also matches the layer's own `constraintsHorizontal: max`. At 11% the watermark
   landed 96px right of the design. Height is carried down the ladder proportionally so
   the crop keeps its shape as the % width shrinks; the widget itself is hidden
   at tablet and below (set on the widget, not here). */
.pgs-sgi-hero-photo img {
	height: 686px;
	object-fit: cover;
	object-position: 100% 0%;
}

@media (max-width: 1439px) {
	.pgs-sgi-hero-photo img {
		height: 560px;
	}
}

@media (max-width: 1199px) {
	.pgs-sgi-hero-photo img {
		height: 470px;
	}
}

/* Design draws both hero CTA arrows at 16x16. Elementor renders an SVG button
   icon as a <span> sized from the font, which lands at 18x18 — and 18 is what
   the live header CTA measures, so the existing chrome is 2px off the design
   there too. That header is out of scope for this page, so this rule is scoped
   to the hero's own class rather than to the header's `pgs-btn-icon16` (which is
   declared on the header button but styled nowhere, and is therefore inert).
   Styling that class instead would silently restyle the approved header. */
.pgs-sgi-hero-btn .elementor-button-icon,
.pgs-sgi-hero-btn .elementor-button-icon svg {
	width: 16px;
	height: 16px;
}

/* ===========================================================================
 * Seamless Gutter Installation (page 185) — section 2, "Seamless by Design"
 * ======================================================================== */

/**
 * The up-pointing notch on this section's top edge.
 *
 * In Figma it is `rectangle_7`, a 28x28 diamond whose bounds are x946 y607, so its
 * centre sits at (960, 621) — exactly on the panel's top edge, leaving only the top
 * half (28 wide x 14 tall) visible above the white panel. A 28x28 diamond is a
 * square of side 28/sqrt(2) = 19.799 rotated 45deg, which is what this draws.
 *
 * Uses ::after, not ::before: Elementor already owns ::before on a container for its
 * background-overlay control, so a ::before here would be overwritten the moment
 * anyone sets an overlay in the editor.
 */
.pgs-sgi-s2 {
	position: relative;
}

.pgs-sgi-s2::after {
	content: "";
	position: absolute;
	top: 0;
	left: 50%;
	width: 19.799px;
	height: 19.799px;
	margin-top: -9.9px;
	margin-left: -9.9px;
	background: #ffffff;
	transform: rotate(45deg);
	pointer-events: none;
}

/* Card icons are 62x62 in the design. The source assets are 512x512 rasters that
   Figma had wrapped inside an SVG as an embedded base64 pattern fill (126KB of
   SVG for a bitmap), so they were extracted and re-encoded as 124px webp (2x)
   rather than shipped as SVG. Elementor's image widget sizes by width only, so the
   square box is pinned here. */
.pgs-sgi-s2-icon img {
	width: 62px;
	height: 62px;
	object-fit: contain;
}

/* ===========================================================================
 * Seamless Gutter Installation (page 185) — section 3, "When Your Gutters Stop
 * Working" (photo + benefit list)
 * ======================================================================== */

/**
 * Installer photo.
 *
 * The design box is 749x682 and the export claims `objectFit: cover` with
 * `objectPosition: 13% 16%`. Both are wrong: cover on a 2048x1733 source gives
 * scale 0.3935 and ZERO vertical overflow, which would make any y% meaningless —
 * yet a y% is specified. Figma actually used a custom fill scale, and the export
 * flattened it to "cover", losing the zoom. Rendered side by side the design is
 * visibly more zoomed: the drainpipe at the left is cropped out entirely.
 *
 * Solved by gradient-magnitude correlation over the photo box, searching scale
 * 0.39-0.62 and both offsets: best fit scale 0.470 (a 1.194x zoom past cover) at
 * offset (128, 132), correlation 0.665. Expressed below as percentages of the
 * 749x682 wrapper so the crop scales with the column instead of breaking at the
 * first breakpoint:
 *     width  963/749 = 128.571%     left -128/749 = -17.089%
 *     height 815/682 = 119.501%     top  -132/682 = -19.354%
 *
 * `top` is a percentage of the wrapper's HEIGHT only because the wrapper has a
 * definite height and the image is absolutely positioned; a percentage MARGIN
 * would have resolved against the wrapper's WIDTH and been wrong by 13px.
 */
.pgs-sgi-s3-photo {
	overflow: hidden;
	height: 682px;
	border-radius: 0 0 0 10px;
}

/* `.elementor` prefix (0,2,1) is required to beat Elementor's own generated
   `.elementor-element-<id> img { width: 100% }` (0,1,1), which loads after the
   theme stylesheet and otherwise clamps the deliberately oversized crop back to
   the wrapper width. Same specificity fight as `.pgs-s11-badge` on the homepage. */
.elementor .pgs-sgi-s3-photo img {
	position: absolute;
	left: -17.089%;
	top: -19.354%;
	width: 128.571%;
	height: 119.501%;
	max-width: none;
	object-fit: fill;
}

@media (max-width: 1439px) {
	.pgs-sgi-s3-photo {
		height: 600px;
	}
}

@media (max-width: 1199px) {
	.pgs-sgi-s3-photo {
		height: 520px;
	}
}

@media (max-width: 991px) {
	.pgs-sgi-s3-photo {
		height: 460px;
	}
}

@media (max-width: 575px) {
	.pgs-sgi-s3-photo {
		height: 340px;
	}
}

/* "10 YEARS" laurel. Figma shipped this as one strip of THREE gold badges
   (image-16.png, 888x329) and used only the middle one, filled white. The middle
   badge's opaque bounds were found by alpha column/row runs (x305-529, y30-244 —
   the two row bands below it are the caption, which the design does not show) and
   re-emitted as a white silhouette from its alpha channel. */
/* 167x161, not the 173x167 the export declares: the wreath's own white bbox above
   the roofline measures 166px wide in the design and came out 172px at the declared
   size, so the artwork sits ~3.5% smaller inside its box than the box implies. The
   offsets below keep the measured centre (x668.5) fixed while shrinking it. */
.pgs-sgi-s3-badge img {
	width: 167px;
	height: 161px;
	object-fit: contain;
}

@media (max-width: 991px) {
	.pgs-sgi-s3-badge img {
		width: 130px;
		height: 126px;
	}
}

/* Benefit list hairlines. Measured at #F1F1F1, 446px wide, sitting exactly halfway
   between consecutive item centres (row pitch 65px) — and absent after the last
   item, which is why this is a border-bottom with a :last-child reset rather than a
   border-top. */
.pgs-sgi-s3-item {
	border-bottom: 1px solid #f1f1f1;
}

.pgs-sgi-s3-item:last-child {
	border-bottom: 0;
}

/* ===========================================================================
 * Seamless Gutter Installation (page 185) — section 4, "A Gutter Company Sees a
 * Gutter" (sky background + roof photo + 4-point feature list)
 * ======================================================================== */

/**
 * Roof photo. The design MIRRORS this asset — its baked-in iStock watermark reads
 * backwards in the render — so the uploaded webp is pre-flipped (sharp .flop())
 * rather than carrying a CSS transform, which keeps the crop maths direct.
 *
 * Crop solved from the orange roof's bounding box in the flipped source vs the
 * design render: scale 0.6947 on x and 0.6945 on y — agreement to 4 decimal places,
 * so this one genuinely IS `cover` (0.6967) with the source's RIGHT edge flush
 * right, i.e. object-position 100% 0%. Unlike s3, no zoom was applied here.
 */
.pgs-sgi-s4-photo img {
	height: 951px;
	object-fit: cover;
	object-position: 100% 0%;
}

@media (max-width: 1439px) {
	.pgs-sgi-s4-photo img {
		height: 860px;
	}
}

@media (max-width: 1199px) {
	.pgs-sgi-s4-photo img {
		height: 760px;
	}
}

/* Feature-list rules. Measured #E2E8EE (the kit's `border` global — the same value
   the badge SVGs use for their own ring), 1px, spanning exactly the 688px content
   column. There are FIVE of them for four rows — one above each row plus one below
   the last — which is why this is border-top on the rows plus border-bottom on the
   list, rather than a border-bottom with a :last-child reset as in s3. */
.pgs-sgi-s4-row {
	border-top: 1px solid #e2e8ee;
}

.pgs-sgi-s4-list {
	border-bottom: 1px solid #e2e8ee;
}

/* 45x45 badge circles. The SVGs are self-contained (white fill, #E2E8EE ring,
   #186DA6 glyph), so they are used as images rather than recoloured icons.
 *
 * `flex: 0 0 45px` on the WRAPPER and `max-width: none` on the img are both load
 * bearing: as a flex item the widget wrapper shrank to 41.2px, and Hello Elementor's
 * `img { max-width: 100% }` then clamped the image to the wrapper rather than the
 * declared 45px. That squeezed the circle to 41.2x45 AND shifted every row title
 * 3.8px left (260 + 41.2 + 17 instead of 260 + 45 + 17 = 322). */
.pgs-sgi-s4-icon {
	flex: 0 0 45px;
}

.pgs-sgi-s4-icon img {
	width: 45px;
	height: 45px;
	max-width: none;
}

/* ===========================================================================
 * Seamless Gutter Installation (page 185) — section 5, "Simple Process"
 * (4-step how-it-works)
 * ======================================================================== */

/**
 * Step badge: a 90px white disc with a very soft shadow. The disc is white on a
 * white page, so it is invisible to a colour scan — it was measured from its own
 * shadow: a horizontal luminance profile through the icon centre dips to 248 at
 * x=360 and x=450 and sits at pure 255 between, giving a 90px diameter centred at
 * (405, 349), which agrees with the icon's own centre (406, 349) and with the
 * 332px card's centre (406). A 7-level dip is about as soft as a shadow gets;
 * the value below is the site's existing card-shadow token at reduced alpha.
 */
.pgs-sgi-s5-badge {
	box-shadow: 0 4px 20px rgba( 3, 31, 45, 0.08 );
}

/**
 * Lock the disc to 90px at every width.
 *
 * The widget's own `width: 90px` is not enough: at the mobile breakpoints Elementor
 * resolves a container's width to 100% regardless, so the disc stretched to the
 * card's content width (479px at 575, 294px at 390) and the circle became a pill.
 * The `.elementor .e-con` ladder (0,3,0) is what it takes to win — the same fight
 * session.md records for `.pgs-s11-badge` on the homepage.
 */
.elementor .e-con.pgs-sgi-s5-badge {
	width: 90px;
	max-width: 90px;
	flex: 0 0 auto;
}

.pgs-sgi-s5-badge img {
	max-width: none;
}

/* The numbered chip sits on the disc's top-left and overhangs its edge by 1px:
   the design has the chip at x359-382 while the disc starts at x360. */
.pgs-sgi-s5-num {
	position: absolute;
	left: -1px;
	top: 3px;
}

/* Dashed connector arcs between steps. Vector assets straight from Figma
   (stroke #186DA6, stroke-dasharray 4 4), so no colour work is needed — but they
   must not shrink, and Hello Elementor's img{max-width:100%} would otherwise clamp
   them inside their absolutely-positioned wrapper. */
.pgs-sgi-s5-arc img {
	width: 174px;
	height: 29px;
	max-width: none;
}

/* Arcs are positioned as percentages of the 1400px steps row (257/1400, 613/1400,
   969/1400) so they track the cards as the row narrows, instead of overflowing it
   with fixed px offsets — at 1200 that overflow was adding real horizontal scroll.
   They are dropped once the row is too tight for the wave to read. */
@media (max-width: 991px) {
	.pgs-sgi-s5-arc {
		display: none;
	}
}

/* ===========================================================================
 * Seamless Gutter Installation (page 185) — section 6, "Clean Lines. Better
 * Water Management" (BEAF before/after slider + CTA row)
 *
 * Mirrors the homepage's s6 treatment (see the S6 block above) because BEAF
 * renders its own markup below the Elementor widget boundary. TWO of the three
 * homepage re-impositions apply here; the third does not:
 *   1. HEIGHT — BEAF sizes .twentytwenty-container to the source images' aspect
 *      ratio, which overrides the frame's designed 540px. Taking the widget out
 *      of flow hands height back to the frame and lets overflow:hidden + the
 *      20px radius clip the slider as designed.
 *   2. FIT — BEAF emits object-fit:fill, which would stretch a 431x420 source
 *      across a 1400px frame. The design crops instead of distorting.
 *   3. NOT desaturation: unlike the homepage, this design's "before" pane is
 *      full colour, so no saturate(0) is applied.
 *
 * Unlike the homepage, this section uses BEAF's OWN labels (the design's Before
 * and After pills), so they are styled here rather than suppressed.
 * Everything is scoped to .pgs-sgi-s6-frame so the homepage slider is untouched.
 * ======================================================================== */

.pgs-sgi-s6-frame > .pgs-sgi-s6-beaf {
	position: absolute;
	inset: 0;
	z-index: 1;
}

.pgs-sgi-s6-frame > .pgs-sgi-s6-beaf > .elementor-widget-container,
.pgs-sgi-s6-frame .twentytwenty-wrapper,
.pgs-sgi-s6-frame .twentytwenty-container {
	height: 100%;
}

.pgs-sgi-s6-frame .twentytwenty-container > img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: 50% 40%;
}

/* Slide title / description / read-more are unused — the section supplies its own
   eyebrow, h2 and lede as Elementor widgets. */
.pgs-sgi-s6-frame .bafg-slider-info-wraper {
	display: none;
}

/* Before / After label pills. Measured from the design: 96x47 and 83x47, flush
   with the slider's left and right edges, white on the photo, label text
   18px/600 #27A800 with ~19-20px of horizontal padding. */
.pgs-sgi-s6-frame .twentytwenty-before-label::before,
.pgs-sgi-s6-frame .twentytwenty-after-label::before {
	font-family: "Instrument Sans", sans-serif;
	font-size: 18px;
	font-weight: 600;
	line-height: 18px;
	letter-spacing: 0;
	color: #27a800;
	background: #ffffff;
	padding: 14px 19px;
	border-radius: 0 0 12px 0;
	top: 0;
	bottom: auto;
	margin: 0;
}

.pgs-sgi-s6-frame .twentytwenty-after-label::before {
	border-radius: 0 0 0 12px;
}

/* The drag handle is a 63px white disc in the design; BEAF's default is smaller
   and carries its own arrow glyphs, which the design keeps. */
/*.pgs-sgi-s6-frame .twentytwenty-handle {
	box-sizing: border-box;
	width: 63px;
	height: 63px;
	margin-left: -31.5px;
	margin-top: -31.5px;
	background: #ffffff;
	border: 0;
	box-shadow: 0 2px 12px rgba( 3, 31, 45, 0.18 );
}*/

/* CTA divider — a 1px rule between the heading and the two buttons. Solved from a
   2-column antialiased sample (242 / 211 over white), which integrates to a single
   fully-covered pixel of ~#C6C7C9. */
.pgs-sgi-s6-rule {
	width: 1px;
	height: 42px;
	background: #c6c7c9;
	flex: 0 0 1px;
}

/* The two CTA buttons carry different icon sizes in the design (16x16 on the
   outline pill, 18x20 on the solid one); Elementor sizes an SVG button icon from
   the font, so each is pinned here. */
.pgs-sgi-s6-btn-outline .elementor-button-icon,
.pgs-sgi-s6-btn-outline .elementor-button-icon svg {
	width: 16px;
	height: 16px;
}

.pgs-sgi-s6-btn-solid .elementor-button-icon,
.pgs-sgi-s6-btn-solid .elementor-button-icon svg {
	width: 18px;
	height: 18px;
}

/* The CTA button pair must size to its content. An Elementor container defaults to
   full width, so as a flex item it claimed the whole 1400px row and pushed both
   buttons onto a second line — which inflated the section from 1110 to 1200px.
   Needs the `.elementor .e-con` ladder (0,3,0) to beat Elementor's own width rule,
   the same fight as the s5 badge and the homepage's s11 badge. */
.elementor .e-con.pgs-sgi-s6-btns {
	width: auto;
	max-width: 100%;
	flex: 0 0 auto;
}

/* ===========================================================================
 * Seamless Gutter Installation (page 185) — section 7, "More Than Just Gutters"
 * (navy band, ACF [pgs_services] carousel)
 *
 * The section reuses the homepage's `.s5-card` styling wholesale — the design is
 * the same carousel, down to the 30.82px radius, the flush outer corners, the
 * 30/39.77 title and the #A8B7C2 body — so nothing about the card is restyled here.
 *
 * ONE thing has to be restated: the card HEIGHT. `.s5-card { height: 628px }` and
 * Swiper's own `.swiper-slide { height: 100% }` are both specificity (0,1,0), so the
 * winner is decided purely by stylesheet order. On the homepage custom.css happens to
 * load last and wins; in this page's context Swiper's rule won instead, and 100% of a
 * height-less wrapper resolved to 0 — every card collapsed and its absolutely
 * positioned body (bottom:0) escaped ~200px UPWARD out of the card.
 *
 * Restating it at (0,2,0) under the widget's own class makes it order-independent,
 * so this cannot silently regress if enqueue order ever changes again. The ladder
 * mirrors the homepage's exactly (628 / 560 / 500 / 440 / 400).
 * ======================================================================== */

.pgs-sgi-s7-services .s5-card {
	height: 628px;
}

@media ( max-width: 1199px ) {
	.pgs-sgi-s7-services .s5-card {
		height: 560px;
	}
}

@media ( max-width: 991px ) {
	.pgs-sgi-s7-services .s5-card {
		height: 500px;
	}
}

@media ( max-width: 767px ) {
	.pgs-sgi-s7-services .s5-card {
		height: 440px;
	}
}

@media ( max-width: 575px ) {
	.pgs-sgi-s7-services .s5-card {
		height: 400px;
	}
}

/* The view-all button's arrow: 16x16 in the design. Elementor sizes an SVG button
   icon from the font and lands at 18, which made the pill 50px tall against the
   design's 47 and pushed the whole section 3px over. */
.pgs-sgi-s7-btn .elementor-button-icon,
.pgs-sgi-s7-btn .elementor-button-icon svg {
	width: 16px;
	height: 16px;
}

/* ===========================================================================
 * Seamless Gutter Installation (page 185) — section 8, testimonial
 * ======================================================================== */

/**
 * The two ghost photographs behind the testimonial.
 *
 * The export reports `opacity: 0.7` on both and nothing else, which is wrong twice
 * over: at 0.7 they would be plainly visible, and it misses that they are also
 * DESATURATED. Solved by sampling six points and comparing the render against the
 * source: the render is neutral where the source is warm, and
 *     render = 255 - 0.065 * (255 - luminance(source))
 * predicts 248 / 246 / 251 / 253 / 245 / 251 against measured 248 / 246 / 251 /
 * 253 / 245 / 251. So: grayscale, at 6.5% opacity. Both sources are placed at
 * exactly 50% scale (1564/2 = 782, 1180/2 = 590) with no crop.
 *
 * The webp files are pre-desaturated, so only the opacity is applied here.
 */
.pgs-sgi-s8-ghost img {
	opacity: 0.065;
}

/* Vertical rule between the two columns. Measured at x=672 spanning y119-519
   (401px tall). The 1px line antialiases across two columns (233 / 247 over
   white), which integrates to a single covered pixel of ~#E1E1E1 — the kit's
   `border` #E2E8EE. */
.pgs-sgi-s8-rule {
	width: 1px;
	height: 401px;
	background: #e2e8ee;
	flex: 0 0 1px;
}

/* The prev/next arrows ship as ONE 170x53 SVG containing both discs, so they are
   placed as a single image rather than two icon widgets. */
.pgs-sgi-s8-arrows img {
	width: 170px;
	height: 53px;
	max-width: none;
}

/* The 78x78 avatar tile. Elementor containers default to full width and then shrink
   as flex items, so this one came out 70.5px — which also dragged the name block
   7.5px left (731 + 70.5 + 19 instead of 731 + 78 + 19 = 828). The `.elementor .e-con`
   ladder (0,3,0) is what beats Elementor's own width rule. */
.elementor .e-con.pgs-sgi-s8-avatar {
	width: 78px;
	max-width: 78px;
	flex: 0 0 78px;
}

/* ===========================================================================
 * Seamless Gutter Installation (page 185) — section 9, service areas
 *
 * The city cards come from [pgs_service_areas] and reuse the homepage's existing
 * `.s10-cards` block wholesale: grid of two 343px columns with a 20px gap, 14px
 * radius, 44x44 icon, 20/26/600 navy name. The design's card box is 88px against
 * the homepage's 84, but the ROW PITCH is identical (84+20 = 88+16 = 104), so
 * subsequent rows land in the same place and nothing is restated.
 * ======================================================================== */

/* The map is a plain scale of its source (1290x1381 -> 574x613, aspect 0.934 vs
   0.936) so no crop is needed — only the 20px radius and a height that tracks the
   column as it narrows. */
/* Height is pinned to the design's 613 rather than left to `auto`: the source is
   1290x1381 (aspect 0.9341) but the design box is 574x613 (0.9364), so `auto`
   renders 614.5 and drags the whole section 1.5px taller. */
.pgs-sgi-s9-map img {
	width: 100%;
	height: 613px;
	object-fit: cover;
	border-radius: 20px;
	display: block;
}

@media ( max-width: 991px ) {
	.pgs-sgi-s9-map img {
		height: auto;
	}
}

/* The shared `.s10-cards` block carries `margin: 0 0 24px`. This design's cards are
   88px tall where the homepage's are 84, so the grid ends 5.5px higher here and the
   note + button below it inherited that. Page-scoped bottom margin restores the
   design's 22px gap from the card grid to the note. */
.pgs-sgi-s9-areas .s10-cards {
	margin-bottom: 27.5px;
}

/* Design draws this arrow at 18x20; Elementor sizes an SVG button icon from the font. */
.pgs-sgi-s9-btn .elementor-button-icon,
.pgs-sgi-s9-btn .elementor-button-icon svg {
	width: 18px;
	height: 18px;
}

/* ============================================================================
   SECTION 10 — FAQ accordion (nested-accordion)
   ========================================================================= */

/* The nested-accordion's own `title_typography` control emits its rule with the
   widget/element part wrapped in :where(), which contributes ZERO specificity —
   the whole selector collapses to `.e-n-accordion-item-title-text`, i.e. (0,1,0).
   The kit's `.elementor-kit-12 h3` rule is (0,1,1) and therefore wins, forcing
   line-height 1.18 (23.6px) and font-weight 700 over the design's 24px / 600.
   Measured on the live page: font-size is the ONLY field that survives, because
   that one field routes through --n-accordion-title-font-size on the wrapper,
   which the widget consumes at (0,2,0).
   No Elementor control can outrank the kit here and the competing rule is the
   site-wide h3 Theme Style (so it must not be unset). Scoped to (0,2,0) instead. */
.pgs-sgi-s10-faq .e-n-accordion-item-title-text {
  line-height: 24px;
  font-weight: 600;
}
/* Match Elementor's OWN breakpoints, read off the generated post-185.css rather
   than assumed. This kit has Pro's extra breakpoints active, so the ladder is:
     desktop >=1440 | laptop <=1439 | tablet_extra <=1199
     tablet <=991   | mobile_extra <=767 | mobile <=575
   The practical consequence: a `_tablet` setting applies at <=991 and a
   `_mobile` setting applies at <=575 (NOT <=767). These queries therefore pair
   with the widget's own font sizes — 20/24 above 991, 18/22 to 576, 17/21 to 575.
   A 767px query here would have put lh 21 against font-size 18 across 576-767. */
@media (max-width: 991px) {
  .pgs-sgi-s10-faq .e-n-accordion-item-title-text { line-height: 22px; }
}
@media (max-width: 575px) {
  .pgs-sgi-s10-faq .e-n-accordion-item-title-text { line-height: 21px; }
}

/* The design's OPEN item is one continuous 16px-radius blue panel (measured: the
   band at y352-361 is solid #186DA6, no seam). Elementor builds an open item from
   two boxes — the title header and the content container — and `accordion_border_radius`
   is a single wrapper-level var applied to the header in EVERY state, so an open
   header keeps its rounded bottom corners and the section gradient shows through two
   16px wedges where the square-topped content box begins. There is no per-state
   radius control on this widget (the normal/hover/active tabs carry background and
   border only), so square the open header's bottom corners here; the content box's
   own `content_border_radius` (0 0 16 16) completes the rectangle. */
.pgs-sgi-s10-faq .e-n-accordion-item[open] > .e-n-accordion-item-title {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

/* ============================================================================
   SECTION 11 — Free quote (photo background + Elementor Pro form)
   ========================================================================= */

/* Trust pills must hug their label. An Elementor container is width:100% by
   default and its `width` control has no "auto", so three pills each filled the
   row and stacked (measured 595 wide each) instead of flowing 2 + 1 as designed.
   Same remedy already used for the s5 badge, s6 buttons and s8 avatar. */
.elementor .e-con.pgs-sgi-s11-pill {
  width: auto;
  max-width: 100%;
  flex: 0 0 auto;
}

/* The design's inputs are 57px tall: padding 16.2/18 + 1.5px border + a 21.6px
   line box. The Pro form widget exposes typography, colour, border width/colour
   and radius for its fields but NO padding control (only `button_text_padding`
   exists, for the submit button), so Elementor's own 8px/16px default padding
   held and every field measured 40px — which then pushed the whole form's
   vertical rhythm up by ~18px per row. Set the design's padding here.
   The textarea needs its own declaration: Elementor gives it a different
   default (5px/14px) from the textual inputs. */
.pgs-sgi-s11-form .elementor-field-textual {
  box-sizing: border-box;
  height: 57px;
  padding: 16.2px 18px;
  border-width: 1.5px;
}
/* Height is stated outright rather than left to padding + line-height + border,
   because the field box is what the form's whole vertical rhythm is built from:
   the design's rows sit at y220/329/437/546 (a 108.66px pitch = a 93.16px group
   plus the 15.5px row gap), and deriving 57 from three separate values left it at
   56.4 and the drift reached -4.8px by the last label. */
.pgs-sgi-s11-form textarea.elementor-field-textual {
  height: 57px;
  padding: 16.2px 18px;
  resize: vertical;
}
/* Placeholder colour — measured #8B98A4 off the design's native @2x render
   (identical to the fine-print colour). The form widget has no placeholder
   control; without this the browser default grey applies. */
.pgs-sgi-s11-form .elementor-field-textual::placeholder {
  color: #8B98A4;
  opacity: 1;
}

/* The house cutout is NOT cropped — it renders at its natural aspect (828x552),
   absolutely positioned bottom-left by the widget's own offset controls, and the
   section's own overflow:hidden + 50px radius clip it.
   The export says `image 15` is 828x495 with objectFit cover / objectPosition
   "0% 10%", and BOTH of those are wrong — the element also carries hasMask:true,
   so those bounds describe Figma's mask frame, not the image placement. Solved by
   silhouette IoU instead: classify every design pixel in the lower-left quadrant
   as photo-vs-blue, then match the candidate's alpha mask over scale x offset.
   Result: IoU 0.9740 at exactly cover scale (0.4994), placed at y380 with its
   bottom flush to the section — i.e. uncropped and bottom-aligned. Cropping to
   495 with the declared 10% offset measured 77.6% differing in that region; every
   y offset from 0% to 100% stayed above 41%, which is what proved the box wrong
   rather than merely mispositioned. */
.pgs-sgi-s11-house img {
  display: block;
}
@media (max-width: 991px) {
  /* stacked layout: let the cutout scale with the section instead of overlapping
     the copy, and keep it decorative */
  .pgs-sgi-s11-house { display: none; }
}

/* ===========================================================================
 * Gutter Services in Jackson, MI (page 218) — section 1, hero
 * ======================================================================== */

/**
 * Design draws both hero CTA arrows at 16x16. Elementor sizes an SVG button icon
 * from the button's font-size, which lands at 18x18. Scoped to this page's own
 * hero button class — NOT to `pgs-btn-icon16`, which the approved header also
 * carries and which is deliberately inert (see the note above the SGI rule).
 */
.pgs-jm-btn .elementor-button-icon,
.pgs-jm-btn .elementor-button-icon svg {
	width: 16px;
	height: 16px;
}

/**
 * The eyebrow dot is a 12px SVG in an image widget. `img` is inline by default,
 * so the widget box inherits the body line-height (27.2px) instead of hugging the
 * 12px graphic — which made the eyebrow row 27.2 tall against the design's 26 and
 * pushed the dot 1.7px below its centre. Block display makes the box hug the image.
 */
.pgs-jm-eyebrow-dot img {
	display: block;
}

/**
 * Elementor's text-editor emits a `<p>` carrying the theme's bottom margin
 * (14.4px). The lede's spacing is owned by the widget's own _margin (17.31px top
 * and bottom, from the design's Paragraph:margin frame), so the paragraph must
 * contribute nothing or the two stack and the CTA row lands ~14px low.
 */
.pgs-jm-lede p {
	margin: 0;
}

/**
 * Responsive visibility for the two decorative hero layers.
 *
 * Elementor 4.2.4's `hide_tablet` / `hide_mobile` controls emit a BARE
 * `elementor-hidden` class rather than the breakpoint-suffixed
 * `elementor-hidden-tablet` / `-mobile` classes, so setting them hides the element
 * at EVERY width. Hiding is therefore done here, against the kit's real ladder.
 *
 * The house is a right-bleeding 900x627 cutout; below the tablet breakpoint the
 * hero switches to the portrait background (attachment 86) as the homepage does,
 * and the cutout would collide with the copy. The notch only reads against the
 * next section's rounded panel, which flattens on phones.
 */
@media (max-width: 991px) {
	.pgs-jm-hero-house { display: none; }
}

@media (max-width: 575px) {
	.pgs-jm-notch { display: none; }
}

/* ---------------------------------------------------------------------------
 * Jackson (218) — section 2, "Jackson Is Where We Started."
 * ------------------------------------------------------------------------ */

/**
 * The intro is two paragraphs separated by a FULL BLANK LINE in the design:
 * its 138px text box is four 34.27px line slots (1 line + blank + 2 lines).
 * Elementor's text-editor emits the theme's 14.4px paragraph margin instead,
 * which collapsed the block to 131.6px and pulled the card row — and every
 * section below it — 7px high. Restate the gap as one line of leading, and
 * drop the trailing margin so the block ends on its last baseline.
 */
.pgs-jm-s02-intro-copy p { margin: 0; }
.pgs-jm-s02-intro-copy p + p { margin-top: 34.27px; }

@media (max-width: 991px) {
	.pgs-jm-s02-intro-copy p + p { margin-top: 31px; }
}

@media (max-width: 575px) {
	.pgs-jm-s02-intro-copy p + p { margin-top: 28px; }
}

/* ---------------------------------------------------------------------------
 * Jackson (218) — section 3, "We Know the Homes We Work On."
 * ------------------------------------------------------------------------ */

/**
 * Same paragraph-rhythm correction as section 2, at this section's 24px leading:
 * the design's 96px intro box is four 24px slots (2 lines + blank + 1 line).
 * Elementor's themed 14.4px <p> margin made it 100.8 and pushed the divider list
 * ~5px low.
 */
.pgs-jm-s03-intro p { margin: 0; }
.pgs-jm-s03-intro p + p { margin-top: 24px; }

/**
 * Section 3's photo is TWO layers in the design: a 490x770 rounded frame holding
 * image-18, plus a background-removed cut-out of the installer that breaks out
 * ~130px to the LEFT of that frame and stands on the section's white.
 *
 * The export contains only the framed photo; no cut-out asset exists anywhere in
 * the asset pool, and none of the library's installer images is this photo. The
 * breakout is therefore reproduced from the designer's own @2x render — the strip
 * x1040-1170 lifted at full 2x resolution. It needs no alpha channel because the
 * backdrop it sits on is #FFFFFF in the design and #FFFFFF here, so an opaque
 * strip composites identically. Flagged for the developer: swap in a real cut-out
 * asset if one is ever produced.
 *
 * Below the tablet breakpoint the two columns stack and the breakout would collide
 * with the copy, so it is hidden and the framed photo goes full width.
 */
@media (max-width: 991px) {
	.pgs-jm-s03-breakout { display: none; }
}

/* ---------------------------------------------------------------------------
 * Jackson (218) — section 4, "What Can We Help With?"
 * (navy band, ACF [pgs_services] carousel)
 *
 * Same carousel component as the homepage's s5 and the service page's s7, reused
 * wholesale: same 462x628 card, 30.82px radius, flush outer corners, 30/39.77
 * title and #A8B7C2 body. The four cards are the SAME seeded pgs_service posts
 * (119-122) and their ACF copy already matches this design verbatim, so nothing
 * about the card is restyled and no content was duplicated.
 *
 * The one thing that must be restated is the card HEIGHT, for the reason recorded
 * above the service page's block: `.s5-card{height:628px}` and Swiper's own
 * `.swiper-slide{height:100%}` are both specificity (0,1,0), so the winner depends
 * purely on stylesheet order. Here Swiper won again — every card measured h=0 and
 * its absolutely positioned body escaped ~245px upward, out of the section. Stating
 * it at (0,2,0) under this section's own class makes it order-independent.
 * Ladder mirrors the homepage: 628 / 560 / 500 / 440 / 400.
 * ------------------------------------------------------------------------ */

.pgs-jm-s04 .s5-card { height: 628px; }

@media ( max-width: 1199px ) { .pgs-jm-s04 .s5-card { height: 560px; } }
@media ( max-width:  991px ) { .pgs-jm-s04 .s5-card { height: 500px; } }
@media ( max-width:  767px ) { .pgs-jm-s04 .s5-card { height: 440px; } }
@media ( max-width:  575px ) { .pgs-jm-s04 .s5-card { height: 400px; } }

/**
 * Per-card art-direction crop for this page's services carousel.
 *
 * The shared `.s5-card .s5-card__img` rule is `inset:0; width/height:100%;
 * object-fit:cover`, which yields a centred cover crop — what the homepage wants.
 * The Jackson design crops three of the four cards differently, and NOT by
 * object-position: it scales two of them well beyond cover, which object-position
 * cannot express. (Signature of the export lying about the fit: card 1 declares
 * `objectPosition: 1% 16%` on an axis where cover leaves ZERO vertical overflow.)
 *
 * Solved from pixels against the scrim-free top 225px of each card by searching
 * scale x offset. Residual mean|d|: card1 7.8, card3 5.1, card4 4.5, card2 59.5.
 * Expressed as an oversized absolutely-positioned box in PERCENTAGES so the crop
 * survives the card-height ladder rather than becoming fixed pixels.
 *
 * The four cards are shared pgs_service CPT posts, also used by the homepage, so
 * the images themselves are deliberately NOT modified here — only this section's
 * framing of them. See the note on card 2 below.
 */
.pgs-jm-s04 .s5-card__img[src*="pgs-s5_card1"],
.pgs-jm-s04 .s5-card__img[src*="pgs-s5_card2"],
.pgs-jm-s04 .s5-card__img[src*="pgs-s5_card3"],
.pgs-jm-s04 .s5-card__img[src*="pgs-s5_card4"] {
	inset: auto;
	right: auto;
	bottom: auto;
	/* The theme reset's `img { max-width: 100% }` silently clamps a deliberately
	   oversized crop box back to the card width — the widths below measured 462px
	   instead of 598/1057/485/495 until this was released. Same trap the homepage's
	   s11 texture layer hit. */
	max-width: none;
}

.pgs-jm-s04 .s5-card__img[src*="pgs-s5_card1"] {
	width: 129.44%; height: 127.07%; left: -1.30%; top: -20.86%;
}

/**
 * Card 2 is the same photograph as the homepage's, but the design zooms it 2.22x
 * — a crop of the dormer and gutter run in the upper-left of the frame. The CPT's
 * image (attachment 96) is a 480x633 copy, so at 2.22x it is upscaled and reads
 * softer than the design's render, which was cropped from the 1290x1702 original.
 * The FRAMING below is correct; only the sharpness is limited by the stored asset.
 * Fix if wanted: re-upload attachment 96 from the full-resolution original — the
 * homepage's own centred cover crop would be unchanged, just sharper.
 */
.pgs-jm-s04 .s5-card__img[src*="pgs-s5_card2"] {
	/* MIRRORED, like the hero house. A same-orientation search bottomed out at
	   mean|d| 53.7 and kept running to the search ceiling; flipping the source drops
	   it to 25.1 at zoom 1.49, and the 480px stored copy and the 1290px design
	   original independently converge on the SAME parameters (1.487 vs 1.493, offset
	   212/127 vs 213/128) — which is what confirms the geometry rather than the
	   optimiser chasing noise. */
	width: 153.25%; height: 148.73%; left: -45.89%; top: -20.22%;
	transform: scaleX( -1 );
}

.pgs-jm-s04 .s5-card__img[src*="pgs-s5_card3"] {
	width: 104.98%; height: 140.61%; left: -2.60%; top: -3.98%;
}

.pgs-jm-s04 .s5-card__img[src*="pgs-s5_card4"] {
	width: 107.14%; height: 100%; left: -3.68%; top: 0%;
}

/* ---------------------------------------------------------------------------
 * Jackson (218) — section 5, "A Better Approach to Exterior Care"
 * ------------------------------------------------------------------------ */

/**
 * Third instance of the same trap: Elementor's text-editor emits the theme's
 * 14.4px trailing <p> margin, which inflated the lede block from the design's 69px
 * to 82.9px and pushed the entire 4-card row and the CTA 14px down. This lede is a
 * single paragraph, so it only needs the margin removed (sections 2 and 3 also
 * needed the inter-paragraph gap restated as a full line of leading).
 */
.pgs-jm-s05-lede p { margin: 0; }

/* ---------------------------------------------------------------------------
 * Jackson (218) — section 6, "Real Work Around Jackson"
 * (pgs_project loop carousel, loop-item template 179)
 * ------------------------------------------------------------------------ */

/**
 * Elementor renders `pagination: progressbar` as an absolutely positioned bar at
 * the TOP of the swiper, spanning the full 2028px track width. The design puts it
 * 30px BELOW the cards and only as wide as the 1400px content box.
 *
 * Measured from the reference: track x260-1659 (1400 wide) at page y4891-4894,
 * 3px tall, colour ~#BCCBD4 on the #EBF7FF panel; the filled portion is the
 * primary green x260-1046.
 */
.pgs-jm-s06 .swiper {
	padding-bottom: 33px;
}

.pgs-jm-s06 .swiper-pagination-progressbar {
	top: auto;
	bottom: 0;
	left: 0;
	width: 1400px;
	max-width: 100%;
	height: 3px;
	border-radius: 2px;
	overflow: hidden;
	background: #BCCBD4;
}

.pgs-jm-s06 .swiper-pagination-progressbar .swiper-pagination-progressbar-fill {
	border-radius: 2px;
	background: var( --e-global-color-primary, #27A800 );
}

/**
 * The loop-item template (179) is shared with the homepage, where its "Show project"
 * row sits 11px higher than this page's design places it. Nudged here only — scoping
 * to `.pgs-jm-s06` leaves the homepage's own rendering of template 179 untouched.
 * The card box is unchanged (335px); the row still clears the bottom edge.
 */
.pgs-jm-s06 .pgs-s8-card-cta {
	margin-top: 11px;
}

/* ---------------------------------------------------------------------------
 * Jackson (218) — section 7, "A Gutter Company Sees a Gutter."
 * ------------------------------------------------------------------------ */

/**
 * The roofline cut-out is MIRRORED in the design — provable rather than inferred:
 * the iStock watermark baked into the photo ("iStock / Credit: db_beyo") and the
 * asset id along the bottom edge both read BACKWARDS in the reference render. With
 * the flip applied, the solver returns the export's own declared geometry exactly
 * (732x488 at panel offset 668,0), which is what confirms it; without the flip the
 * best fit was 30.09 -> 53.32 and drifted to a nonsense 1504px scale.
 *
 * Third mirrored image on this page, after the hero house and the s04 card-2 photo.
 * The watermark is preserved verbatim per project.additional_instructions.
 */
.pgs-jm-s07-cut img {
	transform: scaleX( -1 );
	/* The widget box is sized to 52.29% of the panel (=732px); the <img> must fill it.
	   Releasing the theme's max-width clamp without also stating a width let the image
	   render at its natural 2048x1365. */
	width: 100%;
	height: auto;
	max-width: none;
	display: block;
}

/* Two paragraphs at 24px leading, separated by one blank line, as in sections 2/3/5. */
.pgs-jm-s07-body p { margin: 0; }
.pgs-jm-s07-body p + p { margin-top: 24px; }

@media ( max-width: 991px ) {
	/* Stacked: the cut-out would sit under the copy, so it becomes a bottom-anchored
	   decorative band rather than a right-hand column. */
	.pgs-jm-s07-cut { position: static !important; width: 100%; margin-top: 32px; }
	.pgs-jm-s07-cut img { width: 100%; }
}

/* ---------------------------------------------------------------------------
 * Jackson (218) — section 8, testimonial
 *
 * Structurally identical to the service page's s8: same 120/259 padding, same
 * 413 + rule + 930 column split, same ghost images (attachments 208/209), the same
 * 170x53 arrows SVG (210) and quote mark (211), and the same `[PLACEHOLDER]` copy,
 * which BOTH designs carry verbatim. The tree was cloned from post 185 rather than
 * re-derived; only the class prefix changed, so these four rules are the service
 * page's, restated under this page's own names to keep the two pages decoupled.
 * ------------------------------------------------------------------------ */

.pgs-jm-s08-ghost img {
	opacity: 0.065;
}

/* Vertical rule between the columns: 1px of #E2E8EE, 401px tall. */
.pgs-jm-s08-rule {
	width: 1px;
	height: 401px;
	background: #e2e8ee;
	flex: 0 0 1px;
}

/* Prev/next arrows ship as ONE 170x53 SVG containing both discs. */
.pgs-jm-s08-arrows img {
	width: 170px;
	height: 53px;
	max-width: none;
}

/* The 78x78 avatar tile. An Elementor container defaults to full width and then
   shrinks as a flex item (measured 70.5px on the service page, which also dragged
   the name block 7.5px left). `.elementor .e-con` (0,3,0) is what beats Elementor's
   own width rule. */
.elementor .e-con.pgs-jm-s08-avatar {
	width: 78px;
	max-width: 78px;
	flex: 0 0 78px;
}

@media ( max-width: 991px ) {
	.pgs-jm-s08-rule { display: none; }
	.pgs-jm-s08-row { flex-direction: column; gap: 32px; }
	.pgs-jm-s08-left, .pgs-jm-s08-right { width: 100% !important; }
}

/* ---------------------------------------------------------------------------
 * Jackson (218) — section 9, service areas
 *
 * Cloned from the service page's s9 (same component: [pgs_service_areas] over the
 * homepage's `.s10-cards` grid, same 574x613 map, same note and button). Only the
 * H2 differs. These three rules are the service page's, restated under this page's
 * class names so the two pages stay decoupled — see the commentary above the
 * pgs-sgi-s9 block for why each one exists.
 * ------------------------------------------------------------------------ */

/* Height pinned to the design's 613 rather than `auto`: the source is 1290x1381
   (aspect 0.9341) against a 574x613 box (0.9364), so `auto` renders 614.5 and drags
   the section 1.5px taller. */
.pgs-jm-s09-map img {
	width: 100%;
	height: 613px;
	/* The design declares `contain`, not `cover`. Source 1290x1381 (aspect 0.9341)
	   against a 574x613 box (0.9364): cover scales to 574x614.5 and clips 1.5px of
	   height, contain scales to 572.6x613. The 0.25% scale difference is invisible on
	   a photo but shifts this map's dense labels by up to 1.5px at the edges, which is
	   what the blurred diff caught (12.58 with cover). */
	object-fit: contain;
	border-radius: 20px;
	display: block;
}

/* The shared `.s10-cards` block carries `margin: 0 0 24px`; this design's cards are
   88px tall where the homepage's are 84, so the grid ends 5.5px higher. */
.pgs-jm-s09-areas .s10-cards {
	margin-bottom: 27.5px;
}

/* Design draws this arrow at 18x20; Elementor sizes an SVG button icon from the font. */
.pgs-jm-s09-btn .elementor-button-icon,
.pgs-jm-s09-btn .elementor-button-icon svg {
	width: 18px;
	height: 18px;
}

@media ( max-width: 991px ) {
	.pgs-jm-s09-map img { height: auto; }
}

/* ---------------------------------------------------------------------------
 * Jackson (218) — section 10, FAQ accordion
 *
 * Cloned from the service page's s10 (same nested-accordion, same 16px radius,
 * same open-state blue panel, same plus/minus icons 213/214). Only the seven
 * questions and the heading differ. These rules are the service page's, restated
 * under this page's class names — the reasoning for each is documented above the
 * pgs-sgi-s10 block and is unchanged here.
 * ------------------------------------------------------------------------ */

/* nested-accordion registers `title_typography` behind a :where() wrapper, which
   contributes zero specificity, so the emitted rule is (0,1,0) and loses to the
   kit's `.elementor-kit-12 h3` at (0,1,1). Only font-size survives (it routes
   through a CSS variable the widget consumes at (0,2,0)). Restated at (0,2,0). */
.pgs-jm-s10-faq .e-n-accordion-item-title-text {
	line-height: 24px;
	font-weight: 600;
}

/* Paired with the widget's own font sizes against THIS kit's real ladder
   (tablet <=991, mobile <=575 — not 767). */
@media ( max-width: 991px ) {
	.pgs-jm-s10-faq .e-n-accordion-item-title-text { line-height: 22px; }
}
@media ( max-width: 575px ) {
	.pgs-jm-s10-faq .e-n-accordion-item-title-text { line-height: 21px; }
}

/* The design's OPEN item is one continuous 16px-radius blue panel. Elementor builds
   an open item from two boxes and `accordion_border_radius` is a single wrapper-level
   var applied to the header in every state, so an open header keeps its rounded
   bottom corners and the section gradient shows through two 16px wedges. There is no
   per-state radius control, so square the open header's bottom corners here; the
   content box's own radius (0 0 16 16) completes the rectangle. */
.pgs-jm-s10-faq .e-n-accordion-item[open] > .e-n-accordion-item-title {
	border-bottom-left-radius: 0;
	border-bottom-right-radius: 0;
}

/* ---------------------------------------------------------------------------
 * Jackson (218) — section 11, free quote (photo background + Elementor Pro form)
 *
 * The SAME component as the service page's s11 — same background (attachment 215)
 * and house cut-out (216), verified against this design's own assets at thumbnail
 * distance 0.47 and 1.53; same pills, same card, same field ladder, same fine
 * print. Only the H2 and the form name differ.
 *
 * Below is the service page's s11 CSS restated under this page's class names so
 * the two pages stay decoupled. Every comment is the original measured reasoning
 * and still applies verbatim — including the Pro-form field-height note, which is
 * why the inputs are given an explicit height rather than derived padding.
 * ------------------------------------------------------------------------ */
/* ============================================================================
   SECTION 11 — Free quote (photo background + Elementor Pro form)
   ========================================================================= */

/* Trust pills must hug their label. An Elementor container is width:100% by
   default and its `width` control has no "auto", so three pills each filled the
   row and stacked (measured 595 wide each) instead of flowing 2 + 1 as designed.
   Same remedy already used for the s5 badge, s6 buttons and s8 avatar. */
.elementor .e-con.pgs-jm-s11-pill {
  width: auto;
  max-width: 100%;
  flex: 0 0 auto;
}

/* The design's inputs are 57px tall: padding 16.2/18 + 1.5px border + a 21.6px
   line box. The Pro form widget exposes typography, colour, border width/colour
   and radius for its fields but NO padding control (only `button_text_padding`
   exists, for the submit button), so Elementor's own 8px/16px default padding
   held and every field measured 40px — which then pushed the whole form's
   vertical rhythm up by ~18px per row. Set the design's padding here.
   The textarea needs its own declaration: Elementor gives it a different
   default (5px/14px) from the textual inputs. */
.pgs-jm-s11-form .elementor-field-textual {
  box-sizing: border-box;
  height: 57px;
  padding: 16.2px 18px;
  border-width: 1.5px;
}
/* Height is stated outright rather than left to padding + line-height + border,
   because the field box is what the form's whole vertical rhythm is built from:
   the design's rows sit at y220/329/437/546 (a 108.66px pitch = a 93.16px group
   plus the 15.5px row gap), and deriving 57 from three separate values left it at
   56.4 and the drift reached -4.8px by the last label. */
.pgs-jm-s11-form textarea.elementor-field-textual {
  height: 57px;
  padding: 16.2px 18px;
  resize: vertical;
}
/* Placeholder colour — measured #8B98A4 off the design's native @2x render
   (identical to the fine-print colour). The form widget has no placeholder
   control; without this the browser default grey applies. */
.pgs-jm-s11-form .elementor-field-textual::placeholder {
  color: #8B98A4;
  opacity: 1;
}

/* The house cutout is NOT cropped — it renders at its natural aspect (828x552),
   absolutely positioned bottom-left by the widget's own offset controls, and the
   section's own overflow:hidden + 50px radius clip it.
   The export says `image 15` is 828x495 with objectFit cover / objectPosition
   "0% 10%", and BOTH of those are wrong — the element also carries hasMask:true,
   so those bounds describe Figma's mask frame, not the image placement. Solved by
   silhouette IoU instead: classify every design pixel in the lower-left quadrant
   as photo-vs-blue, then match the candidate's alpha mask over scale x offset.
   Result: IoU 0.9740 at exactly cover scale (0.4994), placed at y380 with its
   bottom flush to the section — i.e. uncropped and bottom-aligned. Cropping to
   495 with the declared 10% offset measured 77.6% differing in that region; every
   y offset from 0% to 100% stayed above 41%, which is what proved the box wrong
   rather than merely mispositioned. */
.pgs-jm-s11-house img {
  display: block;
}
@media (max-width: 991px) {
  /* stacked layout: let the cutout scale with the section instead of overlapping
     the copy, and keep it decorative */
  .pgs-jm-s11-house { display: none; }
}

