/*
Theme Name: DeWitt Base
Theme URI: https://dewitt-studios.com
Author: DeWitt Studios
Description: Block theme for DeWitt Studios client sites. All client-specific
             design lives in theme.json, generated from that client's brand.json.
             Do not hand-edit theme.json — regenerate it.
Version: 0.1.0
Requires at least: 6.5
Tested up to: 6.8
Requires PHP: 8.0
License: GPL-2.0-or-later
Text Domain: dewitt-base
*/

/* ---------- enquiry form ---------- */
.dw-form { display: flex; flex-direction: column; gap: 18px; max-width: 34rem; }
.dw-field { display: flex; flex-direction: column; gap: 6px; margin: 0; }
.dw-field label {
	font-family: var(--wp--preset--font-family--karla);
	font-size: 13px; font-weight: 700; letter-spacing: .06em;
	text-transform: uppercase; color: var(--wp--preset--color--ink);
}
.dw-req { color: var(--wp--preset--color--action); }
.dw-form input, .dw-form select, .dw-form textarea {
	font-family: var(--wp--preset--font-family--karla);
	font-size: 16px; line-height: 1.5; color: var(--wp--preset--color--ink);
	background: var(--wp--preset--color--white);
	border: 1px solid var(--wp--preset--color--sesame);
	border-radius: var(--wp--custom--radius--control); padding: 12px 14px; width: 100%;
	/* Padding and border inside the declared width. Without this, width:100%
	   plus 14px of padding either side made every field 20px wider than its
	   column, and the contact page — her lead form — scrolled sideways on a
	   phone. */
	box-sizing: border-box;
}
.dw-form input:focus, .dw-form select:focus, .dw-form textarea:focus {
	outline: 2px solid var(--wp--preset--color--action); outline-offset: 1px;
	border-color: var(--wp--preset--color--action);
}
.dw-form textarea { resize: vertical; min-height: 120px; }
.dw-form button { align-self: flex-start; cursor: pointer; border: 0; }
.dw-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.dw-form-notice {
	padding: 14px 16px; border-left: 3px solid var(--wp--preset--color--action); /* impeccable-disable-line side-tab -- notice rule, not a card tab: the error variant recolours it, so the border carries state */
	background: var(--wp--preset--color--cotton); max-width: 34rem;
}
.dw-form-notice--error { border-left-color: #A2452F; }

/* Two-column blocks hold their structure into the tablet band.
   WordPress stacks every columns block below 781px — a device preset, not a
   point where this content stops fitting. Two ~350px columns are perfectly
   readable at 700px; three are not, so only 2-col blocks are tagged. */
@media (min-width: 600px) and (max-width: 781px) {
	.wp-block-columns.dw-hold-2col > .wp-block-column {
		flex-basis: 0 !important;
		flex-grow: 1;
	}
}

/* CR-103 (measure) — body copy was running to 110 characters per line at the
   1140px content width. The readable range is 45–75ch, ~65 ideal; past ~75 the
   eye loses its place returning to the next line.
   brand.json has declared `textWidth: 65ch` since day one and nothing ever
   consumed it. This is that value, finally wired up.
   Scoped to prose sitting directly in a section band — paragraphs inside
   columns already measure ~69ch and must not be constrained twice. */
.wp-block-post-content .wp-block-group > p:not(.has-label-font-size) {
	max-width: var(--dw-measure, 58ch);
	/* WordPress's constrained layout emits `margin-left: auto !important` on
	   every child, so a max-width alone centres the paragraph and it drifts
	   off the heading's left edge. Overriding core's !important is the only
	   way to hold the edge without restructuring the markup.
	   Aligning to 0 would snap the paragraph to the *padding* edge, 38px left
	   of every constrained sibling. It has to start where the contentSize box
	   starts, so the offset is half the leftover width — and 0 once the
	   viewport is narrower than contentSize, which is the mobile case. */
	margin-inline-start: max(0px, calc((100% - var(--wp--style--global--content-size)) / 2)) !important;
	margin-inline-end: auto !important;
}

/* A centre-aligned paragraph has to have a centred BOX too. Pinning it to the
   start edge centres its text inside a left-hugging column, which put the
   statement section's copy 226px left of the heading it sits under — both
   "centred", on two different axes. */
.wp-block-post-content .wp-block-group > p.has-text-align-center {
	margin-inline: auto !important;
}

/* Service panels — the price is the section's visual, not a paragraph about it.
   CR-003 (whitespace before borders): the panels were 1px-bordered boxes on a
   tinted band, which is the default move rather than a decision. They now sit
   on white with a single accent rule at the top edge and real internal padding.
   KB rule on cards: internal padding must be <= the gap between cards. Panels
   use 32px inline against a `grid` gutter that reaches 32px, so they hold. */
.dw-service {
	border-top: 2px solid var(--wp--preset--color--action);
}
.dw-price {
	font-family: var(--wp--preset--font-family--lora);
	color: var(--wp--preset--color--ink);
	line-height: 1;
	margin-block: 12px 4px;
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: 10px;
}
/* the qualifier rides beside the number instead of competing with it */
.dw-price__unit {
	font-family: var(--wp--preset--font-family--karla);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 400;
	letter-spacing: normal;
	color: var(--wp--preset--color--text-muted);
}
.dw-includes {
	list-style: none;
	padding-left: 0;
	margin-top: 20px;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink);
}
.dw-includes li {
	position: relative;
	padding-left: 20px;
	margin-bottom: 9px;
	line-height: 1.5;
}
/* a rule, not a bullet glyph — keeps the flat, editorial language */
.dw-includes li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0.72em;
	width: 10px;
	height: 1px;
	background: var(--wp--preset--color--action);
}
/* equal-height panels so the two prices sit on the same line */
.wp-block-columns:has(> .dw-service) { align-items: stretch; }

/* ─── Motion ──────────────────────────────────────────────────────────────
   There were no easing or duration tokens in this theme, so these establish
   them. Built-in CSS easings are too weak for UI; these are the standard
   strong curves. Nothing here animates anything a visitor sees more than a
   few times per visit. */
:root {
	--ease-out: cubic-bezier(0.23, 1, 0.32, 1);
	--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
}

/* 1 + 3 — enquiry notices.
   Purpose: preventing a jarring change. Frequency: rare (once, after someone
   has just decided to contact her). @starting-style animates the entrance with
   no JavaScript at all. 260ms keeps it under the 300ms UI ceiling — the
   original proposal said 320ms, which is over. */
.dw-form-notice {
	transition:
		opacity 260ms var(--ease-out),
		transform 260ms var(--ease-out);
}
@starting-style {
	.dw-form-notice {
		opacity: 0;
		transform: translateY(6px);
	}
}

/* 2 — buttons. Purpose: feedback. Frequency: occasional.

   The colour change is a radial reveal: the hover fill grows as a circle from
   the point the pointer crossed the button's edge, carrying the hover LABEL
   colour with it so the text recolours exactly in step with the ground arriving
   underneath. Flipping the label on a separate timer always looks wrong at the
   halfway mark, with half the word on the wrong ground.

   Three things about how this is built.

   ONE DRIVER. The reveal is driven by a class the script adds, never by :hover.
   pointerenter fires when the element is already :hover, so a CSS hover rule and
   a script both aiming at the same property land in the same frame and race —
   which produced, in order, an origin that slid instead of anchoring, a retract
   that snapped, and a reveal with no sweep at all. The script sets data-js on
   <html>; every rule below is scoped to it, and with no JavaScript the plain
   background-color transition underneath is what runs. That fallback is the
   behaviour this theme already shipped, gated and measured, so losing the
   flourish costs nothing.

   NO COLOUR NAMED HERE. Both pairs come from the client's interaction model via
   theme.json custom properties. A literal in this file would be a colour the
   contrast gate never sees — it reads the generated file, not the stylesheet.

   THE OVERLAY'S CORNERS. An absolutely positioned child with inset:0 sits on the
   PADDING box, so on a bordered button it is already inset by the border width.
   Inheriting the outer radius then pulls its corners away from the hole the
   border leaves and leaks a crescent of resting fill at all four. --btn-border-w
   defaults to 0 (no client sets a button border today) and the inner radius is
   derived from it, so the first one that does will not have to discover this. */
.wp-element-button {
	--btn-border-w: 0px;
	position: relative;
	overflow: hidden;
	isolation: isolate;
	transition:
		background-color 140ms ease,
		/* the press is deliberate, the release is the system answering — so
		   the release snaps back faster than the press goes down */
		transform 90ms var(--ease-out);
}

/* The no-JavaScript path, unchanged from what this theme has always done. */
@media (hover: hover) and (pointer: fine) {
	html:not([data-js]) .wp-element-button:hover {
		background-color: var(--wp--custom--interaction--button-hover-bg);
		color: var(--wp--custom--interaction--button-hover-text);
	}
	/* The dark CTA reverses: white button on ink, so it warms instead. */
	html:not([data-js]) .has-ink-background-color .wp-element-button:hover {
		background-color: var(--wp--custom--interaction--on-dark-hover-bg);
		color: var(--wp--custom--interaction--on-dark-hover-text);
	}
}

/* The reveal layer, injected by the script. It is a full copy of the button's
   face — its own ground AND its own label colour — clipped to a circle. */
.dw-reveal {
	position: absolute;
	inset: 0;
	z-index: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	background-color: var(--wp--custom--interaction--button-hover-bg);
	color: var(--wp--custom--interaction--button-hover-text);
	border-radius: calc(var(--wp--custom--radius--control) - var(--btn-border-w));
	pointer-events: none;
	clip-path: circle(0% at var(--dw-x, 50%) var(--dw-y, 50%));
	transition: clip-path 420ms var(--ease-out);
}
.has-ink-background-color .dw-reveal {
	background-color: var(--wp--custom--interaction--on-dark-hover-bg);
	color: var(--wp--custom--interaction--on-dark-hover-text);
}

/* Frame 1: commit the new centre with the radius at zero and no transition, so
   the centre moves without animating. Frame 2 is the .is-revealed rule below —
   the forced reflow between them is what stops the browser coalescing both
   changes into one recalc and jumping straight to the finished circle. */
.wp-element-button.dw-anchoring .dw-reveal {
	transition: none;
	clip-path: circle(0% at var(--dw-x, 50%) var(--dw-y, 50%));
}
/* The same suppression holding the radius at FULL: used on the way out to move
   the origin while the fill still covers everything, where the move cannot be
   seen. Moving it mid-sweep would tear a hole in the middle of the button. */
.wp-element-button.dw-anchoring-full .dw-reveal {
	transition: none;
	clip-path: circle(var(--dw-max, 140%) at var(--dw-x, 50%) var(--dw-y, 50%));
}
/* --dw-max is measured per entry, not assumed. A circle() percentage resolves
   against √(w²+h²)/√2, so a fixed 140% covers a wide pill and under-reaches on
   a tall or square button, leaving a wedge of the resting fill in one corner. */
.wp-element-button.is-revealed .dw-reveal {
	clip-path: circle(var(--dw-max, 140%) at var(--dw-x, 50%) var(--dw-y, 50%));
}

/* Keyboard users get no hover, and no pointer position either, so the reveal
   comes from the centre. Without this the effect does not exist for them. */
.wp-element-button:focus-visible .dw-reveal {
	clip-path: circle(140% at 50% 50%);
}

.wp-element-button:active {
	transform: scale(0.98);
	transition: transform 120ms var(--ease-out);
}

/* Reduced motion: gentler, not zero. The colour still changes — that is the
   feedback — it cross-fades in place instead of sweeping. */
@media (prefers-reduced-motion: reduce) {
	.dw-reveal {
		clip-path: none;
		opacity: 0;
		transition: opacity 140ms ease;
	}
	.wp-element-button.is-revealed .dw-reveal,
	.wp-element-button:focus-visible .dw-reveal {
		clip-path: none;
		opacity: 1;
	}
	.wp-element-button:active { transform: none; }
}

/* 5 — links. Purpose: feedback. Only the underline colour moves; the text
   colour holds so the link never looks like it changed meaning. */
main a:not(.wp-element-button) {
	/* No resting declaration. Setting one to sesame put the underline at 1.0:1
	   against the sesame band — invisible — on the two links that matter most,
	   her phone number and her email. The underline inherits currentColor and
	   only the hover state moves. */
	transition: text-decoration-color 140ms ease;
}
@media (hover: hover) and (pointer: fine) {
	main a:not(.wp-element-button):hover {
		text-decoration-color: var(--wp--preset--color--text-muted);
	}
}

/* 4 — portfolio stagger. Purpose: delight, at the occasional tier.

   Built with a scroll-driven CSS timeline, not IntersectionObserver, and the
   reason is the failure mode. A JS reveal has to hide the images first and
   then un-hide them; if that script ever fails to run its second half, her
   portfolio is invisible — and losing the photographs is far worse than
   losing the animation.

   `animation-timeline: view()` inverts that. It is wrapped in @supports, so a
   browser without it simply never applies the animation and the images are
   visible by default. There is no hidden state to get stuck in, and no
   JavaScript on the site at all.

   The trade is honest: Safari does not support view() timelines yet, so a good
   share of her visitors will see the grid appear normally. That is the correct
   direction to fail for a delight-tier effect.

   The stagger comes from offsetting each column's entry range rather than an
   animation-delay, which a scroll timeline ignores. */
@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {
		[data-reveal] {
			animation: dw-rise linear both;
			animation-timeline: view();
			animation-range: entry 0% cover 35%;
		}
		[data-reveal]:nth-of-type(2) { animation-range: entry 8% cover 43%; }
		[data-reveal]:nth-of-type(3) { animation-range: entry 16% cover 51%; }
	}
}
@keyframes dw-rise {
	from { opacity: 0; transform: translateY(28px); }
	to   { opacity: 1; transform: none; }
}

/* Reduced motion: gentler, not zero. Movement goes, the fades stay, because
   the fade is what makes the change legible. */
@media (prefers-reduced-motion: reduce) {
	.dw-form-notice { transition: opacity 200ms ease; }
	@starting-style { .dw-form-notice { transform: none; } }
	.wp-element-button { transition: background-color 140ms ease; }
	.wp-element-button:active { transform: none; }
}

/* ─── Scrolling photo band ────────────────────────────────────────────────
   A continuous horizontal strip of her work. The reference site uses this
   shape for press logos; here it carries photography instead, because she has
   the photographs and does not have the press.

   CSS only. The track holds the set twice and translates exactly -50%, so the
   second copy lands where the first began and the loop is seamless.
   `aria-hidden` on the duplicate keeps a screen reader from reading the whole
   set twice. */
.dw-band {
	overflow: hidden;
	background: var(--wp--preset--color--ink);
}
.dw-band__track {
	display: flex;
	width: max-content;
	gap: 4px;
	animation: dw-marquee 64s linear infinite;
}
.dw-band__track img {
	display: block;
	height: clamp(200px, 26vw, 340px);
	width: auto;
	object-fit: cover;
}
@keyframes dw-marquee {
	from { transform: translateX(0); }
	to   { transform: translateX(-50%); }
}
/* Let someone stop it to actually look at a room. */
@media (hover: hover) and (pointer: fine) {
	.dw-band:hover .dw-band__track { animation-play-state: paused; }
}
/* Reduced motion: no crawl. It becomes a strip you scroll yourself, which is
   the same content without the movement. */
@media (prefers-reduced-motion: reduce) {
	.dw-band { overflow-x: auto; }
	.dw-band__track { animation: none; }
}

/* ─── Sticky header ───────────────────────────────────────────────────────
   The reference site pins an opaque bar and lets content scroll up and
   disappear behind it. Its header is transparent but contains a solid white
   layer — the photograph never shows through, which is why the nav stays
   legible over any image.

   Ours is already white. The problem is height: at 205px a pinned header eats
   a quarter of every screen, so it contracts as you scroll. Done with a
   scroll-driven timeline rather than a JS scroll listener — no listener firing
   on every frame, and a browser without support simply gets a header that
   pins at full height, which is still correct. */
/* The <header> element itself must be the sticky one. Sticking the inner div
   does nothing: a sticky box is constrained to its parent, and that parent is
   only as tall as the header, so it has nowhere to travel. */
.wp-site-blocks > header {
	position: sticky;
	top: 0;
	z-index: 20;
}

@supports (animation-timeline: scroll()) {
	@media (prefers-reduced-motion: no-preference) {
		.dw-header {
			animation: dw-header-shrink linear both;
			animation-timeline: scroll(root block);
			animation-range: 0 320px;
		}
		/* Not the animated lockup: that one shrinks as a whole (below), because
		   shrinking only the image would leave the drawn house at full size
		   while the wordmark under it got smaller. */
		.dw-header .wp-block-site-logo img:not(.dw-logo__word) {
			animation: dw-logo-shrink linear both;
			animation-timeline: scroll(root block);
			animation-range: 0 320px;
			transform-origin: left center;
		}
		.dw-header .dw-logo {
			animation: dw-logo-shrink linear both;
			animation-timeline: scroll(root block);
			animation-range: 0 320px;
		}
	}
}
@keyframes dw-header-shrink {
	from { padding-top: 22px; padding-bottom: 18px; }
	to   { padding-top: 10px; padding-bottom: 10px; }
}
@keyframes dw-logo-shrink {
	from { width: 170px; }
	to   { width: 96px; }
}

/* A hairline appears once you have scrolled, so the pinned bar has an edge
   against the photography passing underneath it. */
.dw-header::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	bottom: 0;
	height: 1px;
	background: var(--wp--preset--color--sesame);
	opacity: 0;
}
@supports (animation-timeline: scroll()) {
	.dw-header::after {
		animation: dw-header-rule linear both;
		animation-timeline: scroll(root block);
		animation-range: 0 320px;
	}
}
@keyframes dw-header-rule {
	from { opacity: 0; }
	to   { opacity: 1; }
}

/* ─── Hero: photograph with an overlaid card ──────────────────────────────
   Replaces a 1,083px photograph stacked above the headline. In that version
   her first words landed 1,379px down an 813px screen — nearly two screens of
   room before a visitor learned she was a decorator. The card puts the promise
   on the first screen without giving up the full-bleed photograph.

   The card is opaque, not a translucent overlay on the image. A scrim dulls
   the room and makes the type's contrast depend on whichever photograph is
   loaded; an opaque card holds the same 11.3:1 ink-on-white as the rest of
   the site no matter what is behind it. */
.dw-hero{position:relative}
.dw-hero__media{
	position:relative;
	height:clamp(420px,72vh,760px);
	background:var(--wp--preset--color--sesame);
}
.dw-hero__img{
	position:absolute;inset:0;
	width:100%;height:100%;
	object-fit:cover;display:block;
}
.dw-hero__inner{
	position:absolute;inset:0;
	display:flex;align-items:flex-end;
	padding-inline:max(24px, calc((100% - var(--wp--style--global--content-size)) / 2));
	pointer-events:none;
}
.dw-hero__card{
	pointer-events:auto;
	background:var(--wp--preset--color--white);
	max-width:33rem;
	padding:34px 36px 36px;
}
.dw-hero__eyebrow{
	margin:0 0 14px;
	font-size:var(--wp--preset--font-size--label);
	font-weight:700;letter-spacing:.14em;text-transform:uppercase;
	color:var(--wp--preset--color--action);
}
.dw-hero__title{
	margin:0 0 14px;
	font-family:var(--wp--preset--font-family--lora);
	font-size:var(--wp--preset--font-size--display);
	font-weight:400;line-height:1.05;letter-spacing:-.015em;
	color:var(--wp--preset--color--ink);
	text-wrap:balance;
}
.dw-hero__body{
	margin:0 0 22px;
	font-size:var(--wp--preset--font-size--lead);
	line-height:1.55;
	color:var(--wp--preset--color--text-muted);
	max-width:34ch;
}
.dw-hero__cta{display:inline-block}

/* On a phone the card stops overlaying and simply follows the photograph —
   a card sitting on top of a 420px image leaves the room unreadable. */
@media (max-width:781px){
	.dw-hero__media{height:clamp(280px,46vh,420px)}
	.dw-hero__inner{position:static;padding:0}
	.dw-hero__card{max-width:none;padding:26px 24px 30px}
}

/* ─── "When people call me" ───────────────────────────────────────────────
   Two columns of reasons, separated by space and a hairline rather than by
   cards. Cards here would be five boxes doing the work a gap already does. */
.dw-reasons{
	list-style:none;margin:var(--wp--preset--spacing--section) 0 0;padding:0;
	display:grid;gap:0 var(--wp--preset--spacing--grid);
	grid-template-columns:1fr;
}
@media (min-width:781px){ .dw-reasons{grid-template-columns:1fr 1fr} }
.dw-reasons li{
	padding:22px 0;
	border-top:1px solid var(--wp--preset--color--sesame);
}
.dw-reasons h3{
	margin:0 0 6px;
	font-family:var(--wp--preset--font-family--lora);
	font-size:var(--wp--preset--font-size--card);
	font-weight:600;line-height:1.25;
	color:var(--wp--preset--color--ink);
}
.dw-reasons p{
	margin:0;
	font-size:var(--wp--preset--font-size--small);
	line-height:1.55;
	color:var(--wp--preset--color--text-muted);
	max-width:44ch;
}

/* ─── Project guide ───────────────────────────────────────────────────────
   Sits on snow rather than sesame so it reads as an aside to the page, not as
   another content band. The cover is drawn in CSS: a stand-in for a real cover
   is still a stand-in, and a drawn one cannot be mistaken for a finished book. */
.dw-guide{
	background:var(--wp--preset--color--snow);
	/* No border-top, for the same reason as .d3-work: a sesame hairline is
	   1.25:1 against snow. The change of ground marks the boundary. */
	padding-block:var(--wp--preset--spacing--section);
	padding-inline:24px;
}
.dw-guide__inner{
	max-width:var(--wp--style--global--content-size);
	margin-inline:auto;
	display:flex;gap:var(--wp--preset--spacing--grid);
	align-items:flex-start;flex-wrap:wrap;
}
.dw-guide__cover{
	flex:0 0 132px;height:172px;
	background:var(--wp--preset--color--ink);
	display:flex;align-items:center;justify-content:center;
	color:var(--wp--preset--color--white);
	font-family:var(--wp--preset--font-family--lora);
	font-size:19px;line-height:1.25;text-align:center;
	border-bottom:5px solid var(--wp--preset--color--action);
}
/* Spacing here is flex gap, not margins. The guide renders inside the .d3
   wrapper on the home page, and `.d3 p{margin:0}` (0,1,1) outranks this
   block's single-class rules (0,1,0) — every margin below was computing to
   zero, which is why the pitch and the form ran together. gap cannot be
   zeroed by a margin reset; the two rules that need a different value are
   scoped to .dw-guide so they outrank it. */
.dw-guide__body{
	flex:1 1 22rem;min-width:0;
	display:flex;flex-direction:column;gap:10px;
}
/* a button in the ready state must not stretch to the column width */
.dw-guide__body > .wp-element-button{align-self:flex-start}
/* the group boundary: the pitch ends here and the form begins */
.dw-guide .dw-guide__lead{margin-block-end:22px}
.dw-guide .dw-guide__pending{margin-block-end:6px}
.dw-guide__eyebrow{
	margin:0 0 8px;font-size:var(--wp--preset--font-size--label);
	font-weight:700;letter-spacing:.14em;text-transform:uppercase;
	color:var(--wp--preset--color--action);
}
.dw-guide__body h2{
	margin:0 0 10px;
	font-family:var(--wp--preset--font-family--lora);
	font-size:var(--wp--preset--font-size--heading);
	font-weight:400;line-height:1.15;
	color:var(--wp--preset--color--ink);
}
.dw-guide__lead{margin:0 0 16px;max-width:52ch;color:var(--wp--preset--color--ink)}
.dw-guide__pending{
	margin:0 0 16px;max-width:52ch;
	font-size:var(--wp--preset--font-size--small);
	color:var(--wp--preset--color--text-muted);
}
.dw-guide__label{
	display:block;margin-bottom:8px;
	font-size:var(--wp--preset--font-size--label);
	font-weight:700;letter-spacing:.1em;text-transform:uppercase;
	color:var(--wp--preset--color--text-muted);
}
.dw-guide__row{display:flex;gap:10px;flex-wrap:wrap}
.dw-guide__row input{
	flex:1 1 16rem;min-width:0;box-sizing:border-box;
	font-family:var(--wp--preset--font-family--karla);
	font-size:16px;line-height:1.5;
	color:var(--wp--preset--color--ink);
	background:var(--wp--preset--color--white);
	border:1px solid var(--wp--preset--color--sesame);
	border-radius:var(--wp--custom--radius--control);padding:12px 14px;
}
.dw-guide__row input:focus{
	outline:2px solid var(--wp--preset--color--action);outline-offset:1px;
	border-color:var(--wp--preset--color--action);
}
.dw-guide .dw-guide__note{
	margin:16px 0 0;padding:12px 14px;
	font-size:var(--wp--preset--font-size--small);
	border-left:3px solid var(--wp--preset--color--action); /* impeccable-disable-line side-tab -- notice rule, not a card tab: --error recolours it, so the border carries state */
	background:var(--wp--preset--color--white);max-width:46rem;
}
.dw-guide__note--error{border-left-color:#A2452F}

/* ─── Process band ────────────────────────────────────────────────────────
   The numbered band from the approved design: text and a numbered list on the
   left, two stacked photographs on the right, on the warm ground. Her process
   has five steps, not the reference's six — the numbers are the structure, so
   inventing a sixth to fill the grid would be decoration pretending to be
   information. */
.dw-process{
	background:var(--wp--preset--color--sesame);
	padding-block:var(--wp--preset--spacing--section);
	padding-inline:24px;
}
.dw-process__inner{
	max-width:var(--wp--style--global--content-size);
	margin-inline:auto;
	display:grid;gap:var(--wp--preset--spacing--section);
	grid-template-columns:1fr;
}
@media (min-width:900px){
	.dw-process__inner{grid-template-columns:1.45fr 1fr;gap:var(--wp--preset--spacing--grid)}
}
.dw-process__eyebrow{
	margin:0 0 10px;font-size:var(--wp--preset--font-size--label);
	font-weight:700;letter-spacing:.14em;text-transform:uppercase;
	color:var(--wp--preset--color--action);
}
.dw-process__text h2{
	margin:0 0 12px;
	font-family:var(--wp--preset--font-family--lora);
	font-size:var(--wp--preset--font-size--heading);
	font-weight:400;line-height:1.15;color:var(--wp--preset--color--ink);
	text-wrap:balance;
}
.dw-process__lead{
	margin:0 0 var(--wp--preset--spacing--stack);
	max-width:46ch;color:var(--wp--preset--color--ink);
}
.dw-steps{
	list-style:none;margin:0;padding:0;
	display:grid;gap:18px var(--wp--preset--spacing--grid);
	grid-template-columns:1fr;
}
@media (min-width:600px){ .dw-steps{grid-template-columns:1fr 1fr} }
.dw-steps li{display:flex;gap:12px;align-items:baseline}
.dw-step__n{
	font-family:var(--wp--preset--font-family--lora);
	font-size:var(--wp--preset--font-size--card);
	font-weight:600;color:var(--wp--preset--color--action);
	font-variant-numeric:tabular-nums;flex:none;
}
.dw-step__t{display:flex;flex-direction:column;gap:2px}
.dw-step__t strong{
	font-size:var(--wp--preset--font-size--small);
	font-weight:700;color:var(--wp--preset--color--ink);
}
.dw-step__t em{
	font-style:normal;font-size:var(--wp--preset--font-size--small);
	color:var(--wp--preset--color--text-muted);line-height:1.5;
}
.dw-process__more{margin:var(--wp--preset--spacing--stack) 0 0;font-size:var(--wp--preset--font-size--small)}
.dw-process__pics{display:flex;flex-direction:column;gap:10px}
.dw-process__pics img{
	display:block;width:100%;height:auto;
	aspect-ratio:4/3;object-fit:cover;
}

/* ─── Alternating project rows ────────────────────────────────────────────
   Replaces the three-up grid. A grid shows three thumbnails; rows give each
   project a sentence and room to be a project rather than a tile. */
.dw-rows{
	background:var(--wp--preset--color--white);
	padding-block:var(--wp--preset--spacing--pivot);
	padding-inline:24px;
}
.dw-rows__inner{max-width:var(--wp--style--global--content-size);margin-inline:auto}
.dw-rows__eyebrow{
	margin:0 0 10px;font-size:var(--wp--preset--font-size--label);
	font-weight:700;letter-spacing:.14em;text-transform:uppercase;
	color:var(--wp--preset--color--action);
}
.dw-rows__h{
	margin:0 0 var(--wp--preset--spacing--section);
	font-family:var(--wp--preset--font-family--lora);
	font-size:var(--wp--preset--font-size--heading);
	font-weight:400;line-height:1.15;color:var(--wp--preset--color--ink);
}
.dw-row{
	display:grid;gap:var(--wp--preset--spacing--grid);
	grid-template-columns:1fr;
	align-items:center;
	margin-bottom:var(--wp--preset--spacing--section);
}
@media (min-width:781px){
	.dw-row{grid-template-columns:1.1fr 1fr}
	.dw-row--flip .dw-row__pic{order:2}
	.dw-row--flip .dw-row__text{order:1}
}
.dw-row__pic img{
	display:block;width:100%;height:auto;
	aspect-ratio:3/2;object-fit:cover;
}
.dw-row__meta{
	margin:0 0 6px;font-size:var(--wp--preset--font-size--label);
	font-weight:700;letter-spacing:.12em;text-transform:uppercase;
	color:var(--wp--preset--color--text-muted);
}
.dw-row h3{
	margin:0 0 10px;
	font-family:var(--wp--preset--font-family--lora);
	font-size:var(--wp--preset--font-size--subheading);
	font-weight:600;line-height:1.2;color:var(--wp--preset--color--ink);
}
.dw-row__blurb{margin:0 0 12px;max-width:44ch;color:var(--wp--preset--color--text-muted)}
.dw-row__link{margin:0;font-size:var(--wp--preset--font-size--small)}
.dw-rows__cta{margin:0;text-align:center}

/* ═══ Home — port of Claude Design option 3a ══════════════════════════════
   Every proportion below comes from the handoff. Prototype values were drawn
   on an 800px review card; the handoff's own production figures are used here
   (headline 40->58, section heading 32->42, body 14->17, meta 12-13->14,
   gutter 40->72, section padding 44->80). Eyebrow 11px, nav and buttons 12px
   stay as authored. Radius 0 everywhere, no shadows — both deliberate. */
:root{
	--d3-gutter:72px;
	--d3-pad:80px;
	--d3-line:var(--wp--preset--color--sesame);
}
.d3{ color:var(--wp--preset--color--ink); }
.d3 h1,.d3 h2,.d3 h3,.d3 blockquote{
	font-family:var(--wp--preset--font-family--lora);
	/* 400, not 500: Lora is loaded at 400 and 600, so a 500 request resolves to
	   400 anyway. 400 is also the weight Beauty Awakened sets its headlines at,
	   which is the look Sean asked for. */
	font-weight:400;margin:0;text-wrap:balance;
}
.d3 p{margin:0}
.d3-lead{font-size:17px;font-weight:400;line-height:1.7;color:var(--wp--preset--color--text-muted);max-width:66ch}
.d3-centre{text-align:center}

/* buttons — 12px, .12em, rounded. The handoff drew these square; rounding them
   is a deliberate departure Sean asked for, and the radius is one token in
   brand.json so these and the block editor's buttons cannot drift apart (they
   were 0px and 4px). */
.d3-btn{
	display:inline-block;background:var(--wp--preset--color--ink);color:var(--wp--preset--color--white);
	padding:14px 34px;font-size:12px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
	text-decoration:none;border:1px solid var(--wp--preset--color--ink);
	border-radius:var(--wp--custom--radius--control);
	transition:background-color 160ms ease,color 160ms ease,border-color 160ms ease;
}
.d3-btn--ghost{background:transparent;color:var(--wp--preset--color--ink)}
/* the portfolio CTA — action green rather than the ink of every other button,
   because it is the one thing this section exists to get clicked.
   White on action is 7.33:1, clearing the AAA bar this project enforces. */
.d3-btn--accent{
	background:var(--wp--preset--color--action);
	border-color:var(--wp--preset--color--action);
	color:var(--wp--preset--color--white);
}
@media (hover:hover) and (pointer:fine){
	.d3-btn:hover{background:var(--wp--preset--color--action);border-color:var(--wp--preset--color--action);color:var(--wp--preset--color--white)}
	/* the accent button already is action, so it darkens to ink instead —
	   matching what the block editor's buttons do. White on ink is 11.32:1. */
	.d3-btn--accent:hover{background:var(--wp--preset--color--ink);border-color:var(--wp--preset--color--ink)}
}

/* 2 · hero — 390px proto -> 620px; card absolute, 40px in, flush bottom */
.d3-hero{position:relative}
/* Three photographs cross-fading, the way London Design Group's hero runs:
   three frames, roughly five seconds each, dissolve rather than slide. Pure CSS
   — a stacked-opacity crossfade needs no JavaScript, and a CSS animation keeps
   running off the main thread while the rest of the page is still loading.

   The media box owns the height so the section does not collapse when every
   frame is absolutely positioned. Only opacity animates, so this stays on the
   compositor. */
.d3-hero__media{position:relative;height:clamp(400px,62vh,620px)}
.d3-hero__img{
	position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:cover;
	opacity:0;
	animation:dw-hero-cross 18s linear infinite;
}
/* Frame one runs a second into its own cycle so it is already at full opacity
   on load — starting it at 0% would open the page on a blank hero. Frames two
   and three wait their turn on positive delays, held invisible by the base
   opacity above. */
.d3-hero__img:nth-child(1){animation-delay:-1s}
.d3-hero__img:nth-child(2){animation-delay:5s}
.d3-hero__img:nth-child(3){animation-delay:11s}
@keyframes dw-hero-cross{
	0%     {opacity:0}
	5.5%   {opacity:1}   /* ~1s dissolve in  */
	33.3%  {opacity:1}   /* ~5s hold         */
	38.8%  {opacity:0}   /* ~1s dissolve out */
	100%   {opacity:0}
}
/* The whole effect is movement, so there is no gentler version — reduced motion
   gets the first photograph, still. */
@media (prefers-reduced-motion: reduce){
	.d3-hero__img{animation:none}
	.d3-hero__img:nth-child(1){opacity:1}
}

/* Video hero. The <video> sits on top of the photograph in the same box and
   crops the same way; the photograph underneath is the fallback, so it must not
   run the crossfade when only one frame is present. */
.d3-hero--video .d3-hero__img{animation:none;opacity:1}
/* The hero box is about 3:1 at desktop widths and the footage is 16:9, so
   `cover` shows a horizontal band through the middle of each frame — 58% of its
   height — and throws the rest away. Centred, that band cut the tops of heads
   off in five of the clip's seven scenes (every one with people in it): they
   are framed with headroom, and the headroom is exactly what a centred crop
   discards.

   30% moves the band up to 12.5%–70.9% of the frame. Checked against a frame
   from each of the seven scenes: heads clear the top edge in all five people
   scenes, and the two empty-room scenes still hold their furniture. One value
   serves the whole clip, so the framing stays in CSS instead of being baked
   into the file — which also keeps the phone crop, where the box is nearly
   square and the band is horizontal instead, untouched. */
.d3-hero__video{
	position:absolute;inset:0;display:block;width:100%;height:100%;
	object-fit:cover;object-position:center 30%;
	background:var(--wp--preset--color--sesame);
}
/* Reduced motion: hide the video and let the photograph through. The script
   never starts playback in this case either, so nothing is downloaded — hiding
   a playing video would be motion the reader asked not to have, paid for. */
@media (prefers-reduced-motion: reduce){
	.d3-hero__video{display:none}
}
.d3-hero__wrap{
	position:absolute;inset:0;display:flex;align-items:flex-end;
	padding-inline:max(24px,calc((100% - 1280px)/2 + var(--d3-gutter)));
	pointer-events:none;
}
/* Frosted, so the photograph reads through the card instead of being cut out of
   it. This is the one place on the site where the ground is a PHOTOGRAPH and not
   a token, so the contrast gate cannot check it — the numbers here were measured
   off the three hero frames themselves, at the card's actual rect, against the
   darkest 5% of pixels behind it rather than the average.

   Measured worst case 7.46:1 across all three frames, so this holds the same AAA
   the rest of the site holds, on the darkest frame, with margin.

   Two findings worth keeping, because both are the opposite of the intuition:

   1. brightness() on the backdrop beats contrast() flattening HERE. Earlier
      versions used contrast(.32) to lift the photo's shadows so a thinner veil
      would carry text. It works, but flattening destroys the tonal range that
      makes the photo legible as a photo — so the card showed LESS image, not
      more. At this veil weight no flattening is needed at all, and dropping it
      leaves the picture intact: measured 11.6 of tonal spread inside the card
      against 10.3 for the thin-veil flattened version that looked emptier.

   2. Heavy blur is what makes a translucent card look opaque. This shipped at
      28px and read as a plain white box, because blur removes the image's shape
      while leaving its colour. 6px softens the noise under the type without
      erasing what is behind it — and the blur was buying almost nothing for
      legibility anyway, about 0.01 of luminance, since the dark areas here are
      broad shadows rather than fine detail that averages away.

   Text is ink, not text-muted: muted grey measures 4.48:1 here and never cleared
   AAA at any veil worth having. */
.d3-hero__card{
	pointer-events:auto;
	background:rgba(255,255,255,.92);   /* fallback: see @supports below */
	max-width:560px;padding:44px 48px 48px;
}
/* Only go translucent where the backdrop can actually be blurred. Without
   backdrop-filter the card would be a plain .34 white sheet over a dark
   photograph and the text would fail — so the no-support path stays near-solid
   and legible, which is the house rule: the fallback is the content showing. */
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
	.d3-hero__card{
		background:rgba(255,255,255,.76);
		-webkit-backdrop-filter:blur(6px) brightness(1.25);
		backdrop-filter:blur(6px) brightness(1.25);
	}
}
/* Someone who has asked the OS for less transparency gets the solid card. */
@media (prefers-reduced-transparency: reduce){
	.d3-hero__card{
		background:var(--wp--preset--color--white);
		-webkit-backdrop-filter:none;backdrop-filter:none;
	}
}
.d3-hero__card h1{font-size:clamp(34px,4.6vw,58px);line-height:1.06;letter-spacing:-.015em;margin-bottom:14px}
.d3-hero__card p{font-size:17px;line-height:1.7;color:var(--wp--preset--color--ink);margin-bottom:26px;max-width:44ch}

/* ---------- VIDEO HERO: the Young Bucks scrim, inverted to light ----------
   Young Bucks puts a 105deg gradient over its hero footage and sets the copy
   straight onto it; theirs is slate over a light photograph, hers is white over
   footage that is already bright. So the card stops being a box here — the
   scrim is the ground, and the picture keeps running underneath all the way
   across instead of being cut out behind a panel.

   Measured, not guessed, exactly as the card above was: ink #3B3944 composited
   through this gradient at the copy's rect, against EVERY SECOND of the clip
   (26 frames) rather than an average or a single still. Worst case 7.54:1,
   which is the AAA the frosted card held (7.46:1) — so the treatment changes
   and the legibility does not.

   The shape matters more than the opening value. A LINEAR ramp across the whole
   width washed the entire frame to keep one corner legible: at .95/.80/.52 no
   part of the picture was left clear. This profile holds the white where the
   copy actually is (the left ~36%, which is where a 560px block sits at desktop
   widths), then falls off fast and reaches zero by the right edge — so roughly
   the last third of the footage is untouched and the room reads as video rather
   than as a wash. Earlier attempts: .92/.74/.46 linear measured 6.43:1, AA but
   below the AAA the rest of the site keeps. */
/* Only where the copy actually sits ON the footage. Below 1025px
   .d3-hero__wrap goes static and the text drops beneath the hero box, so a
   scrim there would be washing the picture to protect text that has moved
   out from under it — her footage shows at full strength on a phone. */
@media (min-width:1025px){
	.d3-hero--video .d3-hero__media::after{
		content:"";position:absolute;inset:0;pointer-events:none;
		background:linear-gradient(105deg,
			rgba(255,255,255,.92) 0%,
			rgba(255,255,255,.86) 36%,
			rgba(255,255,255,.35) 55%,
			rgba(255,255,255,.05) 75%,
			rgba(255,255,255,0)  100%);
	}
}
.d3-hero--video .d3-hero__card{
	background:none;
	-webkit-backdrop-filter:none;backdrop-filter:none;
	padding-inline:0;
}
/* Someone who asked the OS for less transparency gets the solid card back, on
   top of the scrim — the same escape hatch the frosted card has. */
@media (prefers-reduced-transparency: reduce){
	.d3-hero--video .d3-hero__card{
		background:var(--wp--preset--color--white);
		padding-inline:48px;
	}
}

/* shared section frame */
.d3-benefits,.d3-band,.d3-why,.d3-meet,.d3-work,.d3-quote,.d3-cta{
	padding:var(--d3-pad) var(--d3-gutter);
}
/* .d3-meet joins these: it was full-bleed with the portrait hard against the
   viewport's left edge, which put all of its weight on one side of the page. */
.d3-benefits,.d3-why,.d3-cta,.d3-meet{max-width:1280px;margin-inline:auto}

/* 3 · three benefits — 3 cols, gap 30->48, centred */
/* Hairline separators between the three. The 48px gap goes to zero and each
   column carries 24px of padding instead, so the rule sits exactly midway
   between the columns rather than hard against the next one — a border on a
   gapped grid item lands at its edge, not in the middle of the gap. Sesame on
   white, the same hairline the project rows and the step rows already use. */
.d3-benefits{display:grid;grid-template-columns:repeat(3,1fr);gap:0;text-align:center}
.d3-benefits > div{padding-inline:24px}
.d3-benefits > div + div{border-inline-start:1px solid var(--d3-line)}
.d3-benefits strong{display:block;font-family:var(--wp--preset--font-family--lora);font-size:20px;font-weight:600;margin-bottom:8px}
.d3-benefits p{font-size:17px;line-height:1.7;color:var(--wp--preset--color--text-muted)}

/* 4 · process band — flex 1.5 / 1, gap 32->52, on the warm ground */
/* stretch, not flex-start: the single photograph matches the height of the
   text beside it, so neither column ends in blank space */
.d3-band{background:var(--wp--preset--color--sesame);display:flex;gap:52px;align-items:stretch}
.d3-band__text{flex:1.5}
.d3-band__pics{flex:1;display:flex;flex-direction:column;gap:16px;min-height:100%}
.d3-band__pics img{display:block;width:100%;height:100%;min-height:340px;object-fit:cover}
.d3-band h2{font-size:42px;line-height:1.15;margin-bottom:12px}
.d3-band .d3-lead{color:var(--wp--preset--color--ink);margin-bottom:32px}
/* One column, not two. The hairline below is a path, and a path has to be
   linear to read as one — across two columns it would zigzag. Single column
   also settles the reading order the numerals used to state outright. */
.d3-steps{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:1fr;gap:0}
.d3-steps li{position:relative;display:flex;gap:14px;align-items:center;padding-bottom:22px}
.d3-steps li:last-child{padding-bottom:0}
/* The connector: a 1px rule from the bottom of one icon to the top of the next,
   centred on the 28px icon column, with a chevron at the far end so it reads as
   an arrow rather than a tick. Action green at .55 lands about 2.3:1 on the
   sesame band — visible as a thread, quiet enough that it never competes with
   the icons it joins. Both halves are pseudo-elements, so the whole thing is
   decorative and never reaches the accessibility tree. */
.d3-steps li:not(:last-child)::before{
	content:"";
	position:absolute;left:14px;transform:translateX(-50%);
	top:28px;bottom:0;width:1px;
	background:var(--wp--preset--color--action);opacity:.55;
}
/* The arrowhead: a 5px square rotated 45deg showing only two edges, so it is
   drawn at the same 1px as the shaft rather than as a filled triangle. */
.d3-steps li:not(:last-child)::after{
	content:"";
	position:absolute;left:14px;bottom:1px;
	width:5px;height:5px;
	border-right:1px solid var(--wp--preset--color--action);
	border-bottom:1px solid var(--wp--preset--color--action);
	transform:translateX(-50%) rotate(45deg);
	opacity:.55;
}
/* Icons rather than numerals here. They carry what each step IS; the order is
   still real, so the list stays an <ol> and the lead sentence still says "from
   the first visit to install day". Action green on the sesame band is 5.3:1,
   well past the 3:1 a non-text mark needs. */
.d3-steps .dw-ic{color:var(--wp--preset--color--action);flex:none}
.d3-steps em{font-style:normal;font-size:17px;line-height:1.4}

/* 5 · when people call me — 2-col grid, gap 22->36 */
.d3-why h2{font-size:42px;line-height:1.15}
.d3-why .d3-lead{margin:10px 0 0}
.d3-why__grid{display:grid;grid-template-columns:1fr 1fr;gap:36px;margin-top:38px}
.d3-why__grid>div{display:flex;gap:16px;align-items:flex-start}
.d3-why__grid strong{display:block;font-size:17px;font-weight:700;margin-bottom:4px;color:var(--wp--preset--color--ink)}
.d3-why__grid em{font-style:normal;font-size:14px;line-height:1.6;color:var(--wp--preset--color--text-muted);display:block}
.d3-why__cta{display:flex;align-items:center;justify-content:center}

/* icons — drawn inline rather than loaded from Google's icon font, so the
   privacy policy's claim that reading the site reports your visit to nobody
   stays true. Sized and coloured as the design specifies: 38px on the
   benefits row, 28px beside each reason, in the accent. */
.dw-ic{color:var(--wp--preset--color--action);flex:none;display:block}
.d3-benefits .dw-ic{margin:0 auto 16px}

/* 6 · meet — portrait 480 wide at its own ratio, flush left, text flex 1 */
/* The portrait is shown whole. It used to be forced into a fixed ratio with
   object-fit:cover, which cropped it — and because height:100% beat the
   declared aspect-ratio once the flex row stretched this column, the amount
   cropped depended on how tall the text beside it happened to run. The frame
   now takes the photograph's ratio instead of imposing one, so nothing is cut,
   and the row centres the two columns against each other. */
.d3-meet{display:flex;gap:0;padding:0;border-top:1px solid var(--d3-line);align-items:center}
.d3-meet__pic{flex:none;width:480px}
.d3-meet__pic img{display:block;width:100%;height:auto}
.d3-meet__text{flex:1;padding:58px var(--d3-gutter)}
.d3-meet h2{font-size:42px;line-height:1.15;margin-bottom:14px}
.d3-meet p{font-size:17px;line-height:1.7;color:var(--wp--preset--color--text-muted);max-width:60ch}
.d3-meet__quote{
	margin:22px 0 26px !important;padding-left:18px;
	border-left:2px solid var(--wp--preset--color--action);
	font-family:var(--wp--preset--font-family--lora);font-size:20px;line-height:1.45;
	color:var(--wp--preset--color--ink) !important;
}

/* 7 · project rows — column, gap 22->40, image left, alternating */
/* The project rows sit on the same warm ground as the project guide, so the
   page reads white - sesame - white - snow - ink - white rather than one long
   white run. Full-bleed with an inner wrapper, the way every other coloured
   band here works; painting the background straight onto the old centred
   1280px box would have left white gutters either side. */
.d3-work{
	background:var(--wp--preset--color--snow);
	/* No border-top. The hairline was the only thing marking this boundary while
	   the section was white on white; against snow it measures 1.25:1 and reads
	   as nothing. The change of ground is the boundary now. */
}
.d3-work__inner{max-width:1280px;margin-inline:auto}
.d3-work h2{font-size:42px;line-height:1.15}
.d3-work .d3-lead{margin:10px 0 0}
.d3-work__rows{
	display:flex;flex-direction:column;gap:40px;margin:42px 0 0;
	/* Two variables drive both the normal and flipped rows. They were separate
	   literals once, and the flipped row kept its old width while the others
	   changed — the middle photo rendered at 888px against its neighbours' 352px.
	
	   The columns are 220fr/145fr: the photo column's proportion is the photo's
	   own aspect ratio. This is her portfolio — her photography is the evidence
	   and the copy is its caption — so the evidence gets the dominant column. At
	   the old 352px the caption column was both wider than the photograph and
	   278px of it sat empty.
	
	   The caption has a floor because a pure ratio keeps shrinking it with the
	   photo: 276px, roughly 32 characters, by the time the rows collapse at
	   781px. The photograph absorbs the shrink instead — a smaller photo still
	   reads, cramped copy does not. */
	--d3-pic:minmax(0,220fr);
	--d3-txt:minmax(400px,145fr);
}
.d3-row{display:grid;grid-template-columns:var(--d3-pic) var(--d3-txt);gap:40px;align-items:center}
.d3-row__pic img{display:block;width:100%;height:auto;aspect-ratio:220/145;object-fit:cover}
.d3-row h3{font-size:26px;line-height:1.2;margin-bottom:10px;font-weight:600}
.d3-row p{font-size:17px;line-height:1.7;color:var(--wp--preset--color--text-muted);max-width:52ch}
.d3-row a{color:var(--wp--preset--color--action);white-space:nowrap}
/* Flipping with order alone moved the image into the 1fr column, so the middle
   row's photograph came out 888px against 352px on its neighbours. The column
   template has to reverse with it — the fixed width stays on the image. */
.d3-row--flip{grid-template-columns:var(--d3-txt) var(--d3-pic)}
.d3-row--flip .d3-row__pic{order:2}
.d3-row--flip>div:not(.d3-row__pic){order:1}
/* Centred in the gap it sits in: the space above equals the band's own bottom
   padding, so the button is midway between the last photograph and the next
   section rather than tucked up under the photo.
   Two classes because .d3-centre is a <p> and `.d3 p{margin:0}` (0,1,1) would
   otherwise zero this — the same reset that had silently killed the project
   guide's spacing. */
.d3 .d3-centre{margin-top:var(--d3-pad)}

/* 8 · quote band — dark, centred, quote 26->34, max 520->830 */
.d3-quote{background:var(--wp--preset--color--ink);color:var(--wp--preset--color--white);text-align:center}
.d3-quote blockquote{font-size:34px;line-height:1.35;max-width:830px;margin-inline:auto;color:var(--wp--preset--color--white)}
.d3-quote__by{
	font-size:11px;letter-spacing:.1em;text-transform:uppercase;margin-top:18px;
	/* Alpha rather than opacity:.75 — identical rendering, but opacity on the
	   parent would cap the link below it, so the hover could never brighten. */
	color:rgb(255 255 255 / .75);
}
/* The attribution is the home page's only route into her story now that Meet
   Leslie has gone. Resting underline in currentColor (7.2:1 on ink) so it reads
   as a link without shouting; the two-class selector is deliberate — the global
   `main a` hover sets text-decoration-color to text-muted, which is 1.7:1 here
   and would erase the underline exactly the way it once did on the sesame band. */
.d3-quote .d3-quote__by a{
	color:inherit;
	text-decoration:underline;
	text-decoration-thickness:1px;
	text-underline-offset:4px;
	text-decoration-color:currentColor;
	transition:color 140ms var(--ease-out);
}
@media (hover:hover) and (pointer:fine){
	.d3-quote .d3-quote__by a:hover{
		color:var(--wp--preset--color--white);
		text-decoration-color:currentColor;
	}
}

/* 9 · closing CTA — heading 34->46, sub max 460->740 */
.d3-cta{text-align:center}
.d3-cta h2{font-size:46px;line-height:1.1}
.d3-cta p{font-size:17px;line-height:1.7;color:var(--wp--preset--color--text-muted);max-width:740px;margin:14px auto 26px}

/* Between the mobile stack and a comfortable desktop the text column is only
   ~400px of usable measure, where a 42px headline wraps to four lines and the
   column outgrows the portrait beside it. Give the words more room and less
   size, so the two columns stay close to the same height. */
@media (min-width:1025px) and (max-width:1200px){
	.d3-meet__text{padding-inline:44px}
	.d3-meet h2{font-size:34px}
}

/* responsive — the handoff left mobile undesigned; these follow its notes */
@media (max-width:1024px){
	:root{--d3-gutter:40px;--d3-pad:56px}
	.d3-band{flex-direction:column}
	.d3-meet{flex-direction:column}
	/* Was aspect-ratio:16/10, which cut 22% off the top of a portrait — the
	   head came off above the eyebrows on every screen under 1024. Uncropped
	   at full width would instead run 1536px tall here, so cap the width and
	   centre it. */
	.d3-meet__pic{width:100%;max-width:400px;margin-inline:auto}
}
@media (max-width:781px){
	:root{--d3-gutter:24px;--d3-pad:44px}
	.d3-benefits{grid-template-columns:1fr;gap:32px}
	/* Stacked, so the rule turns horizontal. The 32px gap sits above it and 32px
	   of padding below, which keeps it centred between the two blocks. */
	.d3-benefits > div{padding-inline:0}
	.d3-benefits > div + div{
		border-inline-start:0;
		border-block-start:1px solid var(--d3-line);
		padding-top:32px;
	}
	.d3-why__grid{grid-template-columns:1fr;gap:26px}
	.d3-row,.d3-row--flip .d3-row__pic{grid-template-columns:1fr;order:0}
	.d3-hero__wrap{position:static;padding:0}
	.d3-hero__card{max-width:none;padding:30px 24px 34px}
	/* Size the media box, not the photograph inside it. This rule predates the
	   hero becoming a stack of three cross-fading frames: back then the img WAS
	   the hero, and now it is absolutely positioned inside .d3-hero__media. So
	   this was shrinking the picture to 44vh while its container stayed at 62vh,
	   leaving ~146px of blank white under the photo on a phone. */
	.d3-hero__media{height:clamp(260px,44vh,380px)}
	.d3-quote blockquote{font-size:26px}
	.d3-cta h2{font-size:34px}
	/* A 24px control is the bare AA minimum and a poor thumb target; the icon
	   stays 24px and the padding does the work. */
	.dw-header .wp-block-navigation__responsive-container-open{
		padding:10px;margin:-10px;
	}
	/* Stacked link lists need a real target height. Links inline in a sentence
	   are exempt and keep their natural size. */
	.d3-foot__col li a,.wp-block-navigation__responsive-container-content a{
		display:inline-block;padding-block:4px;
	}
}

/* ─── Footer — the handoff's dark 4-column band ───────────────────────────
   Proto is 30px/40px on the dark ground, four columns space-between, 12px
   weight-300. Scaled to the same gutter as the rest of the port. Three columns
   plus the mark, not four: the design's fourth is a social icon row and she
   has no accounts — the same call as the press strip and the credential
   badges. Inventing profiles to fill a column is worse than a shorter row.

   The mark is her ordinary logo reversed with a filter. The file is 96%
   transparent, so brightness(0) invert(1) turns the linework white cleanly
   without needing a separate reversed asset to keep in sync. */
.d3-foot{
	background:var(--wp--preset--color--ink);
	color:var(--wp--preset--color--white);
	padding:56px var(--d3-gutter, 72px);
}
.d3-foot__inner{
	max-width:1280px;margin-inline:auto;
	display:flex;justify-content:space-between;gap:48px;flex-wrap:wrap;
	align-items:flex-start;
}
.d3-foot__mark{flex:0 0 84px}
.d3-foot__mark img{
	display:block;width:84px;height:auto;
	filter:brightness(0) invert(1);opacity:.92;
}
.d3-foot__col{flex:1 1 190px;min-width:0}
.d3-foot__col--last{flex:1 1 250px}
.d3-foot__col p{
	margin:0 0 8px;font-size:14px;line-height:1.7;
	color:var(--wp--preset--color--white);opacity:.86;
}
.d3-foot__col a{color:inherit;text-decoration:none;border-bottom:1px solid transparent}
@media (hover:hover) and (pointer:fine){
	.d3-foot__col a:hover{border-bottom-color:currentColor}
}
.d3-foot__legal{font-size:12px;opacity:.62;margin-top:14px}
@media (max-width:781px){
	.d3-foot{padding:44px 24px}
	.d3-foot__inner{gap:30px}
	.d3-foot__mark{flex:0 0 100%}
}

/* ═══ Interior pages — ports of 5a, 5b, 5e ════════════════════════════════
   Same production scaling as the home page. Where Leslie has less content
   than the reference, the grid shrinks rather than being padded: two service
   tiers not three, three projects not nine, and no filter chips for a
   portfolio that fits on a single row. */

.d3-eyebrow{
	margin:0 0 12px;font-size:11px;font-weight:700;letter-spacing:.18em;
	text-transform:uppercase;color:var(--wp--preset--color--action);
}

/* 5a · split hero — 1.05fr / 1fr, photo min-height 340 -> 540 */
.d3-split{display:grid;grid-template-columns:1.05fr 1fr;align-items:stretch}
.d3-split__text{padding:var(--d3-pad) var(--d3-gutter);display:flex;flex-direction:column;justify-content:center}
.d3-split__text h1{font-size:52px;line-height:1.08;margin-bottom:14px}
.d3-split__text .d3-lead{margin-bottom:26px}
.d3-split__pic img{display:block;width:100%;height:100%;min-height:540px;object-fit:cover}

/* 5a · process — 6 columns, gap 18 -> 28 */
/* Cotton, the lightest of the warm tones. The sesame it used to carry has moved
   down to the budget band below it, so the page steps white -> cotton -> sesame
   rather than jumping straight to the dark. */
.d3-pband{background:var(--wp--preset--color--cotton);padding:var(--d3-pad) var(--d3-gutter)}
.d3-pband h2{font-size:42px;line-height:1.15;max-width:1280px;margin:0 auto}
.d3-psteps{
	max-width:1280px;margin:38px auto 0;
	display:grid;grid-template-columns:repeat(6,1fr);gap:28px;
}
/* Six since 2026-09-23, when the discovery call became the first step. Six
   across 1280 leaves ~190px a column, which the two longest labels wrap
   inside; below 1200 that gets mean, so it halves to 3 + 3 before the 1024
   rule below takes it to two. This block must stay ABOVE that one — both
   match at narrow widths and the later rule is the one that wins. */
@media (max-width:1200px){
	.d3-psteps{grid-template-columns:repeat(3,1fr)}
}
/* Icons only here, no connector. The home band threads its steps with an arrow;
   this row does not, so the sequence rests on the left-to-right reading order
   and the copy. */
.d3-pstep .dw-ic{display:block;color:var(--wp--preset--color--action);margin-bottom:8px}
.d3-pstep strong{display:block;font-size:17px;margin-bottom:4px}
.d3-pstep em{font-style:normal;font-size:14px;line-height:1.6;color:var(--wp--preset--color--text-muted)}

/* 5a · tiers — 1px border, 24px padding -> 38px, hairline-topped list */
.d3-tiers{padding:var(--d3-pad) var(--d3-gutter);max-width:1280px;margin-inline:auto}
.d3-tiers h2{font-size:42px;line-height:1.15}
.d3-tiers__grid{display:grid;grid-template-columns:1fr 1fr;gap:32px;margin-top:38px}
.d3-tier{border:1px solid var(--wp--preset--color--sesame);padding:38px}
.d3-tier--lead{background:var(--wp--preset--color--cotton)}
.d3-tier h3{font-size:28px;line-height:1.2;font-weight:600;margin:14px 0 10px}
.d3-tier__price{
	font-family:var(--wp--preset--font-family--lora);font-size:34px;font-weight:600;
	line-height:1;display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:12px;
}
.d3-tier__price span{
	font-family:var(--wp--preset--font-family--karla);font-size:14px;font-weight:400;
	color:var(--wp--preset--color--text-muted);
}
.d3-tier__desc{font-size:17px;line-height:1.7;color:var(--wp--preset--color--text-muted);margin-bottom:18px}
.d3-tier ul{
	list-style:none;margin:0;padding:18px 0 0;
	border-top:1px solid var(--wp--preset--color--sesame);
	font-size:14px;line-height:2;
}

/* 5a · budget band — dark, flex 1.4 / 1, bordered box */
/* Olive, not ink. The footer is ink, and with a white CTA between them the page
   read white / cotton / ink / white / ink — the same slab twice, which is what
   made the band feel like a repeat rather than a section.
   Midnight was the obvious swap and the wrong one: 1.65:1 from ink, so it reads
   as the same dark, just muddier. Sesame carries text well but is 1.16:1 against
   the cotton band directly above, and that boundary would disappear.
   Action is 6.18:1 from cotton above and separates from the footer by hue rather
   than luminance — the ratio to ink is only 1.54:1, but olive against charcoal is
   unmistakable to the eye, and contrast ratio measures lightness, not colour.
   White on it is 7.33:1, so the heading, the body and the white panel all hold. */
.d3-budget{
	background:var(--wp--preset--color--action);color:var(--wp--preset--color--white);
	padding:var(--d3-pad) var(--d3-gutter);display:flex;gap:52px;align-items:center;
}
.d3-budget__text{flex:1.4;max-width:640px}
.d3-budget h2{font-size:38px;line-height:1.2;margin-bottom:12px;color:var(--wp--preset--color--white)}
.d3-budget p{font-size:17px;line-height:1.7;opacity:.88}
/* Filled white and rounded on BOTH bands, not just the sesame one. The fill
   defines the panel, so the border comes off — an edge and a fill drawing the
   same shape is one line too many. It borrows the control radius rather than
   introducing a second value: the site has one radius, and one is the point.

   The colour is set on the box, not on the paragraphs: .d3-budget paints the
   band white-on-ink, and without this the panel would inherit that straight
   through the new white fill. Ink on white is 11.32:1. */
.d3-budget__box{
	flex:1;background:var(--wp--preset--color--white);
	color:var(--wp--preset--color--ink);
	border:0;border-radius:var(--wp--custom--radius--control);
	padding:34px;font-size:14px;line-height:2;
}
.d3-budget__box p{display:flex;gap:12px;align-items:center;margin-bottom:10px;opacity:1}
.d3-budget__box .dw-ic{color:var(--wp--preset--color--ink);opacity:1}

/* 5b · page intro + project grid — 3 columns, gap 16 -> 26 */
.d3-pageintro{padding:var(--d3-pad) var(--d3-gutter) 0;max-width:1280px;margin-inline:auto;text-align:center}
.d3-pageintro--left{text-align:left}
.d3-pageintro h1{font-size:52px;line-height:1.08}
.d3-pageintro .d3-lead{margin:14px auto 0}
.d3-pageintro--left .d3-lead{margin-inline:0}
/* Two up, not three. With four projects a 3-column grid leaves one tile alone
   on a second row; 2x2 fills both rows and gives each photograph half the
   width instead of a third. */
.d3-grid{
	padding:44px var(--d3-gutter) var(--d3-pad);max-width:1280px;margin-inline:auto;
	display:grid;grid-template-columns:repeat(2,1fr);gap:26px 36px;
}
.d3-tile img{display:block;width:100%;height:auto;aspect-ratio:3/2;object-fit:cover}
/* The frame clips, so the photograph can grow inside it on hover without
   nudging the tiles below. */
.d3-tile__frame{display:block;overflow:hidden;margin-bottom:16px}
.d3-tile__link,.d3-row__link{display:block;color:inherit;text-decoration:none}
/* The home project rows get the same treatment as the portfolio tiles, so the
   photograph behaves the same way wherever it is the thing you click. */
.d3-row__pic{overflow:hidden}
@media (hover:hover) and (pointer:fine){
	/* Hover feedback, the way the reference does it: the image grows a little to
	   say it is the thing you are about to click. 1.04 and 400ms — enough to
	   read as deliberate on a large photograph, small enough not to draw the eye
	   while someone is just moving the pointer across the grid. transform only,
	   so it stays on the compositor. */
	.d3-tile__link img,.d3-row__link img{transition:transform 400ms var(--ease-out)}
	.d3-tile__link:hover img,.d3-row__link:hover img{transform:scale(1.04)}
	.d3-tile__link:hover h3{text-decoration:underline;text-underline-offset:4px}
}
.d3-tile__link:focus-visible,.d3-row__link:focus-visible{outline:2px solid var(--wp--preset--color--action);outline-offset:3px}
@media (prefers-reduced-motion: reduce){
	.d3-tile__link img,.d3-row__link img{transition:none}
	.d3-tile__link:hover img,.d3-row__link:hover img{transform:none}
}

/* Nav tabs and the "See the project" links: a 2px rule under the words that is
   transparent at rest and lights up in the action olive, with the text moving to
   the same olive. Lifted from the Young Bucks build — whose header comment reads
   "Leslie Brooks pattern", so the debt runs both ways.

   The project links lose their default underline for this. That is a real
   trade: an underline is the strongest link affordance there is. It is
   acceptable HERE and nowhere else on the site because those links are already
   set in the olive that nothing else in body copy uses, and they end in an
   arrow — two signals carrying what the underline was carrying alone. A link in
   ordinary prose would keep its underline.

   Hover is gated behind a real pointer. On touch, :hover latches after a tap and
   the rule would stay lit under whatever was last touched, which reads as the
   current page and is a lie. */
.wp-block-navigation-item__content,
.d3-row p a,
.dw-row__link a{
	text-decoration:none;
	border-bottom:2px solid transparent;
	transition:color 160ms var(--ease-out), border-bottom-color 160ms var(--ease-out);
}
/* The doubled class on the nav selectors is not a typo. Core ships
       .wp-block-navigation .wp-block-navigation-item__content.wp-block-navigation-item__content{color:inherit}
   and repeats the class on purpose to reach (0,3,0). A single-class rule here
   loses the COLOUR and keeps the BORDER, so the effect half-works and looks
   like a design choice rather than a cascade loss. Matching core's own doubling
   is the least surprising way past it — an !important would win too and would
   be the next person's problem. */
@media (hover:hover) and (pointer:fine){
	.wp-block-navigation .wp-block-navigation-item__content.wp-block-navigation-item__content:hover,
	.d3-row p a:hover,
	.dw-row__link a:hover{
		color:var(--wp--preset--color--action);
		border-bottom-color:var(--wp--preset--color--action);
	}
}
/* Keyboard users get the same rule, not only a focus ring — the ring says
   "focused", the rule says "this is the thing you are about to open". */
.wp-block-navigation .wp-block-navigation-item__content.wp-block-navigation-item__content:focus-visible,
.d3-row p a:focus-visible,
.dw-row__link a:focus-visible{
	color:var(--wp--preset--color--action);
	border-bottom-color:var(--wp--preset--color--action);
	outline:2px solid var(--wp--preset--color--action);
	outline-offset:4px;
}
/* The page you are on stays marked, which is the half of this that survives on a
   phone where there is no hover at all. Jade rather than action so it reads as
   state, not as something you can click to go somewhere new. WordPress emits the
   aria-current itself; nothing had ever styled it. */
.wp-block-navigation .wp-block-navigation-item__content.wp-block-navigation-item__content[aria-current="page"]{
	color:var(--wp--preset--color--action);
	border-bottom-color:var(--wp--preset--color--jade);
}
@media (prefers-reduced-motion: reduce){
	.wp-block-navigation-item__content,
	.d3-row p a,
	.dw-row__link a{transition:none}
}

/* Project pages — header, then the photographs */
.d3-projhead{padding:var(--d3-pad) var(--d3-gutter) 0;max-width:1280px;margin-inline:auto}
.d3-projhead h1{font-size:clamp(34px,4.2vw,52px);line-height:1.06;letter-spacing:-.015em;margin-bottom:16px}
.d3-projhead__meta{
	font-size:13px;letter-spacing:.14em;text-transform:uppercase;
	color:var(--wp--preset--color--action);
}
.d3 .d3-projhead__meta{margin-bottom:4px}
.d3-projhead__intro{font-size:17px;line-height:1.7;color:var(--wp--preset--color--text-muted);max-width:62ch}
.d3 .d3-projhead__intro{margin-top:18px}
.d3-projshots{
	display:flex;flex-direction:column;gap:var(--d3-pad);
	padding:var(--d3-pad) var(--d3-gutter);max-width:1280px;margin-inline:auto;
}
.d3-shot{margin:0}
.d3-shot img{display:block;width:100%;height:auto}
.d3-shot--pair{display:grid;grid-template-columns:1fr 1fr;gap:var(--d3-gutter)}
@media (max-width:781px){
	.d3-shot--pair{grid-template-columns:1fr;gap:var(--d3-gutter)}
}
.d3-tile h3{font-size:24px;line-height:1.2;font-weight:600;margin-bottom:4px}
.d3-tile__meta{
	font-size:12px;letter-spacing:.06em;text-transform:uppercase;
	color:var(--wp--preset--color--text-muted);margin-bottom:10px;
}
.d3-tile__desc{font-size:14px;line-height:1.7;color:var(--wp--preset--color--text-muted)}

/* 5b · closing band — warm, text left, button right */
.d3-endband{
	background:var(--wp--preset--color--sesame);padding:var(--d3-pad) var(--d3-gutter);
	display:flex;gap:40px;align-items:center;justify-content:space-between;flex-wrap:wrap;
}
.d3-endband h2{font-size:42px;line-height:1.15;margin-bottom:8px}
.d3-endband p{font-size:17px;color:var(--wp--preset--color--ink);opacity:.8}

/* 5e · contact — 1.25fr / 1fr, gap 36 -> 56 */
.d3-contact{
	padding:44px var(--d3-gutter) var(--d3-pad);max-width:1280px;margin-inline:auto;
	display:grid;grid-template-columns:1.25fr 1fr;gap:56px;align-items:start;
}
.d3-aside__box,.d3-aside__area{border:1px solid var(--wp--preset--color--sesame);padding:30px 32px}
.d3-aside__area{border-top:0;background:var(--wp--preset--color--sesame)}
.d3-aside__box h2{font-size:28px;line-height:1.2;margin-bottom:16px}
.d3-aside__box p{display:flex;gap:12px;align-items:center;font-size:16px;line-height:2;margin:0}
.d3-aside__area h3{font-size:20px;font-weight:600;margin-bottom:8px}
.d3-aside__area p{font-size:14px;line-height:1.7;color:var(--wp--preset--color--ink);opacity:.85;margin:0}

@media (max-width:1024px){
	.d3-split{grid-template-columns:1fr}
	.d3-split__pic img{min-height:360px}
	.d3-psteps{grid-template-columns:repeat(2,1fr)}
	.d3-budget{flex-direction:column;align-items:stretch}
	.d3-grid{grid-template-columns:1fr 1fr}
	.d3-contact{grid-template-columns:1fr;gap:38px}
}
@media (max-width:781px){
	.d3-tiers__grid{grid-template-columns:1fr}
	.d3-psteps{grid-template-columns:1fr}
	.d3-grid{grid-template-columns:1fr}
	.d3-split__text h1,.d3-pageintro h1{font-size:36px}
}

/* ═══ Process and About — no handoff design, built on its vocabulary ══════ */

/* Process · the five steps, each given room. The home page carries them as a
   compact grid; giving them space here is the reason the page exists. */
/* Warm ground rather than white. Every other band on this page reaches the
   viewport edge, so this one is bled the same way — the constrained box it used
   to be would have floated a beige panel inside white gutters. The padding is
   the hero's idiom, not a new one: it holds the content column at exactly the
   1136px it had before (1280 less two 72px gutters) while the colour runs edge
   to edge, and collapses to the 24px gutter on phones. */
.d3-steprows{
	background:var(--wp--preset--color--cotton);
	padding-block:var(--d3-pad);
	padding-inline:max(24px,calc((100% - 1280px)/2 + var(--d3-gutter)));
	display:flex;flex-direction:column;
}
.d3-steprow{
	/* 52px gutter, not 120px: that column was sized for a 52px numeral, and an
	   icon at the site's 28px would have floated in it. */
	display:grid;grid-template-columns:52px 1fr;gap:28px;align-items:start;
	/* Jade, not sesame. Sesame was a 1.37:1 hairline on white; on cotton it is
	   1.16:1, which is not a faint line but no line at all — and these rules are
	   the only thing separating five steps. Jade is 2.33:1: still a hairline,
	   still quiet, and actually present. */
	padding:38px 0;border-top:1px solid var(--wp--preset--color--jade);
}
.d3-steprow:last-child{border-bottom:1px solid var(--wp--preset--color--jade)}
/* Was a 52px numeral; now holds the step icon. No connector on this page — the
   rows are already hairline-separated, so a thread between them would be
   drawing a boundary that is drawn twice. */
.d3-steprow__n{
	display:block;line-height:0;
	color:var(--wp--preset--color--action);
	padding-top:2px; /* optical: aligns the icon's body with the h2 cap height */
}
.d3-steprow h2{font-size:28px;line-height:1.2;font-weight:600;margin-bottom:10px}
.d3-steprow p{font-size:17px;line-height:1.7;color:var(--wp--preset--color--text-muted);max-width:62ch}

/* About · the 3a Meet-Leslie split, opened out to a full page */
.d3-meet--page .d3-meet__text{padding-block:var(--d3-pad)}
.d3-meet--page h1{font-size:42px;line-height:1.15;margin-bottom:16px}
.d3-meet--page p{margin-bottom:14px}

/* About · credentials — three across, replacing the design's four badge tiles
   with the three things she can actually evidence */
.d3-credential{
	background:var(--wp--preset--color--snow);
	padding:52px var(--d3-gutter);
	border-block:1px solid var(--wp--preset--color--sesame);
}
.d3-credential{display:grid;grid-template-columns:repeat(3,1fr);gap:38px}
.d3-cred__item{display:flex;gap:16px;align-items:flex-start;max-width:1280px}
.d3-cred__item strong{display:block;font-size:16px;line-height:1.4;margin-bottom:4px}
.d3-cred__item em{font-style:normal;font-size:14px;color:var(--wp--preset--color--text-muted)}

/* About · the personal section, image right */
.d3-personal{
	padding:var(--d3-pad) var(--d3-gutter);max-width:1280px;margin-inline:auto;
	display:grid;grid-template-columns:1.15fr 1fr;gap:56px;align-items:center;
}
.d3-personal h2{font-size:42px;line-height:1.15;margin-bottom:14px}
.d3-personal p{font-size:17px;line-height:1.7;color:var(--wp--preset--color--text-muted);margin-bottom:14px;max-width:58ch}
.d3-personal__pic img{display:block;width:100%;height:auto;aspect-ratio:4/5;object-fit:cover}

/* Sits with the other .d3-meet--page rules rather than up with the layout
   block, because .d3-meet--page h1 is redeclared at 42px below that point and
   an equal-specificity override earlier in the file loses on source order. */
@media (min-width:1025px) and (max-width:1200px){
	.d3-meet--page h1{font-size:34px}
}
@media (max-width:1024px){
	.d3-credential{grid-template-columns:1fr;gap:24px}
	.d3-personal{grid-template-columns:1fr;gap:32px}
	.d3-meet--page h1{font-size:32px}
}
@media (max-width:781px){
	.d3-steprow{grid-template-columns:1fr;gap:10px;padding:28px 0}
}

/* ─── Logo draw ───────────────────────────────────────────────────────────
   Front page only. The house is drawn as one continuous stroke over 3s and the
   wordmark fades in across the last 30%, per the handoff.

   The resting state is the FINISHED logo — dash offset 0, wordmark opaque — and
   the animation is layered on top of it inside a no-preference query. That
   ordering is the whole safety argument: a browser that ignores the animation,
   or a visitor who asked for reduced motion, gets her complete logo rather than
   an empty header. There is no hidden state to get stuck in, the same reasoning
   the scroll reveals use.

   `both` fill plus the default single iteration is what makes it run once and
   hold, which is what Sean asked for over the prototype's loop. It replays on
   every load of the front page, so leaving and coming back plays it again. */
/* Two classes: WordPress core ships .custom-logo-link{display:inline-block} at
   the same specificity, and it was winning on source order — which left the
   house flush against the left edge instead of centred over her name. */
.custom-logo-link.dw-logo{
	display:flex;flex-direction:column;align-items:center;
	width:170px;text-decoration:none;
	/* Her PNG carries 29px of blank above and below the artwork, which renders
	   as ~13px at this size. The lockup is built from the artwork alone, so
	   without this the home header stood 16px shorter than every other page and
	   the bar jumped as you navigated. */
	/* No top padding: the SVG's viewBox already carries 42 units of space above
	   the path, which is 7.8px here and lands the drawn house within 1.6px of
	   where the static logo puts it. 6px underneath brings the lockup to the
	   same 162px box, so the header does not move as you navigate between the
	   animated logo and the plain one. */
	padding-block:0 6px;
}
/* Percentages, not pixels: the header shrinks .dw-logo from 170px to 96px on
   scroll, and these ride that down together instead of drifting apart. 43% is
   the 73px mark against the 170px lockup; 12.44% is the 21.2px gap that lands
   the wordmark exactly where the static logo has it. */
.dw-logo__mark{display:block;width:43%;height:auto;overflow:visible}
.dw-logo__word{display:block;width:100%;height:auto;margin-top:12.44%}
.dw-logo__stroke{
	stroke-dasharray:2391.329;   /* = DW_LOGO_PATH_LEN; keep the two in step */
	stroke-dashoffset:0;         /* resting state: drawn */
}
@media (prefers-reduced-motion: no-preference){
	.dw-logo__stroke{animation:dw-logo-draw 3s cubic-bezier(0.65,0,0.35,1) both}
	/* starts at 70% of the 3s draw and runs the remaining 30%, matching the
	   prototype's clamp((p - 0.70) / 0.30, 0, 1) */
	.dw-logo__word{animation:dw-logo-word .9s linear .0s both;animation-delay:2.1s}
}
@keyframes dw-logo-draw{
	from{stroke-dashoffset:2391.329}
	to  {stroke-dashoffset:0}
}
@keyframes dw-logo-word{
	from{opacity:0}
	to  {opacity:1}
}

/* The services budget band on sesame instead of ink. Everything inside it was
   built to sit on a dark ground, so all of it inverts: white text at 1.4:1 on
   sesame would have been unreadable. Ink on sesame is 8.24:1, comfortably past
   the AAA bar this project enforces, and the paragraph's .88 opacity goes with
   it — dimmed ink lands at 6.1:1, which passes AA but not the AAA this palette
   is held to. The icons take action green (5.34:1), matching every other icon
   on the site. Scoped to the modifier so the process page's own budget band
   stays dark. */
.d3-budget--warm{
	background:var(--wp--preset--color--sesame);
	color:var(--wp--preset--color--ink);
}
.d3-budget--warm h2{color:var(--wp--preset--color--ink)}
.d3-budget--warm p{opacity:1}
/* The white fill and radius now live on the base rule, shared by both bands.
   All Services still needs is its own icon colour: action on white is 7.33:1,
   and the olive reads as a deliberate accent against the sesame ground. */
.d3-budget--warm .d3-budget__box .dw-ic{
	color:var(--wp--preset--color--action);opacity:1;
}

/* The logo at phone size. 170px is 45% of a 375px screen, and the header was
   eating a quarter of the viewport before anyone saw a photograph. This has to
   sit after the base .custom-logo-link.dw-logo rule — same specificity, so
   source order decides, and the earlier media block was silently losing. The
   lockup is built in percentages, so one width carries the mark and the
   wordmark down together. */
@media (max-width:781px){
	.custom-logo-link.dw-logo{width:112px}
	.dw-header .wp-block-site-logo img{width:112px}
}

/* 12 · FAQ — question list. Each section carries its own ground, full-bleed,
   with the content still landing on the same 1280 frame as every other band.
   No accordion: every answer is in the DOM and readable without a click, which
   is what an answer engine reads and what a reader scanning for one detail
   wants.

   Grounds run white -> cotton -> snow, closing into the sesame CTA band. Only
   two of those adjacencies are ever seen at once and both are legible
   (cotton/snow 1.08:1, snow/sesame 1.25:1); sections 1 and 3 are similar but
   never touch. Sesame is not available to a section here — it is the closing
   band, and a sesame section above it would erase the boundary entirely. */
/* The inline padding makes a full-bleed band's content land on exactly the same
   line as .d3-pageintro above it. That is NOT (100% - 1280px)/2 + gutter: the
   1280 on those sections is a max-width on the CONTENT box, so their border box
   is 1280 + 2*gutter = 1424 and centring 1424 in 1440 puts content at 80, not
   152. Solving for the same x gives 50% - 640px, floored at the gutter so it
   collapses correctly once the viewport is narrower than the frame. */
.d3-faq{
	padding-block:0 var(--d3-pad);
	padding-inline:max(var(--d3-gutter),calc(50% - 640px));
}
.d3-faq--cotton{background:var(--wp--preset--color--cotton)}
.d3-faq--snow{background:var(--wp--preset--color--snow)}
/* Tinted sections need their top padding back: on white the heading sits under
   the page intro's own spacing, but a band has to open with its own. */
.d3-faq--cotton,.d3-faq--snow{padding-block-start:var(--d3-pad)}

.d3-faq > h2{
	font-size:28px;line-height:1.2;margin-bottom:6px;
	padding-bottom:14px;border-bottom:1px solid var(--wp--preset--color--jade);
}
.d3-faq__list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 56px}
/* Jade, not --d3-line. The default line colour IS sesame, which lands at
   1.16:1 on cotton and 1.00:1 on sesame itself — the row dividers would have
   silently disappeared the moment these sections were tinted. Jade holds at
   2.76 / 2.33 / 2.01 across white, cotton and snow. */
.d3-faq__item{padding:30px 0;border-bottom:1px solid var(--wp--preset--color--jade)}
.d3-faq__item h3{
	font-family:var(--wp--preset--font-family--lora);
	font-size:19px;line-height:1.35;margin-bottom:10px;
	color:var(--wp--preset--color--ink);
}
/* Ink rather than text-muted. Muted is 6.54:1 on white and falls to 5.52 on
   cotton — under this site's own 7.0 bar on every ground it could sit on.
   Ink clears it everywhere (11.32 / 9.55 / 10.28) and these are long answers. */
.d3-faq__item p{
	font-size:16px;line-height:1.75;
	color:var(--wp--preset--color--ink);max-width:60ch;
}
.d3-faq__item p + p{margin-top:12px}
@media (max-width:900px){
	.d3-faq__list{grid-template-columns:1fr;gap:0}
}

/* 13 · mobile menu overlay
   The navigation block ships its overlay with no inline padding, so every link
   sits hard against the screen edge and the close control lands in the very
   corner. 24px is the header's own padding, set inline in parts/header.html, so
   the open menu lines up with the logo and with the hamburger it replaced
   rather than introducing a third margin. */
/* Pin the inline padding on the container and zero it on the content, rather
   than adding to whatever the block already contributes — the two nest, and
   they do not contribute the same amount everywhere, so adding produced 48px
   here against 24px on the deployed copy. Setting both makes it 24 either way. */
/* The class is repeated to raise specificity, and !important settles it: the
   block's own overlay CSS is enqueued after the theme stylesheet on the
   deployed copy (though not in local wp-env, which is why this worked locally
   and did nothing live), so an equal-specificity rule here loses on source
   order. This is third-party CSS we do not control the load order of. */
.wp-block-navigation__responsive-container.is-menu-open.is-menu-open{
	padding-inline:24px !important;
}
.wp-block-navigation__responsive-container.is-menu-open.is-menu-open
	.wp-block-navigation__responsive-container-content{
	padding-inline:0 !important;
}
.wp-block-navigation__responsive-container.is-menu-open
	.wp-block-navigation__responsive-container-close{
	/* Zero, not 24: the control is positioned inside the container, which now
	   carries the 24px itself — an inset here would add to it. */
	inset-inline-end:0;inset-block-start:20px;
}
