/**
 * Lead form controls.
 *
 * Every value here is a theme.json token or a design-pass custom property from
 * assets/css/app.css. Inputs are 16px, which is also what stops iOS Safari
 * zooming the page when a field is focused; the smallest text on any form is
 * `sm`, and `xs` is kept for uppercase labels (TYP-17).
 *
 * Layout is by container query on the form itself, never the viewport: the
 * same form sits in a 22rem hero slot at 1440px and across a whole section on
 * a tablet, and a viewport breakpoint gets one of the two wrong.
 */

.pchs-form {
	display: grid;
	gap: var(--wp--preset--spacing--40);
	container: pchs-form / inline-size;

	/* Mask shapes. The stroke colour is irrelevant: a mask reads alpha only,
	   and the rendered colour comes from a token on the element using it. */
	--pchs-form-icon-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3 2 20.5h20L12 3Z' fill='none' stroke='black' stroke-width='2.2' stroke-linejoin='round'/%3E%3Cpath d='M12 10v4.2' stroke='black' stroke-width='2.2' stroke-linecap='round'/%3E%3Ccircle cx='12' cy='17.4' r='1.2' fill='black'/%3E%3C/svg%3E");
	--pchs-form-icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* ---- field grid ------------------------------------------------------ */

/*
 * Two tracks. Every field takes the full row unless a variant pairs it (see
 * "per-form layouts" at the end), so the default is set at zero specificity:
 * any pairing rule wins without depending on its order in this file.
 */
.pchs-form__fields {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: var(--wp--preset--spacing--40) var(--wp--preset--spacing--30);
}

:where(.pchs-form__fields) > * {
	grid-column: 1 / -1;
}

/*
 * `align-content: start` is the whole fix for something once reported as "the
 * ZIP field is broken". Two fields share a row, one carries a hint, its cell
 * is taller, and the grid stretches the neighbour; without this the auto rows
 * inside the stretched cell shared out the spare height and the input grew.
 */
.pchs-form__field,
.pchs-form__group {
	display: grid;
	align-content: start;
	gap: var(--wp--preset--spacing--20);
	min-width: 0;
}

/* fieldset carries browser defaults that fight the grid. */
.pchs-form__group {
	border: 0;
	margin: 0;
	padding: 0;
	/* The segments lay themselves out by the width they actually have. */
	container: pchs-choice / inline-size;
}

.pchs-form__label,
.pchs-form__legend {
	display: block;
	padding: 0;
	font-size: var(--wp--preset--font-size--sm);
	font-weight: var(--wp--custom--weight--bold);
	line-height: var(--wp--custom--leading--title);
	color: var(--wp--preset--color--navy);
}

.pchs-form__optional {
	font-weight: var(--wp--custom--weight--regular);
	color: var(--wp--preset--color--muted);
}

.pchs-form__hint {
	font-size: var(--wp--preset--font-size--sm);
	line-height: var(--wp--custom--leading--body);
	color: var(--wp--preset--color--muted);
}

/* ---- controls -------------------------------------------------------- */

/*
 * 3rem, the default button height, so a field and a button beside it share a
 * line. The edge is border-cool-2, the same edge the segments and day tiles
 * carry, so every control on a card reads as one set.
 */
.pchs-form__input {
	align-self: start;
	width: 100%;
	min-height: var(--pchs-btn-h);
	box-sizing: border-box;
	margin: 0;
	padding: var(--wp--preset--spacing--20) var(--wp--preset--spacing--40);
	border: 1px solid var(--wp--custom--color--border-cool-2);
	border-radius: var(--wp--custom--radius--xs);
	background: var(--wp--preset--color--base);
	color: var(--wp--preset--color--ink);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--base);
	line-height: var(--wp--custom--leading--title);
}

.pchs-form__textarea {
	min-height: 7rem;
	padding-block: var(--wp--preset--spacing--30);
	line-height: var(--wp--custom--leading--body);
	resize: vertical;
}

.pchs-form__select {
	appearance: none;
	padding-right: var(--wp--preset--spacing--70);
	background-image: linear-gradient(45deg, transparent 50%, var(--wp--preset--color--navy) 50%),
		linear-gradient(135deg, var(--wp--preset--color--navy) 50%, transparent 50%);
	background-position: right 1.25rem top 55%, right 1rem top 55%;
	background-size: 0.3rem 0.3rem, 0.3rem 0.3rem;
	background-repeat: no-repeat;
	cursor: pointer;
}

/* "Choose a project" is the prompt, not an answer, so it reads as one. */
.pchs-form__select:has(option[value=""]:checked) {
	color: var(--wp--custom--color--muted-2);
}

.pchs-form__input:hover {
	border-color: var(--wp--custom--color--navy-muted);
}

/*
 * The ring itself comes from assets/css/app.css, the same one every control on
 * the site gets. All this adds is the field's own edge going green underneath
 * it, which says at a glance which field you are in once the ring is scrolled
 * half out of view.
 */
.pchs-form__input:focus-visible {
	border-color: var(--wp--preset--color--green);
}

.pchs-form__input::placeholder {
	color: var(--wp--custom--color--muted-3);
}

/*
 * The native file control, brought into line with the other inputs (INT-08):
 * its button is drawn from the same tokens as a segment.
 */
.pchs-form__input[type="file"] {
	display: flex;
	align-items: center;
	/* Tight inset so the selector button can be a full 44px+ tap inside the
	   48px field without looking like a chip floating in a tall frame. */
	padding-block: 3px;
	padding-inline: var(--wp--preset--spacing--10);
	color: var(--wp--preset--color--muted);
	font-size: var(--wp--preset--font-size--sm);
	cursor: pointer;
}

.pchs-form__input[type="file"]::file-selector-button,
.pchs-form__input[type="file"]::-webkit-file-upload-button {
	margin-inline-end: var(--wp--preset--spacing--30);
	box-sizing: border-box;
	/* Fill the field: 48px minus the 3px inset and 1px borders on each side. */
	min-height: calc(var(--pchs-btn-h) - 8px);
	padding: var(--wp--preset--spacing--10) var(--wp--preset--spacing--40);
	border: 1px solid var(--wp--custom--color--border-cool-2);
	border-radius: var(--wp--custom--radius--xs);
	background: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--navy);
	font: inherit;
	font-weight: var(--wp--custom--weight--bold);
	cursor: pointer;
}

.pchs-form__input[type="file"]:hover::file-selector-button,
.pchs-form__input[type="file"]:hover::-webkit-file-upload-button {
	border-color: var(--wp--preset--color--green);
}

/*
 * Border and background only. Nothing here animates size or position: a field
 * that grows on focus shunts the rest of a long form down the page.
 */
@media ( prefers-reduced-motion: no-preference ) {

	.pchs-form__input {
		transition: border-color var(--pchs-duration-fast) var(--pchs-ease),
			background-color var(--pchs-duration-fast) var(--pchs-ease);
	}
}

/* ---- segments (real radios) ------------------------------------------- */

/*
 * A segmented control, not a row of pills: equal cells on one line, each the
 * height of an input. Four or more that will not fit go two by two (or three
 * by two for six), never three and one.
 *
 * The two-by-two point is 20rem, not the plan's 22rem: the 26rem hero slot
 * gives the group 21.9rem, and four one-word services fit one row there with
 * the check dropped (below). Two rows there cost 56px of a card that has to
 * stay under 700px beside the hero copy.
 */
.pchs-form__chips {
	display: grid;
	grid-auto-flow: column;
	grid-auto-columns: minmax(0, 1fr);
	gap: var(--wp--preset--spacing--20);
}

.pchs-form__chips[data-count="5"],
.pchs-form__chips[data-count="6"] {
	grid-auto-flow: row;
	grid-template-columns: repeat(3, minmax(0, 1fr));
}

@container pchs-choice (max-width: 20rem) {

	.pchs-form__chips[data-count="4"],
	.pchs-form__chips[data-count="5"],
	.pchs-form__chips[data-count="6"] {
		grid-auto-flow: row;
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/*
 * Three to a row in a phone-width card: each segment takes its own label's
 * width plus an equal share of what is left, so "Full remodel" never breaks
 * inside a third of 292px. Narrower than three can fit, they stack as one
 * list; never two and one.
 */
@container pchs-choice (max-width: 22rem) {

	.pchs-form__chips[data-count="3"] {
		display: flex;
		gap: var(--wp--preset--spacing--20);
	}

	.pchs-form__chips[data-count="3"] .pchs-form__chip {
		flex: 1 1 auto;
	}
}

@container pchs-choice (max-width: 16rem) {

	.pchs-form__chips[data-count="3"] {
		flex-direction: column;
	}
}

/* Budget labels are dollar ranges, twice the length of a service name, and
   "$10,000 to $25,000" wrapped inside a quarter of the 35rem panel. */
@container pchs-choice (max-width: 40rem) {

	.pchs-form__group--budget .pchs-form__chips {
		grid-auto-flow: row;
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

.pchs-form__chip {
	display: flex;
	position: relative;
	min-width: 0;
}

/*
 * The radio is visually hidden but never display:none and never
 * `visibility:hidden`: it has to stay focusable and hit-testable so keyboard
 * and screen-reader users get the native radiogroup behaviour.
 */
.pchs-form__chip-input,
.pchs-form__date-input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: 0;
	padding: 0;
	border: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
}

.pchs-form__chip-label {
	display: flex;
	flex: 1;
	align-items: center;
	justify-content: center;
	gap: var(--wp--preset--spacing--10);
	box-sizing: border-box;
	min-height: var(--pchs-btn-h);
	padding: var(--wp--preset--spacing--10) var(--wp--preset--spacing--20);
	border: 1px solid var(--wp--custom--color--border-cool-2);
	border-radius: var(--wp--custom--radius--sm);
	background: var(--wp--preset--color--base);
	color: var(--wp--preset--color--navy);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: var(--wp--custom--weight--ui);
	line-height: var(--wp--custom--leading--title);
	text-align: center;
	white-space: nowrap;
	cursor: pointer;
}

.pchs-form__chip-label:hover {
	border-color: var(--wp--custom--color--navy-muted);
}

/*
 * Selected: the 2px edge is an inset shadow over the 1px border, so choosing
 * a segment moves nothing. The check says it without colour (WCAG 1.4.1).
 */
.pchs-form__chip-input:checked + .pchs-form__chip-label {
	border-color: var(--wp--preset--color--green);
	background: var(--wp--preset--color--green-tint);
	box-shadow: inset 0 0 0 1px var(--wp--preset--color--green);
	color: var(--wp--preset--color--green-dark);
}

.pchs-form__chip-input:checked + .pchs-form__chip-label::before {
	content: "";
	flex: none;
	width: 1em;
	height: 1em;
	background-color: currentColor;
	-webkit-mask: var(--pchs-form-icon-check) center / contain no-repeat;
	mask: var(--pchs-form-icon-check) center / contain no-repeat;
}

/*
 * Four across in one row leaves no room for the check beside "Windows".
 * The selected segment still differs by more than colour: its edge is twice
 * as thick. Once the group wraps to two-by-two (≤20rem), or is the budget
 * group (always two-by-two below 40rem), put the check back — there is room.
 */
@container pchs-choice (max-width: 26rem) {

	.pchs-form__chips[data-count="4"] .pchs-form__chip-input:checked + .pchs-form__chip-label::before {
		display: none;
	}
}

@container pchs-choice (max-width: 20rem) {

	.pchs-form__chips[data-count="4"] .pchs-form__chip-input:checked + .pchs-form__chip-label::before {
		display: block;
	}
}

.pchs-form__group--budget .pchs-form__chip-input:checked + .pchs-form__chip-label::before {
	display: block;
}

/*
 * The focus ring lives on the label because the input it belongs to is
 * off-screen, so app.css, which only sees the input, cannot reach it. Same
 * two-tone ring, restated rather than reinvented.
 */
.pchs-form__chip-input:focus-visible + .pchs-form__chip-label,
.pchs-form__date-input:focus-visible + .pchs-form__date-label {
	outline: var(--pchs-focus-width) solid var(--pchs-focus-color);
	outline-offset: var(--pchs-focus-gap);
	box-shadow: var(--pchs-focus-halo);
}

.pchs-form__chip-input:checked:focus-visible + .pchs-form__chip-label {
	box-shadow: var(--pchs-focus-halo), inset 0 0 0 1px var(--wp--preset--color--green);
}

@media ( prefers-reduced-motion: no-preference ) {

	.pchs-form__chip-label,
	.pchs-form__date-label {
		transition: border-color var(--pchs-duration-fast) var(--pchs-ease),
			background-color var(--pchs-duration-fast) var(--pchs-ease),
			color var(--pchs-duration-fast) var(--pchs-ease);
	}

	.pchs-form__chip-label:active,
	.pchs-form__date-label:active {
		transform: translateY( 1px );
	}
}

/* ---- the day strip ----------------------------------------------------- */

.pchs-form__date-head {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--20);
}

.pchs-form__date-head .pchs-form__label {
	margin-inline-end: auto;
}

.pchs-form__date-month {
	font-size: var(--wp--preset--font-size--sm);
	line-height: var(--wp--custom--leading--title);
	color: var(--wp--preset--color--muted);
	white-space: nowrap;
}

/* Steppers are for a mouse on a wide screen; a thumb swipes. */
.pchs-form__date-nav {
	display: none;
}

@media (min-width: 48em) {

	/* Laid out before forms.js shows them, only invisible, so the row does
	   not grow or reflow after load and shift the hero copy beside the card. */
	.pchs-form__date-nav {
		display: flex;
		gap: var(--wp--preset--spacing--10);
	}

	.pchs-form__date-nav[hidden] {
		visibility: hidden;
	}
}

.pchs-form__date-step {
	display: grid;
	place-items: center;
	width: var(--pchs-btn-h-sm);
	height: var(--pchs-btn-h-sm);
	padding: 0;
	border: 1px solid var(--wp--custom--color--border-cool-2);
	border-radius: var(--wp--custom--radius--sm);
	background: var(--wp--preset--color--base);
	color: var(--wp--preset--color--navy);
	cursor: pointer;
}

.pchs-form__date-step:hover:not(:disabled) {
	border-color: var(--wp--custom--color--navy-muted);
}

.pchs-form__date-step--prev .pchs-chevron {
	rotate: 90deg;
}

.pchs-form__date-step--next .pchs-chevron {
	rotate: -90deg;
}

/*
 * One row that scrolls sideways inside the card; the page never does. The
 * 4px of padding, taken back by the margin, is room for the focus ring, which
 * a scroll container would otherwise clip. The fade on the right edge says
 * "more this way" and goes once the row is scrolled to its end (forms.js).
 */
.pchs-form__dates {
	display: flex;
	gap: var(--wp--preset--spacing--20);
	margin: -4px;
	padding: 4px;
	overflow-x: auto;
	overscroll-behavior-x: contain;
	scroll-snap-type: x proximity;
	scroll-padding-inline: 4px;
	scrollbar-width: none;
	-webkit-mask-image: linear-gradient(to right, black calc(100% - 2.5rem), transparent);
	mask-image: linear-gradient(to right, black calc(100% - 2.5rem), transparent);
}

.pchs-form__dates::-webkit-scrollbar {
	display: none;
}

.pchs-form__dates[data-at-end] {
	-webkit-mask-image: none;
	mask-image: none;
}

@media ( prefers-reduced-motion: no-preference ) {

	.pchs-form__dates {
		scroll-behavior: smooth;
	}
}

.pchs-form__date {
	display: block;
	flex: none;
	position: relative;
	scroll-snap-align: start;
}

.pchs-form__date-label {
	display: grid;
	place-content: center;
	justify-items: center;
	gap: 2px;
	box-sizing: border-box;
	width: 3.5rem;
	height: 3.5rem;
	border: 1px solid var(--wp--custom--color--border-cool-2);
	border-radius: var(--wp--custom--radius--sm);
	background: var(--wp--preset--color--base);
	color: var(--wp--preset--color--navy);
	cursor: pointer;
}

.pchs-form__date-label:hover {
	border-color: var(--wp--custom--color--navy-muted);
}

.pchs-form__date-dow {
	font-size: var(--wp--preset--font-size--xs);
	font-weight: var(--wp--custom--weight--bold);
	line-height: var(--wp--custom--leading--title);
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}

.pchs-form__date-num {
	font-size: var(--wp--preset--font-size--lg);
	font-weight: var(--wp--custom--weight--heavy);
	line-height: var(--wp--custom--leading--none);
}

.pchs-form__date-label--flexible {
	width: auto;
	padding-inline: var(--wp--preset--spacing--40);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: var(--wp--custom--weight--bold);
	line-height: var(--wp--custom--leading--title);
	white-space: nowrap;
}

/* base on navy measures well above 4.5:1; the day name follows it. */
.pchs-form__date-input:checked + .pchs-form__date-label {
	border-color: var(--wp--preset--color--navy);
	background: var(--wp--preset--color--navy);
	color: var(--wp--preset--color--base);
}

.pchs-form__date-input:checked + .pchs-form__date-label .pchs-form__date-dow {
	color: var(--wp--preset--color--border-cool);
}

.pchs-form__date-note {
	margin: 0;
	font-size: var(--wp--preset--font-size--sm);
	line-height: var(--wp--custom--leading--body);
	color: var(--wp--preset--color--muted);
}

/* ---- errors ------------------------------------------------------------ */

/*
 * The error colours depend on the card painting a light ground under them, and
 * every form card does (.pchs-panel). `danger` measures 6.54:1 on white and
 * 5.72:1 on danger-tint. It is 2.53:1 on navy-deep, so a form ever set straight
 * on the hero photo needs a light error variant, not a different background.
 */
.pchs-form__error {
	display: block;
	font-size: var(--wp--preset--font-size--sm);
	font-weight: var(--wp--custom--weight--ui);
	line-height: var(--wp--custom--leading--body);
	color: var(--wp--preset--color--danger);
}

/*
 * Never colour alone (WCAG 1.4.1). Four things mark an invalid field and three
 * of them survive without colour perception: the message, this icon, the bar
 * on the left, and the doubled field edge. A mask, so the colour is the token.
 */
.pchs-form__error::before {
	content: "";
	display: inline-block;
	width: 1em;
	height: 1em;
	margin-inline-end: 0.3em;
	vertical-align: -0.15em;
	background-color: var(--wp--preset--color--danger);
	-webkit-mask: var(--pchs-form-icon-alert) center / contain no-repeat;
	mask: var(--pchs-form-icon-alert) center / contain no-repeat;
}

.pchs-form__error[hidden] {
	display: none;
}

/*
 * The doubled edge is an inset shadow, not a 2px border, so an invalid field
 * is exactly as tall as a valid one.
 */
.pchs-form__field.is-invalid .pchs-form__input,
.pchs-form__group.is-invalid .pchs-form__input {
	border-color: var(--wp--preset--color--danger);
	box-shadow: inset 0 0 0 1px var(--wp--preset--color--danger);
	background-color: var(--wp--custom--color--danger-tint);
}

/*
 * box-shadow is one property: this rule and the focus ring in app.css both
 * want it, and the later one would silently delete the other. Both matter on
 * the field the error summary just jumped the visitor to.
 */
.pchs-form__field.is-invalid .pchs-form__input:focus-visible,
.pchs-form__group.is-invalid .pchs-form__input:focus-visible {
	border-color: var(--wp--preset--color--danger);
	box-shadow: var(--pchs-focus-halo), inset 0 0 0 1px var(--wp--preset--color--danger);
}

/*
 * The bar on the left. Full-row fields and groups reach out into the gutter by
 * exactly the padding they add, so the label and control stay where they were:
 * an invalid field moves 0px (INT-20). The bar is an inset shadow on that
 * padding.
 *
 * Half-column pairs (Phone|ZIP, Name|Phone, Phone|Email) cannot use the gutter
 * reach: the second cell's negative margin eats the 12px track gap and its bar
 * lands between the two controls. Those keep the bar inside the cell.
 */
.pchs-form__field.is-invalid,
.pchs-form__group.is-invalid {
	box-shadow: inset 3px 0 0 var(--wp--preset--color--danger);
}

.pchs-form__group.is-invalid {
	margin-inline-start: calc(-1 * var(--wp--preset--spacing--30));
	padding-inline-start: var(--wp--preset--spacing--30);
}

.pchs-form__field.is-invalid {
	margin-inline-start: calc(-1 * var(--wp--preset--spacing--30));
	padding-inline-start: var(--wp--preset--spacing--30);
}

/*
 * Cancel only the negative margin for the RIGHT-HAND cell of a pair. Keep the
 * matching padding so the inset bar sits in empty space inside that cell
 * instead of painting over the first letter of the label.
 *
 * The left-hand cell (Phone in Phone|ZIP, Name in Name|Phone, Email in
 * Email|Phone) still reaches into the panel gutter like a full-row field —
 * cancelling both sides was indenting the left control 12px and misaligning
 * it with the field above.
 */
@container pchs-form (min-width: 20rem) {

	.pchs-form--hero .pchs-form__field--zip.is-invalid,
	.pchs-form--booking .pchs-form__field--zip.is-invalid,
	.pchs-form--full .pchs-form__field--zip.is-invalid,
	.pchs-form--contact .pchs-form__field--email.is-invalid {
		margin-inline-start: 0;
	}
}

@container pchs-form (min-width: 26rem) {

	.pchs-form--financing .pchs-form__field--phone.is-invalid,
	.pchs-form--careers .pchs-form__field--phone.is-invalid,
	.pchs-form--credit .pchs-form__field--last_name.is-invalid,
	.pchs-form--credit .pchs-form__field--ssn.is-invalid,
	.pchs-form--credit .pchs-form__field--job_title.is-invalid,
	.pchs-form--credit .pchs-form__field--phone.is-invalid,
	.pchs-form--credit .pchs-form__field--zip.is-invalid {
		margin-inline-start: 0;
	}
}

/* Name|Phone: Phone moves to the right track; ZIP stays alone on the left. */
@container pchs-form (min-width: 30rem) {

	.pchs-form--hero .pchs-form__field--phone.is-invalid,
	.pchs-form--booking .pchs-form__field--phone.is-invalid,
	.pchs-form--full .pchs-form__field--phone.is-invalid {
		margin-inline-start: 0;
	}
}

/*
 * Choice groups already carry the left bar; tint the segment edges too so the
 * error is not colour-on-the-message alone when the group is scrolled partly
 * out of view.
 */
.pchs-form__group.is-invalid .pchs-form__chip-label {
	border-color: var(--wp--custom--color--danger-border);
}

/*
 * Phone|ZIP (and the other half-column pairs) squeeze a long error into ~10rem,
 * which wraps to three lines and makes the row look broken. While either side
 * of the pair is invalid, both tracks go full width so each message stays under
 * its own field but has the whole form to wrap in. Correcting the fields
 * restores the pair (same container queries as the layout rules below).
 */
@container pchs-form (min-width: 20rem) {

	.pchs-form--hero:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--zip.is-invalid ) .pchs-form__field--phone,
	.pchs-form--hero:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--zip.is-invalid ) .pchs-form__field--zip,
	.pchs-form--booking:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--zip.is-invalid ) .pchs-form__field--phone,
	.pchs-form--booking:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--zip.is-invalid ) .pchs-form__field--zip,
	.pchs-form--full:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--zip.is-invalid ) .pchs-form__field--phone,
	.pchs-form--full:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--zip.is-invalid ) .pchs-form__field--zip,
	.pchs-form--contact:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--email.is-invalid ) .pchs-form__field--phone,
	.pchs-form--contact:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--email.is-invalid ) .pchs-form__field--email {
		grid-column: 1 / -1;
	}

	/* Full-row again: put the gutter-reach bar back on the ex-right cell. */
	.pchs-form--hero:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--zip.is-invalid ) .pchs-form__field--zip.is-invalid,
	.pchs-form--booking:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--zip.is-invalid ) .pchs-form__field--zip.is-invalid,
	.pchs-form--full:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--zip.is-invalid ) .pchs-form__field--zip.is-invalid,
	.pchs-form--contact:has( .pchs-form__field--phone.is-invalid, .pchs-form__field--email.is-invalid ) .pchs-form__field--email.is-invalid {
		margin-inline-start: calc(-1 * var(--wp--preset--spacing--30));
	}
}

@container pchs-form (min-width: 26rem) {

	.pchs-form--financing:has( .pchs-form__field--name.is-invalid, .pchs-form__field--phone.is-invalid ) .pchs-form__field--name,
	.pchs-form--financing:has( .pchs-form__field--name.is-invalid, .pchs-form__field--phone.is-invalid ) .pchs-form__field--phone,
	.pchs-form--careers:has( .pchs-form__field--email.is-invalid, .pchs-form__field--phone.is-invalid ) .pchs-form__field--email,
	.pchs-form--careers:has( .pchs-form__field--email.is-invalid, .pchs-form__field--phone.is-invalid ) .pchs-form__field--phone {
		grid-column: 1 / -1;
	}

	.pchs-form--financing:has( .pchs-form__field--name.is-invalid, .pchs-form__field--phone.is-invalid ) .pchs-form__field--phone.is-invalid,
	.pchs-form--careers:has( .pchs-form__field--email.is-invalid, .pchs-form__field--phone.is-invalid ) .pchs-form__field--phone.is-invalid {
		margin-inline-start: calc(-1 * var(--wp--preset--spacing--30));
	}
}

/*
 * Hero and booking hold two pairs here (Name | Phone, ZIP | Email). Any error
 * in either sends all four to full rows, so no field is left alone in a half
 * track.
 */
@container pchs-form (min-width: 30rem) {

	.pchs-form--hero:has( .pchs-form__field--name.is-invalid, .pchs-form__field--phone.is-invalid, .pchs-form__field--zip.is-invalid, .pchs-form__field--email.is-invalid ) :is( .pchs-form__field--name, .pchs-form__field--phone, .pchs-form__field--zip, .pchs-form__field--email ),
	.pchs-form--booking:has( .pchs-form__field--name.is-invalid, .pchs-form__field--phone.is-invalid, .pchs-form__field--zip.is-invalid, .pchs-form__field--email.is-invalid ) :is( .pchs-form__field--name, .pchs-form__field--phone, .pchs-form__field--zip, .pchs-form__field--email ) {
		grid-column: 1 / -1;
	}

	.pchs-form--hero :is( .pchs-form__field--phone, .pchs-form__field--zip, .pchs-form__field--email ).is-invalid,
	.pchs-form--booking :is( .pchs-form__field--phone, .pchs-form__field--zip, .pchs-form__field--email ).is-invalid {
		margin-inline-start: calc(-1 * var(--wp--preset--spacing--30));
	}
}

.pchs-form__summary[hidden] {
	display: none;
}

.pchs-form__summary--active {
	padding: var(--wp--preset--spacing--40);
	/* danger-border is decoration around the panel rather than the boundary of a
	   control, so 1.4.11's 3:1 does not bite here. */
	border: 1px solid var(--wp--custom--color--danger-border);
	border-radius: var(--wp--custom--radius--sm);
	background: var(--wp--custom--color--danger-tint);
}

/*
 * tabindex="-1", so app.css deliberately skips it. This one is the exception:
 * the visitor was thrown here by a failed submit and needs to see where they
 * landed.
 */
.pchs-form__summary--active:focus-visible {
	outline: var(--pchs-focus-width) solid var(--pchs-focus-color);
	outline-offset: var(--pchs-focus-gap);
	box-shadow: var(--pchs-focus-halo);
}

.pchs-form__summary-title {
	margin: 0;
	font-size: var(--wp--preset--font-size--base);
	font-weight: var(--wp--custom--weight--bold);
	line-height: var(--wp--custom--leading--title);
	color: var(--wp--preset--color--navy);
}

.pchs-form__summary-list {
	margin: var(--wp--preset--spacing--20) 0 0;
	padding-left: var(--wp--preset--spacing--40);
	font-size: var(--wp--preset--font-size--sm);
	line-height: var(--wp--custom--leading--body);
}

.pchs-form__summary-list li + li {
	margin-block-start: var(--wp--preset--spacing--20);
}

/* Links in a sentence list, not 44px tap boxes. app.css makes an unclassed
   lone link in a list item one, which spread these 44, 33 and 56px apart; the
   class is what opts them out of that rule. */
.pchs-form__summary-link {
	display: inline;
	min-height: 0;
	color: var(--wp--preset--color--danger);
	text-decoration: underline;
}

/* ---- actions ----------------------------------------------------------- */

.pchs-form__actions {
	display: grid;
}

/* Look and size are .pchs-btn (app.css). This only holds the spinner. */
.pchs-form__submit {
	position: relative;
}

/*
 * Not disabled: busy. The button is only ever [disabled] while a lead is in
 * flight, which is a claim that something is happening, so it keeps most of
 * its colour instead of app.css's faded disabled look, and the cursor says
 * "working". The label reads "Sending…" and has to stay readable in daylight.
 */
.pchs-form__submit[disabled] {
	opacity: 0.7;
	cursor: progress;
}

/*
 * The spinner is absolutely placed at the button's start edge, so it adds no
 * width and nothing moves when it appears (INT-21). Hidden under reduced
 * motion rather than left as a frozen ring, which would read as a hang.
 */
.pchs-form__submit[aria-busy="true"]::before {
	content: "";
	position: absolute;
	inset-inline-start: var(--wp--preset--spacing--50);
	top: 50%;
	width: 1rem;
	height: 1rem;
	margin-top: -0.5rem;
	box-sizing: border-box;
	border: 2px solid color-mix( in srgb, var(--wp--preset--color--base) 40%, transparent );
	border-top-color: var(--wp--preset--color--base);
	border-radius: var(--wp--custom--radius--pill);
	animation: pchs-form-spin 700ms linear infinite;
}

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

	.pchs-form__submit[aria-busy="true"]::before {
		display: none;
	}
}

@keyframes pchs-form-spin {

	to {
		transform: rotate( 1turn );
	}
}

/*
 * While the request is out the fields stay readable and selectable, but stop
 * looking live, so a slow submit does not invite edits that will not be sent.
 */
.pchs-form.is-busy .pchs-form__fields {
	opacity: 0.6;
	cursor: progress;
}

@media ( prefers-reduced-motion: no-preference ) {

	.pchs-form__fields {
		transition: opacity var(--pchs-duration) var(--pchs-ease);
	}
}

/*
 * Empty, it takes no room and has no gap above it to lose. While the lead is
 * in flight the button already says "Sending…", so the status copy is taken
 * out of the flow and clipped: the page moves nothing (INT-21) and the text
 * stays in the live region for a screen reader. A later failure message gets
 * its own line.
 */
.pchs-form__status {
	margin: 0;
	font-size: var(--wp--preset--font-size--sm);
	line-height: var(--wp--custom--leading--body);
	color: var(--wp--preset--color--muted);
}

.pchs-form__status:not(:empty) {
	margin-block-start: var(--wp--preset--spacing--10);
}

/*
 * While the error summary is on screen it already shows the same sentence.
 * Keep the live region filled for assistive tech, but do not print the line
 * again under the button for sighted visitors.
 */
.pchs-form:has(.pchs-form__summary--active:not([hidden])) .pchs-form__status,
.pchs-form.is-busy .pchs-form__status {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.pchs-form__fine {
	margin: 0;
	font-size: var(--wp--preset--font-size--sm);
	line-height: var(--wp--custom--leading--body);
	color: var(--wp--preset--color--muted);
}

/*
 * Below the nav breakpoint the sticky call bar covers the bottom of the
 * viewport while mid-page. Without a clear lane under the form, the trust
 * line sits entirely under that bar whenever the submit button is scrolled
 * into the last row above it (measured 0px of the fine print visible on
 * /contact/ at 390px). The padding is empty space the bar can cover instead.
 *
 * Hero and in-hero booking cards are excluded: they have a height budget, and
 * the bar already hides while a text field is focused. The home-page booking
 * band is included — it sits low on the page where the bar overlays it.
 * Past 80em the bar is display:none (sticky-call-bar/style.css).
 */
@media (max-width: 79.99em) {

	.pchs-form--full,
	.pchs-form--contact,
	.pchs-form--financing,
	.pchs-form--careers,
	.pchs-form--subcontractor,
	.pchs-date-picker--band .pchs-form--booking {
		padding-block-end: calc(var(--pchs-callbar-h) + env(safe-area-inset-bottom, 0px));
	}

	/*
	 * Padding below the form keeps the document end clear; scroll-margin keeps
	 * the last control clear when it is the scroll target (error jump, focus).
	 */
	.pchs-form--full .pchs-form__actions,
	.pchs-form--contact .pchs-form__actions,
	.pchs-form--financing .pchs-form__actions,
	.pchs-form--careers .pchs-form__actions,
	.pchs-form--subcontractor .pchs-form__actions,
	.pchs-date-picker--band .pchs-form--booking .pchs-form__actions,
	.pchs-form--full .pchs-form__fine,
	.pchs-form--contact .pchs-form__fine,
	.pchs-form--financing .pchs-form__fine,
	.pchs-date-picker--band .pchs-form--booking .pchs-form__fine {
		scroll-margin-block-end: calc(
			var(--pchs-callbar-h)
			+ env(safe-area-inset-bottom, 0px)
			+ var(--wp--preset--spacing--20)
		);
	}
}

/* Short statements, each with a check, instead of one line joined by dots. */
.pchs-form__fine--list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--10) var(--wp--preset--spacing--40);
	padding: 0;
	list-style: none;
}

.pchs-form__fine--list li {
	display: flex;
	align-items: baseline;
	gap: var(--wp--preset--spacing--10);
}

.pchs-form__fine--list li::before {
	content: "";
	flex: none;
	width: 0.9em;
	height: 0.9em;
	align-self: center;
	background-color: var(--wp--preset--color--green);
	-webkit-mask: var(--pchs-form-icon-check) center / contain no-repeat;
	mask: var(--pchs-form-icon-check) center / contain no-repeat;
}

/* ---- confirmation ------------------------------------------------------ */

/*
 * The panel replaces the form in place. forms.js holds the card at the height
 * it had before (INT-16) and hides its heading, so the confirmation fills the
 * space the form left instead of the page jumping up under the visitor.
 */
.pchs-form__success {
	display: grid;
	align-content: center;
	justify-items: start;
	gap: var(--wp--preset--spacing--20);
	padding: var(--wp--preset--spacing--50);
	border: 1px solid var(--wp--preset--color--green);
	border-radius: var(--wp--custom--radius--sm);
	background: var(--wp--preset--color--green-tint);
}

.is-sent[data-pchs-card] {
	display: flex;
	flex-direction: column;
}

.is-sent [data-pchs-card-head] {
	display: none;
}

.is-sent[data-pchs-card] > .pchs-form__success,
.is-sent[data-pchs-card] > * > .pchs-form__success {
	flex: 1;
}

/* Inside a card the card is the frame: the confirmation sits centred on it
   rather than as a tinted box filling it. */
.is-sent[data-pchs-card] .pchs-form__success {
	justify-items: center;
	padding: var(--wp--preset--spacing--40);
	border-color: transparent;
	background: none;
	text-align: center;
}

.is-sent[data-pchs-card] .pchs-form__success-title {
	flex-direction: column;
	font-size: var(--wp--preset--font-size--2-xl);
}

.is-sent[data-pchs-card] .pchs-form__success-title::before {
	width: 3rem;
	height: 3rem;
}

/* Same exception, same ring, same reason as the error summary above. */
.pchs-form__success:focus-visible {
	outline: var(--pchs-focus-width) solid var(--pchs-focus-color);
	outline-offset: var(--pchs-focus-gap);
	box-shadow: var(--pchs-focus-halo);
}

@media ( prefers-reduced-motion: no-preference ) {

	.pchs-form__success {
		animation: pchs-form-arrive var(--pchs-duration-slow) var(--pchs-ease) both;
	}
}

@keyframes pchs-form-arrive {

	from {
		opacity: 0;
	}
}

.pchs-form__success-title {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--20);
	margin: 0;
	font-size: var(--wp--preset--font-size--lg);
	font-weight: var(--wp--custom--weight--heavy);
	line-height: var(--wp--custom--leading--title);
	color: var(--wp--preset--color--navy);
}

/* The check says "done" before the words do. The panel may be the JS-built
   one outside any .pchs-form, so the shape is restated here. */
.pchs-form__success-title::before {
	content: "";
	flex: none;
	width: 1.5rem;
	height: 1.5rem;
	background-color: var(--wp--preset--color--green);
	-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
	mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}

.pchs-form__success-body,
.pchs-form__success-next {
	margin: 0;
	font-size: var(--wp--preset--font-size--base);
	line-height: var(--wp--custom--leading--body);
	color: var(--wp--preset--color--ink);
}

/* Inline within a sentence, so it takes WCAG 2.5.8's inline-text exception
   rather than a 44px box. */
.pchs-form__success-next a {
	display: inline;
	min-height: 0;
	color: var(--wp--preset--color--green-dark);
	font-weight: var(--wp--custom--weight--heavy);
	text-decoration: underline;
	white-space: nowrap;
}

/* ---- honeypot ---------------------------------------------------------- */

/*
 * Off-screen rather than display:none. A bot that ignores hidden fields is
 * exactly the bot we are trying to catch; tabindex="-1" and aria-hidden keep
 * every real visitor away from it.
 */
.pchs-form__hp {
	position: absolute;
	left: -9999px;
	top: auto;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

/* ---- utility ----------------------------------------------------------- */

/* Declared here rather than assumed: this stylesheet is loaded per block, and
   the day tiles depend on it for their accessible names. */
.pchs-form .screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
}

/* ---- per-form layouts -------------------------------------------------- */

/*
 * Which fields pair up, by the width the form actually has:
 *
 *   under 20rem   one per row (ZIP full — a half track alone looks broken)
 *   20 to 30rem   quote and booking: Phone | ZIP
 *   26rem and up  financing: Name | Phone, ZIP half; careers and credit pair up
 *   30rem and up  quote and booking: Name | Phone, then ZIP | Email
 *
 * ZIP is five digits, so it shares a half track only when a neighbour sits
 * beside it (Phone | ZIP). Alone on a row it spans both tracks — a half-width
 * field with empty space beside it read as unfinished on the booking card.
 */
.pchs-form__field--zip {
	grid-column: span 1;
}

@container pchs-form (max-width: 19.99rem) {

	.pchs-form__field--zip {
		grid-column: 1 / -1;
	}
}

@container pchs-form (max-width: 25.99rem) {

	.pchs-form--financing .pchs-form__field--zip {
		grid-column: 1 / -1;
	}
}

@container pchs-form (min-width: 20rem) {

	.pchs-form--hero .pchs-form__field--phone,
	.pchs-form--booking .pchs-form__field--phone,
	.pchs-form--full .pchs-form__field--phone,
	.pchs-form--contact .pchs-form__field--phone,
	.pchs-form--contact .pchs-form__field--email {
		grid-column: span 1;
	}
}

/*
 * Name and phone are the only pair on the financing, careers and credit
 * forms' rows, so they pair sooner: at 26rem, which is what the panel has
 * beside the pitch at 1024 and beside the openings at 1440.
 */
@container pchs-form (min-width: 26rem) {

	.pchs-form--financing .pchs-form__field--name,
	.pchs-form--financing .pchs-form__field--phone,
	.pchs-form--careers .pchs-form__field--email,
	.pchs-form--careers .pchs-form__field--phone,
	.pchs-form--credit .pchs-form__field {
		grid-column: span 1;
	}

	.pchs-form--financing .pchs-form__field--zip {
		grid-column: 1 / span 1;
	}
}

@container pchs-form (min-width: 30rem) {

	.pchs-form--hero .pchs-form__field--name,
	.pchs-form--booking .pchs-form__field--name {
		grid-column: span 1;
	}

	/* Email pairs with ZIP; under 30rem it takes its own row below Phone | ZIP. */
	.pchs-form--hero .pchs-form__field--email,
	.pchs-form--booking .pchs-form__field--email {
		grid-column: span 1;
	}
}
