/**
 * About and Contact.
 *
 * Both are static markup in the master rather than mounted views, and both are
 * small enough that one stylesheet for the pair is clearer than two. Generated
 * from the master by `docs/verification/invert-css.py`, fed the raw rules in
 * source order (docs/MASTER-CASCADE-TRAPS.md).
 *
 * `.section-head`, `.work-head` and the `.field` control all stay in
 * components.css — About shares every one of them — and so does the
 * testimonial carousel the quote band is built from: the wrapper class
 * differs, the component does not.
 *
 * The master carries an older generation of the team block as `.person-card`,
 * `.team-card` and `.team-grid`, and an `.about-panel` that nothing renders.
 * Every selector below was matched against the rendered About and Contact
 * views first.
 *
 * `.legal-page` at the end is not the master's: it is the Privacy page, the one
 * approved addition to the master's navigation, and it is the shell's own
 * measure and type rather than a new design.
 */
.about-hero {
	padding: 155px 0 70px;
	display: flex;
	align-items: flex-end;
	min-height: auto;
	padding-top: 118px;
	padding-bottom: 74px;
}

.about-hero-grid {
	display: grid;
	gap: 50px;
	align-items: end;
	grid-template-columns: 1fr;
}

.about-beliefs {
	background: #fff;
	padding: 88px 0;
}

.belief-grid {
	display: grid;
	margin-top: 38px;
	grid-template-columns: 1fr;
	gap: 14px;
}

.belief {
	background: var(--paper);
	border: 1px solid var(--line);
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	min-height: 278px;
	padding: 28px;
	border-radius: 22px;
	/* The numeral never sits on its title: the space between them was only whatever the card's minimum height left over, so a card with a longer title and text had none (Ben, 2 Oct 2026, from a phone: 02 and 03 should be laid out as 01, which has 22px). */
	gap: 22px;
}

.belief b {
	font-size: 58px;
	letter-spacing: -.08em;
	line-height: .85;
	color: var(--orange-text);
}

.belief h3 {
	line-height: 1;
	margin: 0 0 10px;
	font-size: calc(27px * var(--type-heading));
	font-weight: 700;
	letter-spacing: -.012em;
}

.belief p {
	color: var(--spec-lede);
	font-size: var(--fs-body);
	line-height: 1.5;
	font-weight: 400;
}

.team-section {
	padding: 110px 0;
	padding-top: 105px;
}

/*
 * **An approved correction to the frozen master** (Ben, 21 Sep 2026).
 *
 * v58 specifies `.about-quote { padding: 90px 0; background: var(--ink);
 * color: #fff }`, and its QA layer then appends
 * `.case-quote-strip,.about-quote,.testimonial { padding:0!important;
 * background:transparent!important }`, which wins. So the master itself
 * paints white type straight onto the paper ground — the band is unreadable
 * in v58, and this build reproduced that faithfully.
 *
 * Ben's ruling is to restore the panel v58 specifies rather than the one it
 * renders, and explicitly not to darken the type instead. The two losing
 * declarations are simply not written, and `.testimonial-carousel` in
 * components.css supplies the dark ground and the 88px/68px air it already
 * gives every other surface.
 */
.about-quote {
	color: #fff;
}

/*
 * The master writes this colour as an inline style in
 * `testimonialCarouselHTML()`, so it is in the markup rather than the
 * stylesheet and the generator cannot see it. Same treatment as the Case
 * Studies strip, which carries it in case.css for the same reason.
 */
.about-quote.testimonial-carousel .eyebrow {
	color: var(--eyebrow-on-dark);
}



.team-photo-grid {
	display: grid;
	gap: 14px;
	grid-template-columns: 1fr;
}

.person-image-fallback {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform .45s ease;
	display: grid;
	place-items: center;
	background: linear-gradient(145deg,var(--yellow),var(--orange) 58%,var(--red));
}

.person-meta {
	position: absolute;
	z-index: 2;
	left: 24px;
	right: 24px;
	bottom: 22px;
	color: #fff;
}

.person-meta span {
	font-family: var(--font-body);
	font-size: var(--fs-eyebrow-label);
	font-weight: 800;
	letter-spacing: var(--ls-eyebrow-label);
	text-transform: uppercase;
	display: block;
	line-height: 1.35;
	color: rgba(255,255,255,.7);
	margin-bottom: 8px;
}

.person-meta h3 {
	font-size: clamp(27px,3vw,42px);
	line-height: .95;
	letter-spacing: var(--tr-display);
	margin: 0;
}

.person-image-fallback strong {
	font-size: 82px;
	letter-spacing: -.08em;
}

.dogs-card {
	grid-column: auto;
	aspect-ratio: 1/1;
}

.about-hero .lead {
	max-width: 790px;
}

.about-people-hero {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 12px;
	height: 340px;
}

.about-people-hero img {
	height: 100%;
	object-fit: cover;
	border-radius: 24px;
}

.person-flip {
	perspective: 1200px;
	min-width: 0;
	aspect-ratio: 1/1;
}

.person-flip-inner {
	position: relative;
	width: 100%;
	height: 100%;
	transition: transform .58s cubic-bezier(.2,.7,.2,1);
	transform-style: preserve-3d;
}

/*
 * Keyboard focus turns the card; a tap does not (Ben, 1 Oct 2026). This was `:focus-within`, which also matches the focus a TAP leaves on a card (it is focusable) on
 * devices that focus on tap: the card turned, then a second tap removed `.flipped` but the lingering focus kept it turned, so the card could not be turned back by tapping it
 * (measured: class removed, transform still 180deg). `:focus-visible` is true for keyboard focus and not for a tap, on the card itself or on the link on its back.
 */
.person-flip:is(:focus-visible, :has(:focus-visible)) .person-flip-inner {
	transform: rotateY(180deg);
}

/*
 * `.flipped` is the tap and keyboard state. Hover alone leaves a touch screen
 * with a card it can see the back of only by accident.
 */
.person-flip.flipped .person-flip-inner {
	transform: rotateY(180deg);
}

.person-flip:focus-visible {
	outline: 3px solid var(--orange);
	outline-offset: 4px;
	border-radius: 24px;
}

.person-face {
	position: absolute;
	inset: 0;
	border-radius: 24px;
	overflow: hidden;
	backface-visibility: hidden;
	-webkit-backface-visibility: hidden;
}

.person-front {
	background: #191919;
	color: #fff;
}

.person-front img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.person-front .person-image-fallback {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.person-front:after {
	content: "";
	position: absolute;
	inset: 42% 0 0;
	background: linear-gradient(180deg,transparent,rgba(0,0,0,.84));
	z-index: 1;
}

.person-front .person-meta {
	position: absolute;
	z-index: 2;
	left: 26px;
	right: 26px;
	bottom: 24px;
	color: #fff;
}

.person-front .person-meta span {
	font-family: var(--font-body);
	font-size: var(--fs-eyebrow-label);
	font-weight: 800;
	letter-spacing: var(--ls-eyebrow-label);
	text-transform: uppercase;
	display: block;
	line-height: 1.2;
	color: rgba(255,255,255,.74);
	margin-bottom: 8px;
}

.person-front .person-meta h3 {
	font-size: clamp(38px,3.5vw,52px);
	line-height: .94;
	letter-spacing: var(--tr-display);
	text-wrap: balance;
	margin: 0;
}

/*
 * Dark grey, not black (Ben, 22 Sep 2026). The orange ring is the prototype's
 * own; it is decoration, so it sits behind the content rather than in the
 * accessibility tree.
 */
.person-back {
	transform: rotateY(180deg);
	background: var(--grey-card);
	color: #fff;
	padding: 30px;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	pointer-events: none;
}

/*
 * The orange ring that sat on the back face is gone (Ben, 29 Sep 2026). The
 * same side bar as every other card grows down this face's left edge once the
 * card has turned — the delay is the flip's own duration — and, with reduced
 * motion, is simply there, since the flip itself is instant.
 */
.person-back:before {
	content: "";
	position: absolute;
	top: 0;
	bottom: 0;
	left: 0;
	width: var(--card-bar-width);
	background: var(--orange);
	transform: scaleY(0);
	transform-origin: 50% 0;
	transition: transform var(--card-bar-duration) var(--card-bar-ease) 0.28s;
	pointer-events: none;
}

.person-flip:is(:focus-visible, :has(:focus-visible)) .person-back:before,
.person-flip.flipped .person-back:before {
	transform: scaleY(1);
}

.person-back>* {
	position: relative;
	z-index: 2;
}

.person-flip:is(:focus-visible, :has(:focus-visible)) .person-back,
.person-flip.flipped .person-back {
	pointer-events: auto;
}

.person-flip:is(:focus-visible, :has(:focus-visible)) .person-front,
.person-flip.flipped .person-front {
	pointer-events: none;
}

/*
 * One face at a time (Ben, 1 Oct 2026, Dave's list: "the front-face name remains visible over the biography").
 *
 * `backface-visibility: hidden` on the two faces is the flip's own way of hiding the face that is turned away, and in Chromium it is enough: with the back hidden, a turned card
 * photographs as empty at every width (docs/verification/flip-cards.mjs). Dave's report is a rendering engine that leaks a face's positioned children through the other
 * (a WebKit family of bugs: children with a z-index of their own, inside a face that clips with `overflow: hidden` and a radius), and no leak can be shown here to prove a
 * fix against, so this does not rely on one mechanism:
 *
 *   1. The face that is turned away is `visibility: hidden`, not merely facing away: it is not painted, cannot be hit, is not in the tab order and is not read out. The
 *      switch is timed to the middle of the turn (.3s of the .58s) so the card is never empty, and is immediate in the direction that SHOWS a face, so a keyboard user who
 *      tabs from a card that has just turned always finds the link on the back (it would otherwise be hidden for the first .3s).
 *   2. The front's own children (the picture, the darkening gradient, the role and the name) and the back's carry `backface-visibility: hidden` too, so none of them depends on
 *      its parent's.
 *   3. Face-up, the back is out of the accessibility tree: a screen reader reads the name once, not three times (front name, back name, bio) on a card nobody has turned.
 *
 * With reduced motion the transition is `none`, so the faces swap with the card, instantly.
 */
.person-front > *,
.person-front::after,
.person-back > * {
	-webkit-backface-visibility: hidden;
	backface-visibility: hidden;
}

.person-back {
	visibility: hidden;
	transition: visibility 0s linear 0.3s;
}

.person-flip:is(:focus-visible, :has(:focus-visible)) .person-back,
.person-flip.flipped .person-back {
	visibility: visible;
	transition-delay: 0s;
}

.person-flip:is(:focus-visible, :has(:focus-visible)) .person-front,
.person-flip.flipped .person-front {
	visibility: hidden;
	transition: visibility 0s linear 0.3s;
}

/*
 * Hover turns the card only where there IS hover (Ben, 1 Oct 2026). A tap on a touch screen leaves `:hover` stuck on the card until the next tap elsewhere, so the card
 * turned on the first tap and a second tap could not turn it back (the class was removed and `:hover` kept it turned). On a touch device the card turns by `.flipped`
 * alone, from a tap (site.js); with a mouse it turns on hover as before.
 */
@media (hover: hover) {
	.person-flip:hover .person-flip-inner {
		transform: rotateY(180deg);
	}

	.person-flip:hover .person-back:before {
		transform: scaleY(1);
	}

	.person-flip:hover .person-back {
		pointer-events: auto;
		visibility: visible;
		transition-delay: 0s;
	}

	.person-flip:hover .person-front {
		pointer-events: none;
		visibility: hidden;
		transition: visibility 0s linear 0.3s;
	}
}

.person-back>span {
	font-size: max(calc(12px * var(--type-small)), var(--type-floor));
	line-height: 1.2;
	text-transform: uppercase;
	letter-spacing: .14em;
	color: rgba(255,255,255,.74);
	font-weight: 800;
}

.person-back .person-name {
	font-weight: 700;   /* what the h3 it replaces had by default */
	font-size: clamp(34px,3vw,44px);
	line-height: .95;
	letter-spacing: var(--tr-display);
	margin: 10px 0 16px;
}

.person-back p {
	font-size: var(--fs-body);
	line-height: 1.5;
	color: var(--case-hero-lead);
	margin: 0 0 20px;
	max-width: 430px;
}

/*
 * White, explicitly. WordPress emits
 * `a:where(:not(.wp-element-button)) { color: var(--ink) }` from theme.json,
 * and although `:where()` carries no specificity it is still a declaration —
 * so it beats inheritance, and any link that does not set its own colour goes
 * black. On this dark grey face that left `Read full bio` almost invisible
 * (Ben, 22 Sep 2026). Everywhere else on a dark ground the theme happened to
 * set a colour already; this was the one that did not.
 */
.person-back a {
	color: #fff;
	font-size: max(calc(13.5px * var(--type-small)), var(--type-floor));
	text-transform: uppercase;
	letter-spacing: .08em;
	font-weight: 800;
	border-bottom: 1px solid rgba(255,255,255,.45);
	padding-bottom: 5px;
	align-self: flex-start;
}

.person-back a:hover,
.person-back a:focus-visible {
	color: var(--yellow);
	border-color: var(--yellow);
}

/* Keyboard focus is a ring, not only a colour change (it was `outline: 0`): 3px yellow, 3.7:1 against the dark grey face. */
.person-back a:focus-visible {
	outline: 3px solid var(--yellow);
	outline-offset: 4px;
}

@media screen and (min-width: 651px) {

	.about-hero {
		padding: 155px 0 70px;
		padding-bottom: 74px;
	}

	.belief-grid {
		grid-template-columns: 1fr 1fr;
	}

	.team-photo-grid {
		grid-template-columns: repeat(2,1fr);
	}

	.about-people-hero {
		height: 430px;
	}

}

@media screen and (min-width: 1001px) {

	.about-hero-grid {
		grid-template-columns: 1.15fr .85fr;
	}

	.belief-grid {
		grid-template-columns: repeat(3,1fr);
	}

	.team-photo-grid {
		grid-template-columns: repeat(3,1fr);
	}

}

@media screen and (min-width: 1101px) {

	.about-hero {
		padding-top: 112px;
		padding-bottom: 44px;
	}

	.about-beliefs {
		padding: 68px 0;
	}

	.belief {
		min-height: 230px;
	}

	.belief h3 {
		font-size: calc(24px * var(--type-heading));
	}

	.team-section {
		padding: 72px 0;
	}

	.about-people-hero {
		height: 350px;
	}

}

@media screen and (min-width: 1501px) {

	.about-hero {
		padding: 155px 0 70px;
		padding-bottom: 74px;
	}

	.about-beliefs {
		padding: 88px 0;
	}

	.belief {
		min-height: 278px;
	}

	.belief h3 {
		font-size: calc(27px * var(--type-heading));
	}

	.team-section {
		padding: 110px 0;
		padding-top: 105px;
	}

	.about-people-hero {
		height: 430px;
	}

}


.contact-hero .lead {
	margin-top: 20px;
	max-width: 760px;
}

.contact-hero {
	padding: 145px 0 65px;
}

.contact-hero .display-sm {
	max-width: 900px;
}

.contact-main {
	padding: 20px 0 105px;
}

.contact-main-grid {
	display: grid;
	gap: 70px;
	align-items: start;
	grid-template-columns: minmax(0, 1fr);   /* not 1fr: a track may not grow past the page because of what is in it (the consent placeholder in the map, the form labels) */
}

.contact-direct {
	border-top: 1px solid var(--line);
}

.contact-line {
	padding: 22px 0;
	border-bottom: 1px solid var(--line);
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.contact-line span {
	font-family: var(--font-body);
	font-size: var(--fs-eyebrow-label);
	font-weight: 800;
	letter-spacing: var(--ls-eyebrow-label);
	text-transform: uppercase;
	color: var(--muted);
}

.contact-line a {
	font-size: calc(24px * var(--type-heading));
	line-height: 1.25;
	letter-spacing: -.025em;
	font-weight: 800;
}

.contact-line strong {
	font-size: calc(24px * var(--type-heading));
	line-height: 1.25;
	letter-spacing: -.025em;
	font-weight: 800;
}

.contact-form {
	background: #fff;
	border: 1px solid var(--line);
	padding: 28px;
	display: grid;
	gap: 16px;
	grid-template-columns: minmax(0, 1fr);   /* a text field's own width (about 300px) must not hold the column open on a narrow phone */
	border-radius: 22px;
}

.contact-form .full {
	grid-column: auto;
}

.contact-form button {
	justify-self: start;
	margin-top: 8px;
	grid-column: auto;
}

.contact-form small {
	color: var(--muted);
	font-size: var(--fs-field-note);
	line-height: 1.5;
	grid-column: auto;
}

.contact-hero .btn-row {
	margin-top: 18px;
}

.contact-map-line {
	align-items: flex-start;
}

/*
 * The link that replaces the embed's six internal tab stops (Ben, 21 Sep
 * 2026). It inherits `.contact-line a`, so it matches the phone, the email
 * and the social row rather than introducing a new link treatment; all it
 * needs is to sit clear of the map above it.
 */
.contact-map-link {
	display: inline-block;
	margin-top: 14px;
}

.contact-map {
	width: 100%;
	margin-top: 10px;
	overflow: hidden;
	border: 1px solid var(--line);
	background: var(--case-gallery-bg-alt);
	border-radius: 18px;
}

.contact-map iframe {
	display: block;
	width: 100%;
	border: 0;
	filter: grayscale(.25);
	height: 250px;
}

.contact-social-links {
	display: flex;
	flex-wrap: wrap;
	gap: 10px 18px;
	margin-top: 7px;
}

/*
 * `.contact-line a` is 24px and is rewritten in the bands above, so at equal
 * specificity it lands after this one and wins. The master settles that with
 * `!important`; here the selector carries its own parent instead (28_ §7).
 *
 * The laptop band is the exception, written out below: there the master makes
 * `.contact-line a` 20px `!important` after this rule, so the social links
 * follow it up and drop back to 18px at 1501.
 */
.contact-line .contact-social-links a {
	font-size: calc(18px * var(--type-reading));
	font-weight: 800;
}

@media screen and (min-width: 651px) {

	.contact-form {
		grid-template-columns: 1fr 1fr;
	}

	.contact-form .full {
		grid-column: 1/-1;
	}

	.contact-form button {
		grid-column: 1/-1;
	}

	.contact-form small {
		grid-column: 1/-1;
	}

}

@media screen and (min-width: 1001px) {

	.contact-main-grid {
		grid-template-columns: .85fr 1.15fr;
	}

}

@media screen and (min-width: 1101px) {

	.contact-line .contact-social-links a {
		font-size: calc(20px * var(--type-heading));
	}


	.contact-hero {
		padding-top: 112px;
		padding-bottom: 44px;
		min-height: auto;
	}

	.contact-main {
		padding-bottom: 72px;
	}

	.contact-main-grid {
		gap: 52px;
	}

	.contact-line {
		padding: 17px 0;
	}

	.contact-line a {
		font-size: calc(20px * var(--type-heading));
	}

	.contact-line strong {
		font-size: calc(20px * var(--type-heading));
	}

}

@media screen and (min-width: 1501px) {

	/* The laptop band's `.contact-line a` stops applying here. */
	.contact-line .contact-social-links a {
		font-size: calc(18px * var(--type-reading));
	}


	.contact-hero {
		padding: 145px 0 65px;
		min-height: 0;
	}

	.contact-main {
		padding: 20px 0 105px;
	}

	.contact-main-grid {
		gap: 70px;
	}

	.contact-line {
		padding: 22px 0;
	}

	.contact-line a {
		font-size: calc(24px * var(--type-heading));
	}

	.contact-line strong {
		font-size: calc(24px * var(--type-heading));
	}

}


/* --------------------------------------------------------------- Privacy */
/*
 * No design to reproduce: the master has no privacy view. This is the reading
 * measure the rest of the site already uses for body copy, so the page looks
 * like the site the moment the policy is written. Nothing here assumes what
 * that copy says.
 */
.legal-page {
	padding: 118px 0 96px;
}

.legal-wrap {
	max-width: 760px;
}

.legal-page h1 {
	margin: 10px 0 0;
}

/*
 * The legal pages' H1 is a utility heading, not a Home or Route hero (Ben, 6 Oct 2026): 42px on a phone, up to 52px on a tablet, the laptop band's own 43-57px, 64px from 1501px,
 * leading 0.98. It was the shared display size, 48px on a phone and 88px at 1600. Size and leading only; the text, the level and the global `.display-sm` are untouched.
 */
.legal-page h1 {
	font-size: 42px;
	line-height: 0.98;
}

@media screen and (min-width: 651px) {
	.legal-page h1 {
		font-size: clamp(42px, 6vw, 52px);
	}
}

@media screen and (min-width: 1101px) {
	.legal-page h1 {
		font-size: clamp(43px, 3.8vw, 57px);
	}
}

@media screen and (min-width: 1501px) {
	.legal-page h1 {
		font-size: 64px;
	}
}

.legal-body {
	margin-top: 32px;
}

.legal-body > * + * {
	margin-top: 18px;
}

.legal-body h2 {
	margin-top: 44px;
	font-size: var(--fs-h2);
	line-height: 1.12;
	letter-spacing: -0.02em;
}

.legal-body h3 {
	margin-top: 32px;
	font-size: var(--fs-home-card-title);
	line-height: 1.2;
}

.legal-body p,
.legal-body li {
	color: var(--muted);
	font-size: var(--fs-body);
	line-height: 1.62;
}

/*
 * The Cookie Policy is the Complianz plugin's document, and the plugin sizes its own text: `#cmplz-document p, li, td` at 14px, against the Privacy Policy's 16px
 * (Ben, 1 Oct 2026). Both are the same legal-copy role, so the plugin's text takes the Privacy Policy's size and leading. One more id than the plugin's rule, so it wins
 * without `!important`; Larger text scales both through the same `--fs-body`.
 */
.legal-body #cmplz-document p,
.legal-body #cmplz-document li,
.legal-body #cmplz-document td,
.legal-body #cmplz-document th,
.legal-body #cmplz-document dd,
.legal-body #cmplz-document dt {
	font-size: var(--fs-body);
	line-height: 1.62;
}

.legal-body ul,
.legal-body ol {
	padding-left: 20px;
}

.legal-body li + li {
	margin-top: 8px;
}

@media screen and (min-width: 1101px) {

	.legal-page {
		padding: 146px 0 104px;
	}
}

/* ------------------------------------------------- reduced motion */
/*
 * The theme's policy, set in components.css: hover and focus states still
 * change, only the movement is removed.
 *
 * The flip is the exception worth stating. Its transition goes, so the card
 * turns instantly — but the transform stays, because it is what reveals the
 * back face. Removing it would leave a reader with this preference unable to
 * read any of the bios, which is not what the preference asks for.
 */
@media (prefers-reduced-motion: reduce) {

	.person-flip-inner,
	.person-image-fallback,
	.person-back,
	.person-flip:hover .person-front,
	.person-flip:is(:focus-visible, :has(:focus-visible)) .person-front,
	.person-flip.flipped .person-front,
	.person-back:before {
		transition: none;
	}
}

/*
 * Contact's social and review links (Ben, 26 Sep 2026)
 *
 * Recognisable marks rather than the text-only list. Monochrome ink by default
 * so the row stays restrained, orange on hover and focus like every other link
 * on the page, and the external arrow is a separate span so it can stay quiet
 * beside the name.
 */
.contact-social-links {
	display: flex;
	flex-wrap: wrap;
	gap: 18px;
	align-items: center;
}

.contact-social-link {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	color: var(--ink);
	text-decoration: none;
}

.contact-social-link .social-icon {
	flex: none;
	color: inherit;
}

.contact-social-link .contact-social-out {
	opacity: .5;
	font-size: .9em;
}

.contact-social-link:hover {
	color: var(--orange-text);
}

/*
 * Focus is functional rather than decorative, so it does not inherit the
 * orange: `#FC6600` is knowingly below AA and a focus indicator is the one
 * thing a keyboard user cannot do without. The normal and hover states are
 * unchanged; focus keeps the text colour it already had and gains the
 * theme's own 3px ring, in ink so the indicator itself is unambiguous
 * (Ben, 26 Sep 2026).
 */
.contact-social-link:focus-visible {
	outline: 3px solid var(--ink);
	outline-offset: 3px;
}

/* -------------------------------------------------------- Contact form
 * The Contact form only (Ben, 3 Oct 2026), brought into the Form system. The fields, their order and what is submitted are unchanged, and `.field` itself is untouched because the
 * planner's details step shares it; everything here is scoped to `.contact-form`. Labels are Inter in sentence case at 15px/600 (the shout of 800 uppercase tracked type is gone);
 * the controls are Inter 16px (a phone does not zoom into them), 52px tall, with the input radius token and a border that clears 3:1 against the card (--muted, 4.6:1); two columns
 * from 651px, one below; the submit is the dark Form button on the 16px button radius, full width on a phone; keyboard focus is an ink border plus a ring, never colour alone.
 * High contrast keeps its own, stronger rules (contrast.css), which outrank these.
 */
.contact-form {
	gap: 20px 18px;
	padding: 24px;
	border-radius: var(--r-card);
}

.contact-form .field {
	gap: 8px;
}

.contact-form .field label {
	font-family: var(--font-body);
	font-size: calc(15px * var(--type-reading));
	font-weight: 600;
	line-height: 1.3;
	letter-spacing: 0;
	text-transform: none;
	color: var(--ink);
}

.contact-form .field input,
.contact-form .field textarea {
	min-height: 52px;
	padding: 13px 16px;
	border: 1px solid var(--muted);
	border-radius: var(--r-input);
	background: var(--paper);
	font-family: var(--font-body);
	font-size: calc(16px * var(--type-reading));
	font-weight: 400;
	line-height: 1.4;
	color: var(--ink);
	transition: border-color 0.18s, background-color 0.18s, box-shadow 0.18s;
}

.contact-form .field textarea {
	min-height: 150px;
	resize: vertical;
}

.contact-form .field input::placeholder,
.contact-form .field textarea::placeholder {
	color: var(--muted);
	opacity: 1;
}

.contact-form .field input:hover,
.contact-form .field textarea:hover {
	border-color: var(--ink);
}

.contact-form .field input:focus-visible,
.contact-form .field textarea:focus-visible {
	border-color: var(--ink);
	background: var(--white);
	box-shadow: 0 0 0 1px var(--ink), 0 0 0 5px rgba(252, 102, 0, 0.32);
}

.contact-form .field input.has-error,
.contact-form .field textarea.has-error {
	border-color: #b3261e;
	box-shadow: 0 0 0 1px #b3261e;
}

.contact-form .field-error {
	color: #b3261e;
}

.contact-form .btn {
	min-height: 54px;
	margin-top: 4px;
	padding: 16px 28px;
	justify-content: center;
	justify-self: stretch;
}

.contact-form small {
	font-family: var(--font-body);
	font-size: calc(14.5px * var(--type-reading));
}

@media screen and (min-width: 651px) {

	.contact-form {
		gap: 22px 20px;
		padding: 32px;
	}

	.contact-form .btn {
		justify-self: start;
	}

}

@media screen and (min-width: 1101px) {

	.contact-form {
		padding: 36px;
	}

}

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

	.contact-form .field input,
	.contact-form .field textarea {
		transition: none;
	}

}
