/*
Theme Name: Precision Plumbing (Ollie Child)
Theme URI: https://precisionplumbing.co
Description: Ollie child theme for Evan Shelley's Precision Plumbing LLC. Maps the Precision brand (cyan/blue gradient, Barlow type system) onto Ollie's design tokens and adds custom section patterns for services, service areas, and conversion blocks.
Author: PMF
Template: ollie
Version: 0.1.0
Requires at least: 6.5
Requires PHP: 7.4
License: GNU General Public License v3 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
Text Domain: precision-ollie
*/

/* Reserve ALL-CAPS for eyebrows and buttons (brand rule). */
.pp-eyebrow {
	text-transform: uppercase;
	letter-spacing: 0.08em;
}

/* Signature accent rule: short cyan-to-blue underline used beside section headings.
   The !important margins beat the constrained-layout auto-centering that would
   otherwise push this 3.5rem rule into the middle of the column. */
.pp-accent-rule,
hr.pp-accent-rule {
	display: block;
	width: 4.5rem;
	height: 5px;
	margin: 0 auto 0 0 !important;
	border: 0;
	border-radius: 3px;
	opacity: 1;
	/* Decorative only — keeps the vivid brand cyan, which carries no text. */
	background: linear-gradient(135deg, var(--wp--preset--color--primary-alt) 0%, var(--wp--preset--color--primary) 100%);
}

/* ---------------------------------------------------------------------------
   Card depth. Priority cards carry weight; secondary link cards stay light,
   so the eye is told what matters instead of scanning a uniform field.
--------------------------------------------------------------------------- */

.pp-card-major {
	overflow: hidden;
	background-color: var(--wp--preset--color--base);
	box-shadow: 0 1px 2px rgba(17, 17, 17, 0.06), 0 8px 24px -12px rgba(17, 17, 17, 0.18);
	transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.pp-card-major:hover {
	transform: translateY(-4px);
	box-shadow: 0 2px 4px rgba(17, 17, 17, 0.08), 0 18px 40px -14px rgba(27, 117, 187, 0.35);
}

/* Image fills the card's top edge and gains a slow zoom on hover. */
.pp-card-major figure.wp-block-image {
	margin: 0;
	overflow: hidden;
}

.pp-card-major figure.wp-block-image img {
	display: block;
	width: 100%;
	aspect-ratio: 3 / 2;
	object-fit: cover;
	transition: transform 0.5s ease;
}

.pp-card-major:hover figure.wp-block-image img {
	transform: scale(1.045);
}

/* Secondary link cards: a quieter hover that still confirms interactivity. */
.pp-card-minor {
	transition: border-color 0.2s ease, background-color 0.2s ease;
}

.pp-card-minor:hover {
	border-color: var(--wp--preset--color--primary-alt) !important;
	background-color: var(--wp--preset--color--tertiary);
}

@media (prefers-reduced-motion: reduce) {
	.pp-card-major,
	.pp-card-major figure.wp-block-image img,
	.pp-card-minor {
		transition: none;
	}
	.pp-card-major:hover {
		transform: none;
	}
	.pp-card-major:hover figure.wp-block-image img {
		transform: none;
	}
}

/* Offer badge chip (amber, offers only per brand rules). */
.pp-badge {
	display: inline-block;
	width: fit-content;
	padding: 0.25rem 0.5rem;
	border-radius: 3px;
	text-transform: uppercase;
	letter-spacing: 0.06em;
}

/* CallRail dynamic number insertion swap target (PRD: consistent class on every phone number). */
.pp-phone {
	white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Dark sections.

   The site runs dark-dominant (~60/40). Rather than rewrite every child's
   colour attributes when a section flips, `.pp-dark` remaps the palette for
   its whole subtree. Blocks keep their semantic colour classes; this decides
   what those mean on a dark ground.
--------------------------------------------------------------------------- */

.pp-dark {
	background-color: var(--wp--preset--color--main) !important;
	color: var(--wp--preset--color--base);
	/* Navy-tinted surfaces so raised cards read as the same family as the
	   ground, rather than as neutral grey sitting on blue. */
	--pp-surface: #16202e;
	--pp-surface-hover: #1b2738;
	--pp-hairline: #2a3547;
}

.pp-dark h1,
.pp-dark h2,
.pp-dark h3,
.pp-dark h4,
.pp-dark strong {
	color: var(--wp--preset--color--base);
}

/* Body copy that reads as "muted dark grey" on light becomes muted light. */
.pp-dark .has-secondary-color,
.pp-dark p:not([class*="has-"]),
.pp-dark li:not([class*="has-"]) {
	color: var(--wp--preset--color--main-accent) !important;
}

.pp-dark .has-main-color {
	color: var(--wp--preset--color--base) !important;
}

/* Eyebrows and links lift to the vivid cyan, which passes on dark. */
.pp-dark .has-primary-color,
.pp-dark .pp-eyebrow {
	color: var(--wp--preset--color--primary-alt) !important;
}

.pp-dark a:not(.wp-block-button__link):not(.wp-element-button) {
	color: var(--wp--preset--color--primary-alt);
}

/* Cards sit on a slightly raised surface instead of white. */
.pp-dark .pp-card-major,
.pp-dark .pp-card-minor,
.pp-dark .has-base-background-color,
.pp-dark .wp-block-details {
	background-color: var(--pp-surface) !important;
	border-color: var(--pp-hairline) !important;
}

.pp-dark .pp-card-minor:hover {
	background-color: var(--pp-surface-hover) !important;
	border-color: var(--wp--preset--color--primary-alt) !important;
}

.pp-dark .pp-card-major {
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 12px 32px -14px rgba(0, 0, 0, 0.8);
}

.pp-dark .pp-card-major:hover {
	box-shadow: 0 2px 4px rgba(0, 0, 0, 0.55), 0 20px 44px -14px rgba(41, 171, 226, 0.4);
}

/* Tertiary-tinted inner blocks (the direct answer) need a dark equivalent. */
.pp-dark .has-tertiary-background-color {
	background-color: var(--pp-surface) !important;
}

.pp-dark .has-border-light-border-color {
	border-color: var(--pp-hairline) !important;
}

/* Outline buttons invert. */
.pp-dark .is-style-outline .wp-block-button__link {
	color: var(--wp--preset--color--base) !important;
	border-color: var(--pp-hairline) !important;
}

/* ---------------------------------------------------------------------------
   Navy grounds.

   The dark ground is navy rather than pure black, and the header carries a
   left-to-right blue shift so the bar has depth instead of reading as a flat
   slab. Every stop was contrast-checked: the lightest (#16294A) still gives
   14.5:1 with white and 5.5:1 with the brand cyan.
--------------------------------------------------------------------------- */

.site-header {
	background-color: var(--wp--preset--color--main) !important;
	background-image: linear-gradient(100deg, #0b1220 0%, #16294a 52%, #0d1726 100%) !important;
}

/* The dark sections get a gentler version of the same shift, so the page
   reads as one material rather than a stack of separate blocks. */
.pp-dark,
.pp-schematic {
	background-image: linear-gradient(165deg, #0b1220 0%, #101c2e 60%, #0c1420 100%);
}

/* Cover blocks keep their photographic overlay — don't tint those. */
.wp-block-cover.pp-dark,
.wp-block-cover.pp-schematic {
	background-image: none;
}

/* ---------------------------------------------------------------------------
   Section reveal. The class is only ever added by motion.js, so with JS off
   or reduced motion on, nothing is hidden.
--------------------------------------------------------------------------- */

.pp-reveal {
	opacity: 0;
	transform: translateY(18px);
	will-change: opacity, transform;
}

.pp-revealed {
	opacity: 1;
	transform: none;
	transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}

@media (prefers-reduced-motion: reduce) {
	.pp-reveal {
		opacity: 1;
		transform: none;
	}
}

/* ---------------------------------------------------------------------------
   Service page components — symptoms, process steps, pull quote.
--------------------------------------------------------------------------- */

.pp-symptom-grid {
	row-gap: var(--wp--preset--spacing--medium) !important;
	column-gap: var(--wp--preset--spacing--medium) !important;
}

.pp-symptom {
	background-color: rgba(255, 255, 255, 0.035);
	transition: border-color 0.2s ease, background-color 0.2s ease;
}

.pp-symptom:hover {
	border-color: var(--wp--preset--color--primary-alt) !important;
	background-color: rgba(41, 171, 226, 0.07);
}

.pp-symptom-num {
	margin: 0 0 0.4rem !important;
	font-size: 1.5rem;
	font-weight: 800;
	line-height: 1;
	letter-spacing: -0.03em;
	background: linear-gradient(135deg, var(--wp--preset--color--primary-alt) 0%, var(--wp--preset--color--primary) 100%);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

/* Numbered process steps sit against a hairline spine. */
.pp-steps {
	border-left: 1px solid var(--wp--preset--color--border-light);
	padding-left: var(--wp--preset--spacing--medium);
}

.pp-step {
	gap: var(--wp--preset--spacing--small) !important;
	align-items: baseline !important;
}

.pp-step p {
	margin: 0;
}

.pp-step-num {
	flex: 0 0 2.25rem;
	font-size: 1.0625rem;
	font-weight: 800;
	letter-spacing: -0.02em;
	color: var(--wp--preset--color--primary);
}

.pp-process-shot img {
	display: block;
	width: 100%;
	aspect-ratio: 4 / 5;
	object-fit: cover;
	border-radius: 10px;
}

/* The consequence paragraph runs at display scale — the page's one big
   typographic moment after the hero. */
.pp-pullquote-text {
	max-width: 22ch;
	font-size: clamp(1.75rem, 3.4vw, 2.75rem);
	font-weight: 700;
	line-height: 1.18;
	letter-spacing: -0.02em;
	color: var(--wp--preset--color--base) !important;
	max-width: 34ch;
}

@media (max-width: 781px) {
	.pp-process-shot img {
		aspect-ratio: 3 / 2;
	}
}

/* ---------------------------------------------------------------------------
   Animated pipe-schematic background.

   A slowly drifting technical drawing of pipe runs — elbows, tees and joints —
   rather than abstract orbs. It reads as blueprint, which suits a journeyman
   trade, and it gives the dark sections colour and movement without competing
   with the photography. Two layers: the schematic itself drifts, and a soft
   cyan wash breathes underneath it.
--------------------------------------------------------------------------- */

.pp-schematic {
	position: relative;
	isolation: isolate;
	overflow: hidden;
}

/* Layer 1 — the drifting schematic. */
.pp-schematic::before {
	content: "";
	position: absolute;
	inset: -80px;
	z-index: -2;
	pointer-events: none;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='420' height='420' viewBox='0 0 420 420'%3E%3Cg fill='none' stroke='%2329ABE2' stroke-width='1.5'%3E%3Cpath d='M-20 70 H140 a22 22 0 0 1 22 22 V190 a22 22 0 0 0 22 22 H440'/%3E%3Cpath d='M-20 320 H70 a22 22 0 0 0 22-22 V130 a22 22 0 0 1 22-22 H440'/%3E%3Cpath d='M210 -20 V48 a22 22 0 0 0 22 22 H300'/%3E%3Cpath d='M330 440 V270 a22 22 0 0 1 22-22 H440'/%3E%3Cpath d='M162 212 V440'/%3E%3C/g%3E%3Cg fill='%2329ABE2'%3E%3Ccircle cx='162' cy='92' r='3.5'/%3E%3Ccircle cx='184' cy='212' r='3.5'/%3E%3Ccircle cx='92' cy='298' r='3.5'/%3E%3Ccircle cx='114' cy='108' r='3.5'/%3E%3Ccircle cx='352' cy='248' r='3.5'/%3E%3Ccircle cx='232' cy='70' r='3.5'/%3E%3C/g%3E%3C/svg%3E");
	background-size: 420px 420px;
	opacity: 0.13;
	animation: pp-schematic-drift 90s linear infinite;
}

/* Layer 2 — a slow cyan wash so the section has colour, not just texture. */
.pp-schematic::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	background:
		radial-gradient(60% 50% at 12% 0%, rgba(41, 171, 226, 0.16), transparent 70%),
		radial-gradient(50% 60% at 92% 100%, rgba(27, 117, 187, 0.18), transparent 72%);
	animation: pp-schematic-wash 18s ease-in-out infinite alternate;
}

@keyframes pp-schematic-drift {
	from { background-position: 0 0; }
	to   { background-position: 420px 210px; }
}

@keyframes pp-schematic-wash {
	from { opacity: 0.55; }
	to   { opacity: 1; }
}

/* Motion is decoration here — hold the texture, drop the movement. */
@media (prefers-reduced-motion: reduce) {
	.pp-schematic::before,
	.pp-schematic::after {
		animation: none;
	}
}

/* ---------------------------------------------------------------------------
   Mega menu panels (Ollie Menu Designer).
--------------------------------------------------------------------------- */

.pp-mega {
	box-shadow: 0 24px 60px -18px rgba(0, 0, 0, 0.7);
}

/* The plugin anchors the panel to its nav item, so a wide panel opened from a
   left-hand item runs off the right edge. Pin it to the viewport instead and
   centre it under the header. Desktop only — below the breakpoint the panel
   becomes the full-screen mobile overlay, which positions itself. */
@media (min-width: 1024px) {
	/* Fallback only. assets/trust.js measures the real header and overwrites
	   --pp-header-height, so adding or removing a header row (like the utility
	   strip) no longer silently misaligns the mega panel. This value is what a
	   no-JS visitor gets, and no-JS visitors do not get hover mega menus anyway. */
	:root {
		--pp-header-height: 124px;
	}

	.wp-block-ollie-mega-menu__menu-container.menu-width-wide {
		position: fixed !important;
		top: var(--pp-header-height, 90px) !important;
		left: 50% !important;
		right: auto !important;
		transform: translateX(-50%);
		width: min(var(--wp--style--global--wide-size), calc(100vw - 3rem)) !important;
		max-width: none !important;
	}
}

.pp-mega-label {
	color: var(--wp--preset--color--primary-alt) !important;
	letter-spacing: 0.09em;
	margin-bottom: 0.75rem;
}

.pp-mega-cols {
	row-gap: var(--wp--preset--spacing--medium) !important;
	column-gap: var(--wp--preset--spacing--large) !important;
}

/* Force the three groups onto one row. WP's auto-fill maths, against the
   panel's padding and the aside, was collapsing them to two and leaving a
   dead column. */
@media (min-width: 1024px) {
	.pp-mega-cols {
		grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
		column-gap: var(--wp--preset--spacing--medium) !important;
	}
}

/* Categorised columns: a hairline separates each group's label from its links. */
.pp-mega-col p {
	margin: 0;
}

.pp-mega-col a {
	display: block;
	padding: 0.4rem 0;
	text-decoration: none;
	color: var(--wp--preset--color--main-accent) !important;
	transition: color 0.15s ease, padding-left 0.15s ease;
}

.pp-mega-col a:hover {
	color: var(--wp--preset--color--primary-alt) !important;
	padding-left: 0.35rem;
}

/* The first item in each group is the one people actually want. */
.pp-mega-lead a {
	color: var(--wp--preset--color--base) !important;
	font-weight: 700;
}

.pp-mega-label--sub {
	margin-top: var(--wp--preset--spacing--medium) !important;
}

.pp-mega-featured {
	gap: var(--wp--preset--spacing--large) !important;
}

/* Each service row is one hit target, lifted on hover. */
.pp-mega-item {
	gap: 0.75rem !important;
	padding: 0.5rem;
	margin: -0.5rem;
	border-radius: 8px;
	transition: background-color 0.18s ease;
}

.pp-mega-item:hover {
	background-color: #242427;
}

.pp-mega-thumb {
	margin: 0;
	flex: 0 0 56px;
}

.pp-mega-thumb img {
	display: block;
	width: 56px;
	height: 56px;
	object-fit: cover;
	border-radius: 6px;
}

.pp-mega-item a {
	color: var(--wp--preset--color--base) !important;
	text-decoration: none;
}

.pp-mega-item:hover a {
	color: var(--wp--preset--color--primary-alt) !important;
}

.pp-mega-links p {
	margin: 0;
}

.pp-mega-links a {
	display: block;
	padding: 0.3rem 0;
	text-decoration: none;
}

.pp-mega-links a:hover {
	text-decoration: underline;
}

.pp-mega-rule {
	border-color: var(--wp--preset--color--border-dark) !important;
	opacity: 1;
	margin: var(--wp--preset--spacing--small) 0 !important;
}

.pp-mega-hero img {
	display: block;
	width: 100%;
	aspect-ratio: 16 / 10;
	object-fit: cover;
	border-radius: 8px;
}

.pp-mega-card {
	background-color: #1c1c1e;
}

/* Cities read as a two-up list rather than a wrapped paragraph run. */
.pp-mega-cities a {
	border-bottom: 1px solid var(--wp--preset--color--border-dark);
}

@media (max-width: 1023px) {
	.pp-mega-aside {
		display: none;
	}
}

/* ---------------------------------------------------------------------------
   Section seams. WordPress gives every top-level block a 32px block gap, which
   showed as a white stripe between sections because each section paints its
   own background. Every section in this theme supplies its own vertical
   padding, so the gap between them should be zero and the colours should meet.
--------------------------------------------------------------------------- */

.wp-block-post-content > * {
	margin-block-start: 0;
	margin-block-end: 0;
}

/* Header and footer butt against the content for the same reason. */
.site-content {
	margin-block-start: 0 !important;
	margin-block-end: 0 !important;
}

/* Direct-answer block: keeps a readable measure but starts at the same left
   edge as every other section, instead of floating centered in the page. */
.pp-direct-answer > .has-tertiary-background-color {
	max-width: 52rem;
	margin-left: 0 !important;
	margin-right: auto !important;
}

/* ---------------------------------------------------------------------------
   Split feature. The photograph runs to the viewport's left edge while the
   copy stays on the page grid — the one place the layout deliberately breaks
   the rectangle-in-a-grid rhythm.
--------------------------------------------------------------------------- */

.pp-split {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	align-items: center;
	gap: 0;
}

.pp-split-media,
.pp-split-media figure.wp-block-image {
	margin: 0;
	height: 100%;
}

.pp-split-media figure.wp-block-image img {
	display: block;
	width: 100%;
	height: 100%;
	min-height: 26rem;
	object-fit: cover;
}

.pp-split-body {
	/* Sit on the same rhythm as the rest of the page, not hard against the fold. */
	padding: var(--wp--preset--spacing--xx-large) var(--wp--preset--spacing--medium);
	max-width: 34rem;
	margin-right: auto;
	padding-left: var(--wp--preset--spacing--x-large);
}

@media (max-width: 781px) {
	.pp-split {
		grid-template-columns: minmax(0, 1fr);
	}
	.pp-split-media figure.wp-block-image img {
		min-height: 0;
		aspect-ratio: 4 / 3;
	}
	.pp-split-body {
		max-width: none;
		padding: var(--wp--preset--spacing--x-large) var(--wp--preset--spacing--medium);
	}
}

/* ---------------------------------------------------------------------------
   Before/after showcase. Photographs are the strongest asset on the site, so
   they run at scale on a dark ground instead of as thumbnails.
--------------------------------------------------------------------------- */

.pp-shot {
	position: relative;
}

.pp-shot figure.wp-block-image {
	margin: 0;
	overflow: hidden;
	border-radius: 6px;
}

.pp-shot figure.wp-block-image img {
	display: block;
	width: 100%;
	aspect-ratio: 4 / 3;
	object-fit: cover;
}

/* The BEFORE / AFTER tag sits on the image rather than stacking above it. */
.pp-showcase-feature .pp-shot figure.wp-block-image img {
	aspect-ratio: 3 / 2;
}

.pp-showcase-more {
	row-gap: var(--wp--preset--spacing--x-large) !important;
	column-gap: var(--wp--preset--spacing--large) !important;
}

/* Team headshots: square crops so a mix of source ratios still lines up. */
.pp-team-photo {
	margin: 0 0 var(--wp--preset--spacing--small);
}

.pp-team-photo img {
	display: block;
	width: 100%;
	aspect-ratio: 1 / 1;
	object-fit: cover;
	object-position: center top;
	border-radius: 6px;
}

/* ---------------------------------------------------------------------------
   Proof stats band. Oversized numerals are the point — they give the page a
   scale change it otherwise never gets, and break the white/near-white
   striping with a full dark moment.
--------------------------------------------------------------------------- */

.pp-stats-grid {
	/* Tightened from x-large: as raised cards the stats no longer need a wide
	   gutter to separate them, and the section was running 680px for four short
	   facts. */
	row-gap: var(--wp--preset--spacing--large) !important;
	column-gap: var(--wp--preset--spacing--medium) !important;
}

/* Each stat is a raised panel rather than a hairline-topped column. As bare text
   on the schematic ground they read as loose fragments; a surface gives the
   section the weight the guarantees deserve. */
.pp-stat {
	position: relative;
	overflow: hidden;
	padding: var(--wp--preset--spacing--medium);
	border: 1px solid var(--pp-hairline, #2a3547);
	border-radius: 12px;
	background-color: rgba(22, 32, 46, 0.72);
	/* Sits over the animated schematic, so a blur keeps the pattern legible
	   behind without competing with the numerals. */
	backdrop-filter: blur(2px);
	transition: transform 0.25s ease, border-color 0.25s ease, background-color 0.25s ease;
}

/* Brand rule down the leading edge, echoing the coupon cards. */
.pp-stat::before {
	content: "";
	position: absolute;
	inset-block: 0;
	inset-inline-start: 0;
	inline-size: 3px;
	background: linear-gradient(180deg, var(--wp--preset--color--primary-alt) 0%, var(--wp--preset--color--primary-deep) 100%);
	opacity: 0.85;
}

.pp-stat:hover {
	transform: translateY(-3px);
	border-color: var(--wp--preset--color--primary-alt);
	background-color: rgba(27, 39, 56, 0.82);
}

@media (prefers-reduced-motion: reduce) {
	.pp-stat,
	.pp-stat:hover {
		transition: none;
		transform: none;
	}
}

/* The gradient rides the numeral itself. */
.pp-stat-figure {
	margin: 0 0 0.35rem;
	font-size: clamp(3.5rem, 6vw, 5rem);
	font-weight: 800;
	line-height: 0.9;
	letter-spacing: -0.04em;
	background: linear-gradient(135deg, var(--wp--preset--color--primary-alt) 0%, var(--wp--preset--color--primary) 100%);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

/* The licence block is text, not a figure — it needs a smaller, calmer size. */
.pp-stat-figure--small {
	font-size: clamp(1.75rem, 2.6vw, 2.25rem);
	line-height: 1.05;
	letter-spacing: -0.02em;
}

.pp-stat-unit {
	font-size: 0.42em;
	letter-spacing: -0.01em;
	margin-left: 0.1em;
}

.pp-stat-label {
	margin: 0 0 0.35rem;
	font-weight: 600;
}

/* ---------------------------------------------------------------------------
   Quote request form ([precision_quote_form]).
--------------------------------------------------------------------------- */

.pp-quote-form {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--wp--preset--spacing--small);
}

/* Fields that need the full row. */
.pp-quote-form .pp-field--textarea,
.pp-quote-form .pp-field--radio,
.pp-quote-form .pp-field--checkbox,
.pp-quote-form .pp-error--form,
.pp-quote-form .pp-formnote,
.pp-quote-form .pp-submit {
	grid-column: 1 / -1;
}

@media (max-width: 599px) {
	.pp-quote-form {
		grid-template-columns: minmax(0, 1fr);
	}
}

.pp-quote-form label,
.pp-quote-form legend {
	display: block;
	margin-bottom: 0.35rem;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

.pp-quote-form input[type="text"],
.pp-quote-form input[type="tel"],
.pp-quote-form input[type="email"],
.pp-quote-form select,
.pp-quote-form textarea {
	width: 100%;
	padding: 0.7rem 0.85rem;
	border: 1px solid var(--wp--preset--color--border-light);
	border-radius: 4px;
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--main);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--small);
}

.pp-quote-form input:focus-visible,
.pp-quote-form select:focus-visible,
.pp-quote-form textarea:focus-visible,
.pp-quote-form button:focus-visible {
	outline: 2px solid var(--wp--preset--color--primary);
	outline-offset: 2px;
}

/* Choice rows read as one line, not a stacked label. */
.pp-quote-form .pp-choice {
	display: flex;
	gap: 0.5rem;
	align-items: flex-start;
	margin-bottom: 0.35rem;
	font-weight: 400;
}

.pp-quote-form fieldset {
	margin: 0;
	padding: 0;
	border: 0;
}

.pp-quote-form .pp-req {
	color: var(--wp--preset--color--primary);
}

.pp-quote-form .pp-optional {
	color: var(--wp--preset--color--secondary);
	font-weight: 400;
}

.pp-quote-form .has-error input,
.pp-quote-form .has-error select,
.pp-quote-form .has-error textarea {
	border-color: #b3261e;
}

.pp-error {
	margin: 0.35rem 0 0;
	color: #b3261e;
	font-size: var(--wp--preset--font-size--x-small);
}

.pp-error--form {
	padding: 0.75rem 1rem;
	border-left: 4px solid #b3261e;
	background-color: #fdeceb;
	font-size: var(--wp--preset--font-size--small);
}

.pp-formnote {
	margin: 0;
	color: var(--wp--preset--color--secondary);
	font-size: var(--wp--preset--font-size--x-small);
}

.pp-submit {
	justify-self: start;
	padding: 0.75rem 1.75rem;
	border: 0;
	border-radius: 4px;
	background-image: linear-gradient(135deg, var(--wp--preset--color--primary-deep) 0%, var(--wp--preset--color--primary) 100%);
	color: var(--wp--preset--color--base);
	font-family: var(--wp--preset--font-family--condensed);
	font-size: var(--wp--preset--font-size--base);
	font-weight: 700;
	cursor: pointer;
}

.pp-submit:hover {
	background-image: linear-gradient(135deg, var(--wp--preset--color--primary) 0%, var(--wp--preset--color--primary-deep) 100%);
}

/* Honeypot: off-screen rather than display:none, which bots skip. */
.pp-hp {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

.pp-form-success {
	padding: var(--wp--preset--spacing--large);
	border-left: 4px solid var(--wp--preset--color--primary-alt);
	border-radius: 4px;
	background-color: var(--wp--preset--color--tertiary);
}

.pp-form-success h3 {
	margin-top: 0;
}

/* Long unbroken strings (the contact email) must wrap inside narrow cards
   rather than run past the edge. */
a[href^="mailto:"] {
	overflow-wrap: anywhere;
}

/* Map embed keeps its aspect ratio at every width. */
.pp-map iframe {
	display: block;
	width: 100%;
	height: 100%;
	border: 0;
}

.pp-map {
	aspect-ratio: 16 / 10;
	border-radius: 8px;
	overflow: hidden;
	/* Neutral ground so a slow or blocked embed reads as loading, not broken. */
	background-color: var(--wp--preset--color--tertiary);
	border: 1px solid var(--wp--preset--color--border-light);
}

/* Filter chips (reviews, our work). The active chip inverts to the brand fill. */
.pp-filter .wp-block-button__link {
	background-color: transparent;
	background-image: none;
	border: 1px solid var(--wp--preset--color--border-light);
	color: var(--wp--preset--color--main);
}

.pp-filter.is-active .wp-block-button__link,
.pp-filter .wp-block-button__link:hover,
.pp-filter .wp-block-button__link:focus {
	background-image: linear-gradient(135deg, var(--wp--preset--color--primary-deep) 0%, var(--wp--preset--color--primary) 100%);
	border-color: transparent;
	color: var(--wp--preset--color--base);
}

/* Cards hidden by the filter script — [hidden] alone loses to grid display. */
.pp-review-card[hidden],
.pp-work-card[hidden] {
	display: none;
}

/* ---------------------------------------------------------------------------
   Header responsive pass.
   Desktop (≥1024px): logo | inline nav · hours chip · phone button.
   Tablet + mobile (<1024px): single row — logo | phone button · hamburger.
   The inline-nav breakpoint WP ships is 600px, too narrow for this header,
   so the hamburger is kept through tablet widths.
--------------------------------------------------------------------------- */

@media (max-width: 1023px) {
	/* The status and rating chips are desktop-only: at mobile the header is one
	   row (logo | phone | hamburger) and the sticky call bar carries the status
	   instead. Leaving them in wrapped the 390px header to 237px tall. */
	.site-header .pp-status,
	.site-header .pp-ratingbadge {
		display: none;
	}
	/* Ollie Menu Designer emits an identical breakpoint rule from the nav
	   block's mobileMenuBreakpoint (1024). This duplicate is a deliberate
	   fallback: if the plugin is ever deactivated, WP reverts to its own
	   600px cutoff and the inline nav would crowd the header from 600–1023px.
	   Same intent, so the two rules cannot conflict. */
	.site-header .wp-block-navigation__responsive-container:not(.is-menu-open) {
		display: none;
	}
	.site-header .wp-block-navigation__responsive-container-open:not(.always-shown) {
		display: flex;
	}
	/* Phone button before the hamburger; hamburger sits outermost right. */
	.site-header .pp-nav-actions {
		gap: 0.75rem;
	}
	.site-header .pp-nav-actions .wp-block-buttons {
		order: 1;
	}
	.site-header .pp-nav-actions .wp-block-navigation {
		order: 2;
	}
}

@media (max-width: 479px) {
	/* Mark-only logo and a compact call button so one row fits 390px. */
	.site-header .pp-wordmark {
		display: none;
	}
	.site-header .pp-nav-actions .pp-phone .wp-block-button__link {
		font-size: var(--wp--preset--font-size--x-small);
		padding: 0.65rem 0.9rem;
	}
}

/* Overlay menu: roomier type. The call CTA inside the overlay is now a real
   template part (parts/mobile-menu.html) rendered by Ollie Menu Designer,
   so it needs no CSS of its own. */
.wp-block-navigation__responsive-container.is-menu-open {
	padding: var(--wp--preset--spacing--large) var(--wp--preset--spacing--medium);
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item__content {
	font-size: var(--wp--preset--font-size--large);
	font-weight: 600;
}

.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container {
	gap: var(--wp--preset--spacing--medium);
}

/* Menu Designer injects its part above the link list; give them separation. */
.wp-block-navigation__mobile-menu-content {
	margin-bottom: var(--wp--preset--spacing--large);
}

/* ---------------------------------------------------------------------------
   COVERAGE MAP  (inline SVG, [precision_coverage_map])

   Themed through local custom properties so the same SVG reads correctly on
   the light, tertiary, and .pp-dark grounds. `--pp-cm-bg` must match whatever
   the map is sitting on: it is the halo colour behind label text, which is how
   names stay legible where they cross a highway line.
--------------------------------------------------------------------------- */

.pp-coverage-map {
	margin: 0;
	--pp-cm-bg: var(--wp--preset--color--base);
	--pp-cm-line: var(--wp--preset--color--border-light);
	--pp-cm-name: var(--wp--preset--color--main);
	--pp-cm-meta: var(--wp--preset--color--secondary);
	--pp-cm-dot: var(--wp--preset--color--primary-deep);
	--pp-cm-ring: rgba(41, 171, 226, 0.18);
	--pp-cm-active: #c77700;
}

.pp-coverage-map .pp-cm-svg {
	display: block;
	width: 100%;
	height: auto;
	overflow: visible;
}

/* Coverage wash. Edgeless by design — see the note in inc/coverage-map.php. */
.pp-cm-stop-1 { stop-color: #29abe2; stop-opacity: 0.17; }
.pp-cm-stop-2 { stop-color: #29abe2; stop-opacity: 0.09; }
.pp-cm-stop-3 { stop-color: #29abe2; stop-opacity: 0; }

.pp-cm-road {
	fill: none;
	stroke: var(--pp-cm-line);
	stroke-width: 2.5;
	stroke-dasharray: 9 8;
	stroke-linecap: round;
}

.pp-cm-road-label {
	fill: var(--pp-cm-meta);
	font-family: var(--wp--preset--font-family--condensed);
	font-size: 15px;
	letter-spacing: 0.1em;
}

/* Text sits above the road lines; the background-coloured stroke behind each
   glyph keeps names readable where the two cross. */
.pp-cm-label,
.pp-cm-sub {
	paint-order: stroke fill;
	stroke: var(--pp-cm-bg);
	stroke-width: 6px;
	stroke-linejoin: round;
}

.pp-cm-label {
	fill: var(--pp-cm-name);
	font-size: 22px;
	font-weight: 600;
}

.pp-cm-sub {
	fill: var(--pp-cm-meta);
	font-family: var(--wp--preset--font-family--condensed);
	font-size: 16px;
}

.pp-cm-hit { fill: transparent; }
.pp-cm-ring { fill: var(--pp-cm-ring); }

.pp-cm-dot {
	fill: var(--pp-cm-dot);
	transition: r 0.18s ease;
}

.pp-cm-city { cursor: pointer; }

.pp-cm-city:hover .pp-cm-dot,
.pp-cm-city:focus-visible .pp-cm-dot { r: 10; }

.pp-cm-city:hover .pp-cm-label,
.pp-cm-city:focus-visible .pp-cm-label {
	fill: var(--wp--preset--color--primary-deep);
	text-decoration: underline;
}

/* SVG ignores `outline` inconsistently, so the oversized hit circle carries the
   focus indicator instead. */
.pp-cm-city:focus { outline: none; }

.pp-cm-city:focus-visible .pp-cm-hit {
	stroke: var(--wp--preset--color--primary);
	stroke-width: 3;
}

/* Springfield — the shop. Larger, and the only marker with a filled ring. */
.pp-cm-city.is-hq .pp-cm-dot { r: 9; }

.pp-cm-city.is-hq .pp-cm-ring {
	fill: none;
	stroke: var(--pp-cm-dot);
	stroke-width: 2.5;
}

.pp-cm-city.is-hq .pp-cm-label { font-size: 24px; }

/* The city whose page you are on. Not a link, so it must read as "you". */
.pp-cm-city.is-active .pp-cm-dot {
	fill: var(--pp-cm-active);
	r: 10;
}

.pp-cm-city.is-active .pp-cm-ring {
	fill: none;
	stroke: var(--pp-cm-active);
	stroke-width: 3;
}

.pp-cm-city.is-active .pp-cm-label {
	font-size: 25px;
	font-weight: 700;
}

.pp-coverage-map .pp-cm-caption {
	margin-top: var(--wp--preset--spacing--medium);
	font-size: var(--wp--preset--font-size--x-small);
	line-height: 1.7;
	color: var(--wp--preset--color--secondary);
	/* WP centres figcaptions; a wrapping city list reads better flush left. */
	text-align: left;
}

.pp-cm-caption-label {
	font-family: var(--wp--preset--font-family--condensed);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--main);
}

.pp-dark .pp-cm-caption-label { color: var(--wp--preset--color--base); }

/* Ground-specific halo colours. */
.has-tertiary-background-color .pp-coverage-map,
.pp-city-services .pp-coverage-map {
	--pp-cm-bg: var(--wp--preset--color--tertiary);
}

.pp-dark .pp-coverage-map {
	--pp-cm-bg: #0f1b2c;
	--pp-cm-line: var(--pp-hairline);
	--pp-cm-name: var(--wp--preset--color--base);
	--pp-cm-meta: #9fb0c4;
	--pp-cm-dot: #4cc3f0;
	--pp-cm-ring: rgba(76, 195, 240, 0.16);
	--pp-cm-active: #f5a623;
}

.pp-dark .pp-cm-stop-1 { stop-color: #29abe2; stop-opacity: 0.30; }
.pp-dark .pp-cm-stop-2 { stop-color: #1f7fb0; stop-opacity: 0.15; }

.pp-dark .pp-cm-caption { color: #9fb0c4; }

.pp-dark .pp-cm-city:hover .pp-cm-label,
.pp-dark .pp-cm-city:focus-visible .pp-cm-label { fill: #4cc3f0; }

/* Mobile: the viewBox scales down hard, so bump type in user units to keep it
   readable, and drop the highway labels rather than let them collide. */
@media (max-width: 781px) {
	.pp-cm-label { font-size: 32px; }
	.pp-cm-city.is-hq .pp-cm-label { font-size: 34px; }
	.pp-cm-city.is-active .pp-cm-label { font-size: 36px; }
	.pp-cm-sub { font-size: 24px; }
	.pp-cm-road-label { display: none; }
	.pp-cm-dot { r: 9; }

	/* At the larger mobile type size Creswell's label runs into Pleasant Hill's.
	   Flip it to the left of its own marker instead. */
	.pp-cm-c-creswell .pp-cm-label,
	.pp-cm-c-creswell .pp-cm-sub {
		text-anchor: end;
		transform: translateX(-32px);
	}
}

/* ---------------------------------------------------------------------------
   CTA form card.

   A light island inside a dark cover block. Cover context sets light text, so
   anything in here WITHOUT an explicit colour class inherits white and becomes
   invisible — that hit the card heading and Gravity Forms' own "indicates
   required fields" legend. `:where()` carries zero specificity, so GF's own
   label colours (set via classes) still win where it sets them.
--------------------------------------------------------------------------- */

.pp-cta-form-card {
	color: var(--wp--preset--color--main);
	box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18), 0 24px 60px -20px rgba(0, 0, 0, 0.55);
}

.pp-cta-form-card :where(h1, h2, h3, h4, p, li, legend, label, .gform_required_legend) {
	color: inherit;
}

.pp-cta-form-card .gform_required_legend {
	color: var(--wp--preset--color--secondary);
	font-size: var(--wp--preset--font-size--x-small);
}

/* The card supplies its own padding; GF's wrapper margins would double it. */
.pp-cta-form-card .pp-gform .gform_wrapper {
	margin-top: 0;
	margin-bottom: 0;
}

/* GF sizes textareas through `block-size: var(--gf-ctrl-textarea-height)`, so a
   plain `height` declaration loses. Setting the custom property is the supported
   lever and needs no !important. The default made this row 192px tall, which was
   the single biggest contributor to the card's depth and left a large dead gap
   under the half-width City select beside it. The field asks for one sentence. */
/* Belt and braces alongside the field's `size` setting. GF constrains textarea
   height with min-block-size on a 3-class selector, so this needs comparable
   specificity — a plain `min-height` on .pp-gform textarea loses. */
.pp-cta-form-card .pp-gform .gfield textarea {
	min-block-size: 7rem;
}

/* GF renders field descriptions with a bordered surface, which in this wide card
   made the consent note read as another empty input. */
.pp-herobar-form .gfield_description,
.pp-cta-form-card .pp-gform .gfield_description {
	border: 0;
	padding-left: 0;
	padding-right: 0;
	background: transparent;
}

/* Sit the submit button beside the consent checkbox instead of below it, which
   recovers another row on desktop. */
@media (min-width: 782px) {
	.pp-cta-form-card .pp-gform .gform_footer {
		margin-top: 0;
		padding-top: 0;
	}
}

/* ---------------------------------------------------------------------------
   TRUST SIGNALS: live status chip, Google rating badge, sticky call bar.

   The status chip is rendered server-side and corrected by assets/trust.js, so
   it is right with JS off and cannot be frozen by a page cache. Both chips sit
   in the dark header, so their colours are tuned for the navy ground.
--------------------------------------------------------------------------- */

.pp-status {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.375rem 0.875rem;
	border: 1px solid var(--wp--preset--color--border-dark);
	border-radius: 999px;
	line-height: 1.25;
	white-space: nowrap;
}

.pp-status-dot {
	flex: none;
	width: 0.5rem;
	height: 0.5rem;
	border-radius: 50%;
	/* Amber reads as "not right now" without alarming; 9:1 on the navy ground. */
	background-color: var(--wp--preset--color--highlight);
}

.pp-status.is-open .pp-status-dot {
	background-color: #3dd68c;
	box-shadow: 0 0 0 3px rgba(61, 214, 140, 0.22);
}

/* The dot is never the only signal — the label says it in words — so the pulse
   is decoration and is dropped under reduced motion. */
@media (prefers-reduced-motion: no-preference) {
	.pp-status.is-open .pp-status-dot {
		animation: pp-status-pulse 2.4s ease-in-out infinite;
	}
}

@keyframes pp-status-pulse {
	0%, 100% { box-shadow: 0 0 0 3px rgba(61, 214, 140, 0.22); }
	50%      { box-shadow: 0 0 0 6px rgba(61, 214, 140, 0.05); }
}

.pp-status-text {
	display: flex;
	flex-direction: column;
}

.pp-status-label {
	font-family: var(--wp--preset--font-family--condensed);
	font-size: var(--wp--preset--font-size--x-small);
	font-weight: 700;
	color: var(--wp--preset--color--base);
}

.pp-status-detail {
	font-size: 0.6875rem;
	color: var(--wp--preset--color--main-accent);
}

/* --- Google rating badge --- */

.pp-ratingbadge {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	text-decoration: none;
	white-space: nowrap;
}

.pp-ratingbadge-stars {
	color: var(--wp--preset--color--highlight);
	font-size: 0.8125rem;
	letter-spacing: 0.04em;
}

.pp-ratingbadge-text {
	display: flex;
	flex-direction: column;
	line-height: 1.2;
}

.pp-ratingbadge-text strong {
	font-family: var(--wp--preset--font-family--condensed);
	font-size: var(--wp--preset--font-size--x-small);
	color: var(--wp--preset--color--base);
}

.pp-ratingbadge-count {
	font-size: 0.6875rem;
	color: var(--wp--preset--color--main-accent);
}

.pp-ratingbadge:hover .pp-ratingbadge-count,
.pp-ratingbadge:focus-visible .pp-ratingbadge-count {
	text-decoration: underline;
}

/* --- Sticky mobile call bar ---------------------------------------------------
   Hidden above the mobile-nav breakpoint. Below it, the header is static and
   scrolls away, so this is the only persistent path to the phone. Body padding
   reserves exactly the bar's height so it can never cover page content or a
   form's submit button — keep the two driven by the same custom property.
--------------------------------------------------------------------------- */

.pp-callbar {
	display: none;
}

@media (max-width: 899px) {
	:root {
		--pp-callbar-h: 6rem;
	}

	.pp-callbar {
		position: fixed;
		z-index: 100;
		right: 0;
		bottom: 0;
		left: 0;
		display: flex;
		flex-direction: column;
		gap: 0.4rem;
		padding: 0.6rem var(--wp--preset--spacing--small)
			calc(0.6rem + env(safe-area-inset-bottom, 0px));
		background-color: var(--wp--preset--color--main);
		background-image: linear-gradient(100deg, #0b1220 0%, #16294a 62%, #0d1726 100%);
		border-top: 1px solid var(--wp--preset--color--border-dark);
		box-shadow: 0 -6px 24px -8px rgba(0, 0, 0, 0.55);
	}

	body {
		padding-bottom: calc(var(--pp-callbar-h) + env(safe-area-inset-bottom, 0px));
	}

	.pp-callbar-note {
		margin: 0;
		font-family: var(--wp--preset--font-family--condensed);
		font-size: 0.6875rem;
		letter-spacing: 0.04em;
		text-transform: uppercase;
		text-align: center;
		color: var(--wp--preset--color--main-accent);
	}

	.pp-callbar-actions {
		display: flex;
		gap: 0.5rem;
	}

	.pp-callbar-btn {
		flex: 1 1 0;
		padding: 0.7rem 0.5rem;
		border-radius: 8px;
		font-weight: 700;
		font-size: var(--wp--preset--font-size--small);
		text-align: center;
		text-decoration: none;
		/* 44px minimum touch target. */
		min-height: 44px;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	/* primary-deep, not primary-alt: white on #1678A2 is 4.95:1 and passes AA. */
	.pp-callbar-btn--call {
		flex: 1.45 1 0;
		background-color: var(--wp--preset--color--primary-deep);
		color: var(--wp--preset--color--base);
	}

	.pp-callbar-btn--quote {
		background-color: transparent;
		border: 1px solid var(--wp--preset--color--main-accent);
		color: var(--wp--preset--color--base);
	}
}

@media (max-width: 359px) {
	/* Drop the number on the narrowest phones so the label never truncates. */
	.pp-callbar-btn--call {
		font-size: var(--wp--preset--font-size--x-small);
	}
}

@media print {
	.pp-callbar {
		display: none;
	}
}

/* --- Header utility strip ----------------------------------------------------
   The rating badge and status chip need real prominence, but the main header row
   is already logo + nav + phone and `alignwide` caps it at 1260px, so adding
   them there forced a second row (165px) and broke the mega-panel offset. A slim
   strip above the header gives them room without competing for that width.

   Desktop only: below the nav breakpoint the sticky call bar carries the status,
   and the header collapses to one row.
--------------------------------------------------------------------------- */

.pp-utilitybar {
	gap: var(--wp--preset--spacing--medium);
	padding-bottom: 0.4rem;
	margin-bottom: 0.4rem;
	border-bottom: 1px solid var(--wp--preset--color--border-dark);
}

/* The strip supplies its own separation, so the header's generous top padding
   (spacing-small, ~30px) would otherwise stack on top of it and push the header
   past 160px. Only trimmed where the strip is actually shown. */
@media (min-width: 1024px) {
	.site-header > .wp-block-group:has(> .pp-utilitybar) {
		padding-top: 0.5rem !important;
	}
}

/* In the strip the chips run on ONE line — stacked label/detail made the strip
   56px tall, which is a banner, not a utility row. */
.pp-utilitybar .pp-status-text,
.pp-utilitybar .pp-ratingbadge-text {
	flex-direction: row;
	align-items: baseline;
	gap: 0.4rem;
}

.pp-utilitybar .pp-status {
	padding: 0.2rem 0.7rem;
}

.pp-utilitybar .pp-status-detail,
.pp-utilitybar .pp-ratingbadge-count {
	font-size: 0.75rem;
}

@media (max-width: 1023px) {
	/* WordPress emits a generated flex-layout class for this group
	   (.wp-container-core-group-is-layout-*) with `display:flex` at the same
	   single-class specificity, and prints it AFTER the theme stylesheet — so a
	   bare `.pp-utilitybar` loses on source order. Verified with a fresh browser
	   profile: it was not a caching artefact. */
	.site-header .pp-utilitybar {
		display: none !important;
	}
}

/* ---------------------------------------------------------------------------
   LIGHT SECTION TEXTURE

   The dark sections carry the animated pipe schematic; the light ones had
   nothing, so they read as bare white slabs between them. This gives them a
   faint blueprint grid — apt for a trade that lives on permits and plans — plus
   a soft brand wash so the ground is not flat.

   Applied via `background-image` rather than a ::before, deliberately: a
   positioned pseudo-element would introduce a stacking context over section
   content, and layered backgrounds avoid that entirely.

   Scoped to TOP-LEVEL section wrappers only (direct children of the post
   content). Cards also use `has-base-background-color`, and texturing those
   would turn every card into graph paper. Dark sections use `main` so they are
   not matched, and covers are `wp-block-cover`, not `wp-block-group`.
--------------------------------------------------------------------------- */

.wp-block-post-content > .wp-block-group.alignfull.has-base-background-color,
.wp-block-post-content > .wp-block-group.alignfull.has-tertiary-background-color {
	/* CONTRAST BUDGET — do not raise these opacities without re-checking.
	   Where the grid line and the wash overlap they stack, and that worst-case
	   ground is what small text sits on. At 0.045/0.07 it pushed
	   primary-deep (#1678A2, used for eyebrows) to 4.39:1, under the 4.5:1 AA
	   floor. At 0.035/0.04 the worst case is 4.57:1 for primary-deep and
	   5.03:1 for secondary. */
	background-image:
		linear-gradient(to right, rgba(27, 117, 187, 0.035) 1px, transparent 1px),
		linear-gradient(to bottom, rgba(27, 117, 187, 0.035) 1px, transparent 1px),
		radial-gradient(ellipse 70% 60% at 100% 0%, rgba(41, 171, 226, 0.04), transparent 70%);
	background-size: 34px 34px, 34px 34px, 100% 100%;
	background-repeat: repeat, repeat, no-repeat;
}

/* Alternate the wash side so consecutive light sections do not look identical. */
.wp-block-post-content > .wp-block-group.alignfull.has-tertiary-background-color {
	/* Slightly lighter than the white ground's: tertiary already starts darker, so
	   the same opacities would eat more of the contrast budget. */
	background-image:
		linear-gradient(to right, rgba(27, 117, 187, 0.03) 1px, transparent 1px),
		linear-gradient(to bottom, rgba(27, 117, 187, 0.03) 1px, transparent 1px),
		radial-gradient(ellipse 70% 60% at 0% 100%, rgba(41, 171, 226, 0.03), transparent 70%);
	background-size: 34px 34px, 34px 34px, 100% 100%;
	background-repeat: repeat, repeat, no-repeat;
}

/* Cards sitting on the textured ground need an opaque fill so the grid does not
   show through them and muddy the type. */
.wp-block-post-content > .wp-block-group.alignfull.has-base-background-color .pp-card-major,
.wp-block-post-content > .wp-block-group.alignfull.has-base-background-color .pp-card-minor,
.wp-block-post-content > .wp-block-group.alignfull.has-tertiary-background-color .pp-card-major,
.wp-block-post-content > .wp-block-group.alignfull.has-tertiary-background-color .pp-card-minor,
.wp-block-post-content > .wp-block-group.alignfull .pp-work-card,
.wp-block-post-content > .wp-block-group.alignfull .pp-review-card {
	background-color: var(--wp--preset--color--base);
}

/* --- Service icon cards -------------------------------------------------------
   Icons rather than photographs for the secondary services — see the reasoning
   in inc/service-icons.php. Sized to sit above the card heading, and themed by
   `color` alone because the SVG geometry is all currentColor.
--------------------------------------------------------------------------- */

/* The media panel: same 3:2 footprint the photo cards use, so an icon card and a
   photo card have an identical silhouette in a mixed grid. Carries the blueprint
   grid at a stronger opacity than the page sections — there is no text on it, so
   it has no contrast budget to respect. */
.pp-svcmedia {
	display: flex;
	align-items: center;
	justify-content: center;
	aspect-ratio: 3 / 2;
	background-color: var(--wp--preset--color--primary-accent);
	background-image:
		linear-gradient(to right, rgba(27, 117, 187, 0.09) 1px, transparent 1px),
		linear-gradient(to bottom, rgba(27, 117, 187, 0.09) 1px, transparent 1px),
		radial-gradient(ellipse 60% 60% at 50% 40%, rgba(255, 255, 255, 0.75), transparent 75%);
	background-size: 22px 22px, 22px 22px, 100% 100%;
	background-repeat: repeat, repeat, no-repeat;
	border-bottom: 1px solid var(--wp--preset--color--border-light);
}

.pp-svcicon {
	display: block;
	width: 5rem;
	height: 5rem;
	/* Thinner stroke at this size so the icon reads as a drawing, not a logo. */
	stroke-width: 1.5;
	color: var(--wp--preset--color--primary-deep);
	transition: color 0.2s ease, transform 0.2s ease;
}

.pp-svcicon svg {
	display: block;
	width: 100%;
	height: 100%;
	/* Stroke weight lives here, inherited from .pp-svcicon, so one declaration
	   covers every size the icon is used at. */
	stroke-width: inherit;
	vector-effect: non-scaling-stroke;
}

.pp-svccard:hover .pp-svcicon {
	color: var(--wp--preset--color--primary);
	transform: scale(1.08);
}

@media (prefers-reduced-motion: reduce) {
	.pp-svcicon,
	.pp-svccard:hover .pp-svcicon {
		transition: none;
		transform: none;
	}
}

/* On the dark ground the deep blue loses too much against navy. */
.pp-dark .pp-svcicon {
	color: var(--wp--preset--color--primary-alt);
}

.pp-dark .pp-svcmedia {
	background-color: #12233a;
	border-bottom-color: var(--pp-hairline);
}

/* --- Small brand text on light grounds ---------------------------------------
   `primary-deep` (#1678A2) is sized for button FILLS, where white sits on it at
   4.95:1. As small text it is the other way round, and it was already marginal
   before the section texture existed: 4.57:1 on white and 4.29:1 on the tinted
   ground once the grid and wash are counted — a fail. #136584 keeps the same
   hue and reads at 6.0:1 and 5.7:1 respectively.

   Two classes, so this beats the single-class colour rule WordPress generates.
   Dark grounds are already handled by the `.pp-dark .pp-eyebrow` override.
--------------------------------------------------------------------------- */

.pp-eyebrow.has-primary-deep-color {
	/* !important is required, not sloppiness: WordPress emits its preset colour
	   classes as `.has-primary-deep-color { color: … !important }`, so nothing
	   without it can win regardless of specificity. */
	color: #136584 !important;
}

/* --- Job-site video ----------------------------------------------------------
   The one usable clip in the client library is portrait, so it is framed in a
   narrow column rather than stretched. Capped in height so a vertical video
   cannot run away with the section on a wide screen.
--------------------------------------------------------------------------- */

.pp-workvideo {
	margin: 0;
}

.pp-workvideo video {
	display: block;
	width: 100%;
	max-height: 32rem;
	border-radius: 12px;
	background-color: #000;
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4), 0 18px 48px -18px rgba(0, 0, 0, 0.7);
}

/* ---------------------------------------------------------------------------
   COUPON CARDS (offers band)

   Previously two outlined boxes on a flat ground with uneven heights. The card
   now reads as a coupon: raised surface, a coloured top rule, a dashed
   perforation above the CTA, and the button pushed to the bottom so both cards
   align regardless of copy length.
--------------------------------------------------------------------------- */

.pp-coupon {
	position: relative;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	background-color: var(--pp-surface, #16202e);
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45), 0 18px 44px -20px rgba(0, 0, 0, 0.8);
	transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}

/* Coloured rule across the top edge — the coupon's "ticket" cue. */
.pp-coupon::before {
	content: "";
	position: absolute;
	inset-block-start: 0;
	inset-inline: 0;
	block-size: 4px;
}

.pp-coupon--offer::before {
	background: linear-gradient(90deg, var(--wp--preset--color--highlight) 0%, #ffcd6b 100%);
}

.pp-coupon--guarantee::before {
	background: linear-gradient(90deg, var(--wp--preset--color--primary-alt) 0%, var(--wp--preset--color--primary-deep) 100%);
}

.pp-coupon:hover {
	transform: translateY(-3px);
	border-color: var(--wp--preset--color--primary-alt) !important;
	box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5), 0 26px 56px -20px rgba(41, 171, 226, 0.35);
}

/* The figure is the reason the card exists — let it dominate. */
.pp-coupon-figure {
	line-height: 1.05;
	letter-spacing: -0.02em;
}

.pp-coupon-kicker {
	text-transform: uppercase;
	letter-spacing: 0.08em;
	font-size: var(--wp--preset--font-size--x-small);
}

/* Perforation. Pushed down by auto margin so the CTA sits on the bottom edge of
   every card in the row, whatever the copy above it does. */
.pp-coupon-perf.wp-block-separator {
	width: auto;
	margin: auto calc(var(--wp--preset--spacing--large) * -1) var(--wp--preset--spacing--small);
	border: 0;
	border-top: 2px dashed var(--pp-hairline, #2a3547);
	opacity: 1;
}

.pp-coupon-fine {
	margin-top: 0.5rem;
	opacity: 0.85;
}

/* Secondary on the navy surface is too dim for fine print; lift it. */
.pp-dark .pp-coupon-fine,
.pp-coupon .pp-coupon-fine {
	color: #9fb0c4 !important;
}

@media (prefers-reduced-motion: reduce) {
	.pp-coupon,
	.pp-coupon:hover {
		transition: none;
		transform: none;
	}
}

/* --- Wordmark --------------------------------------------------------------
   The header wordmark is a link, so Ollie's body link styling underlines it.
   It is a logo lockup, not prose — underlining it makes the brand read as a
   sentence. The logo mark beside it links to the same place, so nothing is lost
   by dropping the affordance here; :focus-visible keeps it keyboard-reachable.
--------------------------------------------------------------------------- */

.pp-wordmark a,
.site-header .pp-wordmark a {
	text-decoration: none;
}

.pp-wordmark a:focus-visible {
	outline: 2px solid var(--wp--preset--color--primary-alt);
	outline-offset: 3px;
}

/* The logo image is also a link; make sure it never picks up a rule either. */
.site-header .wp-block-image a {
	text-decoration: none;
}

/* --- Touch target sizes ------------------------------------------------------
   WCAG 2.5.8 asks for 24x24 CSS px minimum; 44px is the comfortable target on a
   phone. Inline links inside prose are explicitly exempt and are left alone —
   enlarging those would wreck the line rhythm for no benefit. These are the
   standalone CONTROLS, which are not exempt and were genuinely small:
   lightbox triggers at 20x20, social icons at 24x24, the nav toggle at 32x32.
--------------------------------------------------------------------------- */

@media (max-width: 1023px) {
	/* Padding alone only reached 36px — the button carries its own sizing — so
	   set the box explicitly and centre the icon inside it. */
	.site-header .wp-block-navigation__responsive-container-open {
		min-width: 44px;
		min-height: 44px;
		display: flex;
		align-items: center;
		justify-content: center;
	}
}

.wp-block-image .lightbox-trigger {
	min-width: 44px;
	min-height: 44px;
	/* The icon itself stays its original size; only the target grows. */
	display: flex;
	align-items: center;
	justify-content: center;
}

.wp-block-social-link .wp-block-social-link-anchor {
	min-width: 44px;
	min-height: 44px;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* ---------------------------------------------------------------------------
   DIRECT ANSWER PANEL

   The first thing below the hero on 25 pages, so its treatment sets the tone for
   everything under the fold. It was a flat grey slab with one accent edge,
   floating in a large white gap — the section carried it, rather than the other
   way round.

   Restyled here in CSS ONLY, deliberately: the markup is generated by
   pp_direct_answer() into page content, and changing it would mean rebuilding
   every page including the three that have no builder. A visual pass needs none
   of that risk.
--------------------------------------------------------------------------- */

.pp-direct-answer {
	padding-top: var(--wp--preset--spacing--large) !important;
	padding-bottom: var(--wp--preset--spacing--large) !important;
}

.pp-direct-answer > .wp-block-group {
	position: relative;
	/* The wrapper is alignwide with a constrained layout, which centres its child.
	   Every other section on the page starts at the wide column's left edge, so a
	   centred panel sat ~260px inboard and read as a layout bug. Keep the readable
	   card width, but anchor it to the same left edge as the hero and the section
	   headings above and below it.

	   WordPress emits the centring as `margin-left/right: auto !important` on the
	   constrained-layout container, so matching !important is required here, not
	   laziness. */
	margin-inline-start: 0 !important;
	margin-inline-end: auto !important;
	/* Reads as a raised card on the textured ground rather than a sunken slab. */
	background-color: var(--wp--preset--color--base) !important;
	border: 1px solid var(--wp--preset--color--border-light);
	border-left: 0 !important;
	border-radius: 12px;
	box-shadow: 0 1px 2px rgba(13, 21, 34, 0.05), 0 14px 36px -18px rgba(13, 21, 34, 0.22);
	padding-left: calc(var(--wp--preset--spacing--large) + 6px) !important;
	padding-right: var(--wp--preset--spacing--large) !important;
	overflow: hidden;
}

/* Accent spine — a gradient rather than the flat 4px rule, matching the coupon
   and stat cards so the whole site shares one vocabulary. */
.pp-direct-answer > .wp-block-group::before {
	content: "";
	position: absolute;
	inset-block: 0;
	inset-inline-start: 0;
	inline-size: 6px;
	background: linear-gradient(180deg, var(--wp--preset--color--primary-alt) 0%, var(--wp--preset--color--primary-deep) 100%);
}

/* Measure: the panel is alignwide, but a single paragraph at full wide width is
   a long line to track. Cap it where prose stays readable. */
.pp-direct-answer > .wp-block-group > p {
	max-width: 60ch;
	margin: 0;
	line-height: 1.65;
	color: var(--wp--preset--color--main);
}

@media (max-width: 781px) {
	.pp-direct-answer > .wp-block-group {
		padding-left: calc(var(--wp--preset--spacing--medium) + 6px) !important;
		padding-right: var(--wp--preset--spacing--medium) !important;
	}
}

/* On the dark ground the card has to become a raised navy surface, not white. */
.pp-dark .pp-direct-answer > .wp-block-group {
	background-color: var(--pp-surface, #16202e) !important;
	border-color: var(--pp-hairline, #2a3547);
}

.pp-dark .pp-direct-answer > .wp-block-group > p {
	color: var(--wp--preset--color--main-accent);
}

/* ---------------------------------------------------------------------------
   LINK POLICY

   The question was whether to drop underlines entirely or underline on hover.
   Measured against this site's own palette, the answer is not a matter of taste:

     link #1B75BB vs secondary #636A75 (body copy)   1.12:1
     link #1B75BB vs main #0D1522                    3.75:1

   WCAG 1.4.1 allows colour alone to identify a link inside a block of text only
   when link and surrounding text differ by at least 3:1. Nineteen of twenty-five
   body paragraphs on a sampled page use `secondary`, where the ratio is 1.12:1 —
   effectively invisible to anyone who does not perceive that hue difference.
   Removing underlines from prose links would be a real failure, not a nitpick.

   So the rule is by CONTEXT, not a blanket on or off:

     Prose links (inside a paragraph or list in the content) — always underlined.
     Structural links (nav, footer lists, card titles, chips, buttons) — never
     underlined; they are identified by position, not by sitting in a sentence,
     so 1.4.1 does not apply. Underline on hover as an affordance.

   This also fixes a real inconsistency: header and footer links were previously
   split 40 without underlines and 21 with, for no reason anyone could infer.
--------------------------------------------------------------------------- */

/* 1. Prose links — the accessible default. */
.wp-block-post-content p a,
.wp-block-post-content li a,
.wp-block-post-content dd a {
	text-decoration: underline;
	/* Clear of descenders, thin enough not to shout. */
	text-underline-offset: 0.18em;
	text-decoration-thickness: 1px;
}

.wp-block-post-content p a:hover,
.wp-block-post-content li a:hover,
.wp-block-post-content p a:focus-visible {
	text-decoration-thickness: 2px;
}

/* 2. Structural links — position identifies them, so no underline at rest.
      Listed explicitly rather than with a blanket :not() chain so it stays
      readable and each entry is a deliberate decision. */
.site-header a,
.site-footer a,
.wp-block-navigation a,
.wp-block-button__link,
.wp-block-post-content h1 a,
.wp-block-post-content h2 a,
.wp-block-post-content h3 a,
.wp-block-post-content h4 a,
.pp-city-chip a,
.pp-filter a,
.pp-mega-col a,
.pp-card-major a,
.pp-card-minor a,
.pp-svccard a,
.pp-coupon a,
.pp-work-card a,
.pp-review-card a,
.pp-cm-caption a,
.pp-ratingbadge,
.pp-svcgrid a,
/* Styled as buttons but plain anchors, not wp-block-button links, so they were
   picking up the prose underline. */
.pp-callbar-btn {
	text-decoration: none;
}

/* Hover/focus still confirms interactivity for everything above. */
.site-footer a:hover,
.wp-block-navigation a:hover,
.wp-block-post-content h2 a:hover,
.wp-block-post-content h3 a:hover,
.wp-block-post-content h4 a:hover,
.pp-mega-col a:hover,
.pp-card-major a:hover,
.pp-card-minor a:hover,
.pp-svccard a:hover,
.pp-work-card a:hover,
.pp-cm-caption a:hover,
.site-footer a:focus-visible,
.wp-block-navigation a:focus-visible,
.pp-mega-col a:focus-visible,
.pp-cm-caption a:focus-visible {
	text-decoration: underline;
	text-underline-offset: 0.18em;
}

/* Buttons and chips are already shapes; an underline on hover just looks broken. */
.wp-block-button__link:hover,
.pp-city-chip a:hover,
.pp-filter a:hover,
.pp-callbar-btn:hover {
	text-decoration: none;
}

/* ---------------------------------------------------------------------------
   MEGA-COLUMN LINKS ON LIGHT GROUNDS

   `.pp-mega-col` / `.pp-mega-lead` were written for the navy mega panel, so they
   force light ink with !important. The city pages reuse the same markup for
   "Every Service in {City}" on a TERTIARY (light) section, where that ink is
   invisible — measured at 1.08:1 for the lead links and 1.44:1 for the rest,
   against a 4.5:1 requirement. They were effectively unreadable.

   Scoped by ground rather than by unpicking the shared markup: inside a dark
   panel the original colours stand; anywhere light, they invert to the same inks
   the rest of the light UI uses. #136584 is the small-brand-text ink chosen
   earlier for exactly this contrast reason.
--------------------------------------------------------------------------- */

:where(.pp-city-services, .has-tertiary-background-color, .has-base-background-color) .pp-mega-col a {
	color: #136584 !important;
}

:where(.pp-city-services, .has-tertiary-background-color, .has-base-background-color) .pp-mega-col a:hover,
:where(.pp-city-services, .has-tertiary-background-color, .has-base-background-color) .pp-mega-col a:focus-visible {
	color: var(--wp--preset--color--primary-deep) !important;
	text-decoration: underline;
	text-underline-offset: 0.18em;
}

:where(.pp-city-services, .has-tertiary-background-color, .has-base-background-color) .pp-mega-lead a {
	color: var(--wp--preset--color--main) !important;
}

/* The group labels above each column are eyebrows and need the same treatment. */
:where(.pp-city-services, .has-tertiary-background-color, .has-base-background-color) .pp-mega-label {
	color: #136584 !important;
}

/* A dark panel must always win, whatever it is nested inside. */
.pp-dark .pp-mega-col a {
	color: var(--wp--preset--color--main-accent) !important;
}

.pp-dark .pp-mega-lead a {
	color: var(--wp--preset--color--base) !important;
}

/* Uniform card rows on the service-areas landing grid. CSS grid already equalises
   cards WITHIN a row; this equalises them across rows too, so a city whose
   one-liner wraps to three lines does not make its row taller than the others. */
.wp-block-group.alignwide:has(> .pp-card-major) {
	grid-auto-rows: 1fr;
}

/* ---------------------------------------------------------------------------
   PROSE LINK INK ON LIGHT GROUNDS

   `primary` #1B75BB measures 4.87:1 on white but only 4.50:1 on the tertiary
   ground — sitting exactly on the AA line before the blueprint texture is even
   counted, which is why links in tinted sections read as washed out.

   #136584 is the same hue, already in use for small brand text, and gives 6.0:1
   on white and 5.7:1 on tertiary. Applied to content and footer prose links so
   there is one readable link ink across every light section.
--------------------------------------------------------------------------- */

.wp-block-post-content p a,
.wp-block-post-content li a,
.wp-block-post-content dd a {
	color: #136584;
}

.wp-block-post-content p a:hover,
.wp-block-post-content li a:hover,
.wp-block-post-content p a:focus-visible {
	color: var(--wp--preset--color--primary-deep);
}

/* Dark sections keep the bright cyan, which is what reads there.
   Keyed on `has-base-color` as well as `.pp-dark`: several dark bands (the
   response band, for one) set a dark background and white text WITHOUT carrying
   the .pp-dark class, and the first version of this rule missed them — putting
   the dark ink on a navy ground at 2.81:1. White text is a reliable signal that
   the ground underneath is dark. */
.pp-dark p a,
.pp-dark li a,
.has-main-background-color p a,
.has-main-background-color li a,
.has-base-color p a,
.has-base-color li a,
.wp-block-cover p a {
	color: var(--wp--preset--color--primary-alt) !important;
}

/* A block that sets its own link colour (elements.link.color) knows something the
   ground-based rules above do not — the emergency band, for instance, paints a
   brand GRADIENT and sets white links, which a background-colour check cannot
   see. Overriding it there dropped that link to 2.62:1. Give the block back its
   own choice. */
.has-link-color p a,
.has-link-color li a {
	color: var(--wp--style--color--link) !important;
}

/* The eyebrow label inside the short-answer panel. Sits tight to the paragraph
   so the two read as one unit rather than a heading and a stray block of text. */
.pp-direct-answer .pp-da-label {
	margin-bottom: 0.35rem;
}

/* Short-answer panel, desktop: a label column beside the answer rather than a
   narrow card marooned in a wide section. The label reads as a margin note, the
   answer keeps a comfortable measure, and the panel finally occupies the width
   the rest of the page uses. CSS-only — the markup is generated into 25 pages. */
@media (min-width: 900px) {
	.pp-direct-answer > .wp-block-group {
		display: grid;
		grid-template-columns: 11rem minmax(0, 1fr);
		column-gap: var(--wp--preset--spacing--large);
		align-items: start;
		/* Override the constrained layout's content-size cap so the panel spans
		   the wide column like every other section. */
		max-width: none !important;
	}

	.pp-direct-answer .pp-da-label {
		grid-column: 1;
		margin: 0.35rem 0 0;
		line-height: 1.4;
	}

	.pp-direct-answer > .wp-block-group > p:not(.pp-da-label) {
		grid-column: 2;
		max-width: 68ch;
	}
}

/* ---------------------------------------------------------------------------
   STICKY CONDENSING HEADER  (≥900px)

   Measured problem: the header is `position: static`, so above the call bar's
   900px breakpoint there is NO persistent way to call once a visitor scrolls —
   and 901-1023px had neither the bar nor a sticky header at all.
   Normalised 2026-09-03: the boundary is now a clean 899/900 pair. It used to be
   max-width:900 against min-width:901, so at exactly 900px a visitor got the
   mobile call bar together with the desktop short-answer grid and hero-bar form,
   and no sticky header.

   Chosen over a flyout or floating button deliberately. The phone number and the
   whole nav already live in the header; making it stick solves persistence with
   no new interaction pattern, nothing overlapping content, and none of the
   spammy feel a floating tab carries on a contractor site. It also keeps one
   idea per breakpoint: call bar below 900px, sticky header above it.

   It condenses on scroll because a 148px sticky header eats too much viewport —
   the utility strip drops away and the padding tightens to about 70px.
--------------------------------------------------------------------------- */

@media (min-width: 900px) {
	.site-header {
		position: sticky;
		top: 0;
		/* Above page content, below the mega panel it opens. */
		z-index: 90;
		transition: box-shadow 0.2s ease;
	}

	.site-header.is-condensed {
		box-shadow: 0 6px 24px -12px rgba(0, 0, 0, 0.6);
	}

	/* Collapse the utility strip once scrolled — the rating and hours are
	   orientation, not something needed at every scroll position. */
	.site-header.is-condensed .pp-utilitybar {
		display: none !important;
	}

	.site-header.is-condensed > .wp-block-group {
		padding-top: 0.5rem !important;
		padding-bottom: 0.5rem !important;
	}

	@media (prefers-reduced-motion: reduce) {
		.site-header {
			transition: none;
		}
	}
}

/* Anchor links must not land under the sticky header. */
@media (min-width: 900px) {
	:target {
		scroll-margin-top: calc(var(--pp-header-height, 148px) + 1rem);
	}
}

/* Footer bottom bar: full-bleed rule, content held to the wide column, and the
   social icons relocated here from the brand column where they sat orphaned
   under the address. Right-aligned legal text so the row reads social | legal. */
.pp-footer-bottom .pp-footer-legal {
	text-align: right;
}

.pp-footer-bottom .pp-footer-legal p {
	margin: 0;
}

@media (max-width: 599px) {
	.pp-footer-bottom .pp-footer-legal {
		text-align: left;
	}
}

/* ---------------------------------------------------------------------------
   HERO CONTACT BAR

   A single row below the hero. The four fields are 3/12 spans, so Gravity Forms
   already lays them out four-across on desktop; the work here is compacting the
   row so it reads as a bar rather than a form section — and putting the consent
   and the submit on one line beneath instead of two stacked rows.
--------------------------------------------------------------------------- */

.pp-herobar-lead a {
	color: var(--wp--preset--color--base) !important;
	text-decoration: underline;
	text-underline-offset: 0.18em;
}

.pp-herobar-card {
	box-shadow: 0 2px 6px rgba(13, 21, 34, 0.14), 0 18px 44px -22px rgba(13, 21, 34, 0.4);
}

/* Tighter than the page forms — a bar should not breathe like a section. */
.pp-herobar-form .gform_wrapper {
	margin: 0;
}

.pp-herobar-form .gfield {
	--gf-form-gap-y: 0.75rem;
}

.pp-herobar-form .gform_footer {
	margin-top: 0.5rem;
	padding-top: 0;
}

/* 1024, not 900. Screenshotted at every boundary: at exactly 900px the four
   fields plus a 13rem button do not fit, so the "What do you need?" label wraps
   to two lines and drops its select a row below the other three inputs — the bar
   looked more broken at 900 than the stacked layout did at 899. The stacked
   version below this width is genuinely good: four fields across one row, submit
   beneath, nothing cramped. 1024 is where the beside-layout actually fits, and it
   is already a boundary this file uses, so the scale stays as it was. */
@media (min-width: 1024px) {
	/* Put the submit beside the fields rather than under them, so the block reads
	   as a bar and the right-hand third is not dead space.

	   Flex on the FORM, not inline-block on its parts: GF sets its own display on
	   .gform-body and .gform_footer, and the inline-block override took on the body
	   but not the footer — which shrank the field row and left the very gap it was
	   meant to close. */
	.pp-herobar-form form {
		display: flex;
		flex-wrap: nowrap;
		/* start, not center. The footer is a flex sibling of the WHOLE body —
		   the field row and the consent row beneath it — so centring dropped the
		   button about 28px below the inputs, straddling the two rows and reading
		   as though it had come loose from the form. Aligning to the top and
		   pushing the button down by the height of one field label puts it on the
		   input row, where it belongs. */
		align-items: flex-start;
		gap: var(--wp--preset--spacing--medium);
	}

	.pp-herobar-form .gform-body {
		flex: 1 1 auto;
		min-width: 0;
	}

	.pp-herobar-form .gform_footer {
		flex: 0 0 13rem;
		/* Measured: a GF label block is 28px here (label line plus its margin).
		   This is the offset that lines the button up with the inputs rather than
		   with the labels above them. If the label font size or spacing changes,
		   this needs re-measuring — it is a layout constant, not a guess. */
		margin: 1.75rem 0 0;
		padding: 0;
	}

	.pp-herobar-form .gform_footer .gform_button {
		width: 100%;
	}
}

/* The consent field's description repeats what the checkbox already says and, in
   a one-row bar, renders as a bordered block that reads like another empty input.
   Hidden visually only — the description must stay in the FORM DATA, because a
   consent field without one makes GF_Field_Consent read an undefined array key on
   every render. */
.pp-herobar-form .gfield--type-consent .gfield_description {
	display: none;
}

/* The required-fields legend is noise above a four-field bar. */
.pp-herobar-form .gform_required_legend {
	display: none;
}

/* Desktop and tablet only.
   Fully stacked, this bar is 849px on a 390px screen — a whole screen of form
   between the hero and the first line of content, which is worse than no bar at
   all. Mobile already has the sticky call bar carrying Call and Get an Estimate,
   so showing both is redundant as well as long. One conversion path per
   breakpoint, same rule as the sticky header. */
@media (max-width: 781px) {
	.pp-herobar {
		display: none;
	}
}

/* --------------------------------------------------------------------------
   Solo service review
   Four of the seven services we hold matching reviews for have exactly one.
   A single card stranded in a three-column grid reads as unfinished, so one
   review becomes a pull-quote instead — deliberate rather than sparse.
   Sits on the light `tertiary` ground, so ink is `main`, not `base`.
   -------------------------------------------------------------------------- */
.pp-review-solo {
	border-left: 3px solid var(--wp--preset--color--primary-deep);
	padding-left: clamp(1rem, 3vw, 2rem);
}

/* Measure lives on the text, not the container: the wrapper is alignwide, and
   the theme's alignwide max-width beats anything set here, so constraining the
   container silently did nothing and left ~95ch lines. */
.pp-review-solo-text {
	max-width: 46ch;
	font-size: clamp(1.25rem, 2.2vw, 1.75rem);
	font-weight: 600;
	line-height: 1.32;
	letter-spacing: -0.01em;
	text-wrap: pretty;
}
