/* ==========================================================================
   Bright Path — band layer

   OWNER: the band track. Reveal, split geometry, grid rungs, steps rail, band decoration, dark surfaces, titles, interaction states, panel geometry.

   One shard of what used to be a single style.css. The split exists so that
   parallel work cannot collide: `.design-core/` is not under version control,
   so two agents editing one file means one of them is lost, not merged.
   Do not move rules between shards without saying so.
   ========================================================================== */
/* ==========================================================================
   Sections 6-11 -- the pattern layer.

   Sections 1-5 above are measured and frozen. Everything below exists because
   a pattern shipped in theme/patterns/ renders an element that needs it AND
   no theme.json preset or core block support already expresses it. A selector
   with no pattern consumer does not belong here; the prototype's 63 orphan
   class names, its tabs/toast/modal code, its :root token block, its --rgb-*
   triplets, [data-demo-state] and @media print are all deliberately absent.

   Contents
     6   Reveal
     7   Split geometry
     8   Grid rungs
     9   Steps rail
    10   Band decoration
    11   Head split

   One convention used throughout 7-9. Core's layout support prints
   `gap` and core's own block stylesheet prints `flex-basis`/`flex-wrap` at
   specificity (0,1,0) or (0,2,0), from a stylesheet whose position relative to
   this one is not guaranteed. Where this file must win, the class is repeated
   (`.bp-grid-3.bp-grid-3`) to raise specificity rather than relying on source
   order. Where core uses !important -- core/media-text's inline
   grid-template-columns, and .wp-block-columns' flex-wrap -- !important is the
   only answer and is marked at each site.
   ========================================================================== */


/* --------------------------------------------------------------------------
   6  Reveal

   Ported from main.css 1582-1592 (the .js [data-reveal] block) and 1732 (the
   reduced-motion override), with the selector rewritten: no core block emits
   an arbitrary data-* attribute, so the built site's hook is .bp-reveal.
   BUILD-CONTRACT.md 10. Anything measuring motion here selects .bp-reveal;
   [data-reveal] matches zero elements on this site.

   Scoped to .bp-js, which functions.php prints inline in <head> before first
   paint. With JavaScript off the class never lands, none of these rules match,
   and every band is simply visible. The editor canvas never runs wp_head, so
   .bp-js is absent there and pattern content is visible in the editor by
   construction.
   -------------------------------------------------------------------------- */

.bp-js .bp-reveal {
	opacity: 0;
	transform: translateY(16px);
	transition:
		opacity 520ms cubic-bezier(0.22, 0.61, 0.36, 1),
		transform 520ms cubic-bezier(0.22, 0.61, 0.36, 1);
	will-change: opacity, transform;
}

.bp-js .bp-reveal.is-revealed {
	opacity: 1;
	transform: none;
	will-change: auto;
}

.bp-js .bp-reveal--d1 { transition-delay: 70ms; }
.bp-js .bp-reveal--d2 { transition-delay: 140ms; }
.bp-js .bp-reveal--d3 { transition-delay: 210ms; }
.bp-js .bp-reveal--d4 { transition-delay: 280ms; }

@media (prefers-reduced-motion: reduce) {
	.bp-js .bp-reveal {
		opacity: 1 !important;
		transform: none !important;
		transition: none !important;
	}
}


/* --------------------------------------------------------------------------
   7  Split geometry

   Two shapes. core/media-text carries the 20+ instance `split-media-text`
   band; core/columns carries the hero and CTA splits, which need a media
   query core does not offer.

   Runtime facts, observed and not to be re-derived (BUILD-CONTRACT.md 1):
     - core/media-text writes grid-template-columns as an INLINE style, so the
       override below must be !important.
     - core/media-text stacks at 600px in its own stylesheet; this design
       stacks at 900.
     - core/media-text has NO blockGap support, so the gap is CSS and is not an
       editor control. Value from main.css:441.
     - core/columns stacks at 782px and forces flex-basis:100% !important below
       it; this design stacks at 900, so 782-899 needs !important back.

   core/media-text also pads its content column by 8% on both sides. Left in
   place that padding reads as part of the gap and the measured gutter between
   media and text is wrong at every width, so it is zeroed here and the gap is
   the only thing between the two columns.
   -------------------------------------------------------------------------- */

.wp-block-media-text.bp-split {
	grid-template-columns: minmax(0, 1fr) !important;
	gap: clamp(2rem, 5vw, 4.5rem);
	align-items: center;
}

.wp-block-media-text.bp-split > .wp-block-media-text__content {
	padding: 0;
}

/* A2. Below 900 the single-track grid is not enough on its own. Core keeps
   `.wp-block-media-text__content{grid-column:2;grid-row:1}` at (0,2,0) in ALL
   viewports, and a child placed in column 2 of a one-track grid creates an
   IMPLICIT second track -- so the explicit media track collapsed to 0px and
   the image vanished between 601 and 899. Core's own is-stacked-on-mobile
   rules only reach 600px, which is why 390 and 599 were correct.
   Both children are therefore pinned to column 1 and given their own row.
   Measured 0.00px media track at 767 and 899 before this rule. */
@media (max-width: 899px) {
	.wp-block-media-text.bp-split {
		grid-template-rows: auto auto;
	}

	.wp-block-media-text.bp-split > .wp-block-media-text__media {
		grid-column: 1;
		grid-row: 1;
	}

	.wp-block-media-text.bp-split > .wp-block-media-text__content {
		grid-column: 1;
		grid-row: 2;
	}

	/* mediaPosition:right raises core's own selectors to (0,3,0); match it. */
	.wp-block-media-text.bp-split.has-media-on-the-right > .wp-block-media-text__media {
		grid-column: 1;
		grid-row: 1;
	}

	.wp-block-media-text.bp-split.has-media-on-the-right > .wp-block-media-text__content {
		grid-column: 1;
		grid-row: 2;
	}
}

/* RETAINED, NOT LIVE. After the media-slot change no pattern composes
   core/media-text any more -- split-media-text is core/columns, because a
   media-text figure cannot hold the label block or the panel the design puts
   there (measured: the editor draws them in the CONTENT column and the first
   save relocates them). Every .bp-split rule below therefore has no PATTERN
   consumer.

   They stay until the page track rebuilds, and not one day longer: the live
   /about/ and /services/daily-living/ still store the old media-text markup,
   and deleting these rules today would break both bands the moment the CSS
   deployed. Delete this whole block once no document composes core/media-text
   -- it is the one exception to section 6's no-orphan-selector rule, and it is
   an exception with an end date, not a permanent one.

   A2b. The ratio is POSITIONAL, not media-relative -- measured, main.css:1674.
   28 of the 29 container splits in the prototype are `.split--wide`
   (1.15fr / 0.85fr); only index.html and sil-property.html carry the plain
   1.05/0.95 rung, and neither is a band this pattern builds. The earlier rule
   here used the plain rung AND swapped the tracks for mediaPosition:right, so
   the wide side followed the media instead of staying in column one. The
   prototype's `.split--reverse` moves the media with `order:-1` and never
   touches the tracks: column one is 1.15fr whatever sits in it.
   Measured before: 579.594 / 524.391. Prototype: 634.797 / 469.203.

   Why CSS and not the `mediaWidth` attribute: mediaWidth is one percentage of
   the whole grid, so it cannot subtract the 72px gap and it is only correct at
   one container width; and it describes the MEDIA column, so a flipped
   instance would need the complementary number -- two owners for one ratio.
   The fr pair holds at every width and in both orientations. core/media-text
   writes grid-template-columns inline (contract 1), hence !important. */
@media (min-width: 900px) {
	.wp-block-media-text.bp-split,
	.wp-block-media-text.bp-split.has-media-on-the-right {
		grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr) !important;
	}
}

/* --- the core/columns splits ------------------------------------------- */

.wp-block-columns.bp-split-cols {
	--bp-split-gap: clamp(2rem, 5vw, 4.5rem);
}

.wp-block-columns.bp-split-cols.bp-split-cols {
	gap: var(--bp-split-gap);
	align-items: center;
}

@media (max-width: 899px) {
	/* A4. Two core declarations had to be beaten here, not one.
	   1. `@media(min-width:782px){.wp-block-columns{flex-wrap:nowrap!important}}`
	      -- without wrap restored, a 100% basis cannot break the row and the
	      columns simply shrink side by side. Measured: flipped at 782, not 900.
	   2. `.wp-block-columns:not(.is-not-stacked-on-mobile)>.wp-block-column
	      {flex-basis:100%!important}` is (0,3,0); the old selector here was
	      (0,3,0) too and won only below 782 by source order. Now (0,4,0). */
	.wp-block-columns.bp-split-cols.bp-split-cols {
		flex-wrap: wrap !important;
	}

	.wp-block-columns.bp-split-cols.bp-split-cols > .wp-block-column {
		flex-basis: 100% !important;
		flex-grow: 0;
	}
}

@media (min-width: 900px) {
	/* A percentage flex-basis ignores the gap, so the gap comes out of 100%
	   first and the ratio is applied to what is left. This is the footer
	   lesson from the foundation, not a preference. */
	.bp-split-cols > .wp-block-column {
		flex-basis: calc((100% - var(--bp-split-gap)) * 0.5);
		flex-grow: 0;
	}

	.bp-split-cols--lead > .wp-block-column:first-child {
		flex-basis: calc((100% - var(--bp-split-gap)) * 0.575);
	}

	.bp-split-cols--lead > .wp-block-column:last-child {
		flex-basis: calc((100% - var(--bp-split-gap)) * 0.425);
	}

	/* pages.css:539 -- the home hero, 1.02/0.98. Approved rung.

	   CORRECTION to what I reported: this band is NOT on a third container
	   measure. I read `.hero__grid` at 1240 and called it a wider container;
	   1240 is its PADDED box and 32px of that is padding on each side, so the
	   content measure is 1176, exactly like every other band. Two numbers
	   differ from the plain split, not three: the gap (64, not 72) and the
	   ratio. Measured: 567.109 / 544.891 of 1112, which is 1.02 / 0.98. */
	.bp-split-cols--hero > .wp-block-column:first-child {
		flex-basis: calc((100% - var(--bp-split-gap)) * 0.51);
	}

	.bp-split-cols--hero > .wp-block-column:last-child {
		flex-basis: calc((100% - var(--bp-split-gap)) * 0.49);
	}

	/* main.css:1673 -- the plain `.split` rung, 1.05/0.95, which `.bp-split`
	   used to be before D3 moved the default to `--wide`. Two compositions
	   want it back: index.html's one reverse split and sil-property.html's
	   bare one. A per-instance class the composer sets, not a pattern default:
	   28 of the prototype's 29 container splits are the wide rung.
	   Measured target: 579.594 / 524.391 of 1104. */
	.bp-split-cols--even > .wp-block-column:first-child {
		flex-basis: calc((100% - var(--bp-split-gap)) * 0.525);
	}

	.bp-split-cols--even > .wp-block-column:last-child {
		flex-basis: calc((100% - var(--bp-split-gap)) * 0.475);
	}
}

/* The hero's gap is FLUID, and 64px is only its cap. Set on the variable the
   whole ladder already reads, so the basis maths above subtracts the right
   number without a second declaration of it.

   64px flat was wrong, and wrong in a way that hid itself: it is correct from
   1280 up, which includes 1440, and the other gated width is 390 where the
   band has stacked and has no gap at all. So both gates passed while the band
   was 2-9px out at every width in between. Measured, prototype against the
   flat 64:

     900   45 vs 64     1100   55 vs 64     1240   62 vs 64
     1024  51.2 vs 64   1200   60 vs 64     1440   64 vs 64

   That is 5% of the viewport capped at 4rem. The ratio rung was right all
   along; it was dividing a narrower track than the prototype was.

   A drift of 2-9px is exactly the size that reads as sub-pixel rounding and is
   not one. Gate at the rungs; sweep when a number is off by less than a rung.

   The block class is repeated for (0,2,0): the base declaration is
   `.wp-block-columns.bp-split-cols`, not `.bp-split-cols`, so a single-class
   override loses and does so SILENTLY. The resolved custom property said
   `clamp(2rem, 5vw, 4.5rem)` where the file said 64px; reading the resolved
   value rather than the file is what found it. */
.wp-block-columns.bp-split-cols--hero {
	--bp-split-gap: clamp(2rem, 5vw, 4rem);
}


/* --------------------------------------------------------------------------
   8  Grid rungs

   core/columns is one flex row that never wraps: core sets
   flex-wrap:nowrap !important at >=782 and on .is-not-stacked-on-mobile.
   These bands are grids of 3, 4, 6 or 8 cards, so wrapping is restored here
   with the only tool core leaves -- !important, at a raised specificity.

   Widths are flex-basis percentages, never fr: core/columns has no fr concept.
   Every basis subtracts the gaps before dividing, because a percentage basis
   ignores the gap and the row overflows by exactly the gap total.

   Gap totals below are the resolved px of the blockGap preset each pattern
   sets on its columns block:
     bp-grid-2   32px column / 24px row (spacing|60 / spacing|50), 48px column
                 at >=900 per pages.css:544
     bp-grid-3   24px (spacing|50)
     bp-grid-4   24px (spacing|50)

   Rungs, from main.css 1671-1710 and pages.css 528-544:
     <=640   one column, every grid
     641-899 two columns
     >=900   bp-grid-3 three; bp-grid-4 still three
     >=1200  bp-grid-4 four
   bp-grid-4--early is the trust-strip ladder: 2 -> 4 at 900, with no 3-up
   rung, because that band's four items are structural and 3+1 is ragged.
   -------------------------------------------------------------------------- */

.wp-block-columns.bp-grid-2.bp-grid-2,
.wp-block-columns.bp-grid-3.bp-grid-3,
.wp-block-columns.bp-grid-4.bp-grid-4 {
	flex-wrap: wrap !important;
	align-items: stretch;
}

.bp-grid-2 > .wp-block-column,
.bp-grid-3 > .wp-block-column,
.bp-grid-4 > .wp-block-column {
	flex-grow: 0;
	flex-basis: 100%;
}

/* A3. Every basis below is written with the block class repeated AND the
   core class present -- (0,4,0) -- because core ships
   `@media(max-width:781px){.wp-block-columns:not(.is-not-stacked-on-mobile)
   > .wp-block-column{flex-basis:100%!important}}` at (0,3,0). Two !important
   declarations are decided by specificity, so the old (0,2,0)/(0,3,0)
   selectors lost the whole 641-781 range: every grid measured one column at
   641 and at 781 and flipped at 782, which is core's breakpoint and not this
   design's. Contract 3: 1-col below 641, 2-col from 641. */
@media (min-width: 641px) {
	.wp-block-columns.bp-grid-2.bp-grid-2 > .wp-block-column {
		flex-basis: calc((100% - 32px) / 2) !important;
	}

	.wp-block-columns.bp-grid-3.bp-grid-3 > .wp-block-column,
	.wp-block-columns.bp-grid-4.bp-grid-4 > .wp-block-column {
		flex-basis: calc((100% - 24px) / 2) !important;
	}
}

@media (min-width: 900px) {
	.bp-grid-2.bp-grid-2 {
		column-gap: 48px;
		row-gap: 32px;
	}

	.wp-block-columns.bp-grid-2.bp-grid-2 > .wp-block-column {
		flex-basis: calc((100% - 48px) / 2) !important;
	}

	.wp-block-columns.bp-grid-3.bp-grid-3 > .wp-block-column {
		flex-basis: calc((100% - 48px) / 3) !important;
	}

	.wp-block-columns.bp-grid-4.bp-grid-4 > .wp-block-column {
		flex-basis: calc((100% - 48px) / 3) !important;
	}

	.wp-block-columns.bp-grid-4--early.bp-grid-4--early > .wp-block-column {
		flex-basis: calc((100% - 72px) / 4) !important;
	}
}

/* D2. The statement-card pair is `.grid grid--2 grid--gap-lg` (main.css:431
   plus the gap-lg modifier): ONE 32px gutter at every width. bp-grid-2 is the
   benefit grid, which opens to 48px at 900 (pages.css:544) -- a different
   component that happens to also be two columns. The modifier pins the wider
   rung back rather than forking the whole ladder.
   Measured before (about, 1440): 564px columns, on the 48px rung.
   Prototype: 572px. Selector is (0,5,0) against the 48px rule's (0,4,0). */
@media (min-width: 900px) {
	.wp-block-columns.bp-grid-2.bp-grid-2--flat {
		column-gap: var(--wp--preset--spacing--60);
	}

	.wp-block-columns.bp-grid-2.bp-grid-2--flat.bp-grid-2--flat > .wp-block-column {
		flex-basis: calc((100% - 32px) / 2) !important;
	}
}

/* The prototype's .card is a direct grid child, so the pair is one row and
   both cards take the taller height (measured 323.719px each, not 290.8 and
   319.7). core/columns puts a column between the row and the panel, and a
   column that stretches does not pass its height on. Measured before this
   rule: 290.828 / 319.719 -- two different-height cards in one row. */
.bp-grid-2--flat > .wp-block-column > .bp-panel {
	height: 100%;
}

@media (min-width: 1200px) {
	.wp-block-columns.bp-grid-4.bp-grid-4 > .wp-block-column {
		flex-basis: calc((100% - 72px) / 4) !important;
	}
}


/* --------------------------------------------------------------------------
   9  Steps rail

   The rule connecting the step markers has no core block. It is a pseudo
   element on the marker, sized to the remainder of the track.

   THE FOUR-MARKER ASSUMPTION. steps-row is four columns and steps-stack is
   five items, and the connector is drawn on every marker except the last
   (:not(:last-child)). A fifth column in steps-row re-flows the track and the
   rule no longer reaches the next marker. That is why both patterns ship
   templateLock:"all" -- the lock is what enforces the count this rule assumes,
   and neither can be relaxed without rewriting this block.
   -------------------------------------------------------------------------- */

.wp-block-columns.bp-steps-row.bp-steps-row {
	flex-wrap: wrap !important;
	align-items: flex-start;
}

.bp-steps-row > .wp-block-column {
	flex-grow: 0;
	flex-basis: 100%;
}

.bp-steps-row .bp-step-marker {
	position: relative;
	display: flex;
	align-items: center;
	width: 100%;
}

@media (min-width: 641px) {
	/* Contract 3: two up from 641. Same (0,4,0) requirement as section 8. */
	.wp-block-columns.bp-steps-row.bp-steps-row > .wp-block-column {
		flex-basis: calc((100% - 32px) / 2) !important;
	}
}

@media (min-width: 900px) {
	.wp-block-columns.bp-steps-row.bp-steps-row > .wp-block-column {
		/* blockGap spacing|60 = 2rem = 32px, three gaps across four columns. */
		flex-basis: calc((100% - 96px) / 4) !important;
	}

	.bp-steps-row > .wp-block-column:not(:last-child) .bp-step-marker::after {
		content: "";
		flex: 1 1 auto;
		height: 2px;
		margin-left: 0.75rem;
		background: linear-gradient(
			to right,
			var(--wp--preset--color--sage-300),
			var(--wp--preset--color--sand-200)
		);
	}
}

/* The stacked form: the rail runs down the left of the markers. */
.bp-steps-stack .bp-step-marker {
	position: relative;
	display: flex;
	align-items: center;
}

.bp-steps-stack > .wp-block-group:not(:last-child) .bp-step-marker::after {
	content: "";
	position: absolute;
	top: 48px;
	bottom: calc(var(--wp--preset--spacing--70) * -1);
	left: 24px;
	width: 2px;
	transform: translateX(-50%);
	background: linear-gradient(
		to bottom,
		var(--wp--preset--color--sage-300),
		var(--wp--preset--color--sand-200)
	);
}


/* --------------------------------------------------------------------------
  10  Band decoration

   Solid colours resolve through a palette preset custom property, exactly as
   the theme.json aliases do. color-mix() is used only where the prototype
   painted with alpha -- the --rgb-* call-sites -- since no theme.json slot
   accepts a bare channel triplet.
   -------------------------------------------------------------------------- */

/* Eyebrow. The whole rule lives here, not in the pattern: uppercase, tracking
   and the 22px bar are not expressible as presets, and putting them in a
   pattern would mean a literal in every one of the fourteen that use it. */
.bp-eyebrow {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	font-weight: 600;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--wp--preset--color--sage-600);
}

.bp-eyebrow::before {
	content: "";
	flex: 0 0 auto;
	width: 22px;
	height: 2px;
	border-radius: 2px;
	background-color: var(--wp--preset--color--terracotta-600);
}

/* Tick list. main.css 833-849. The tick is a background image, not a block. */
.tick-list {
	list-style: none;
	padding-left: 0;
}

.tick-list li {
	position: relative;
	padding-left: var(--wp--preset--spacing--70);
	line-height: 1.6;
}

.tick-list li + li {
	margin-top: var(--wp--preset--spacing--30);
}

.tick-list li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0.15em;
	width: 22px;
	height: 22px;
	border-radius: 50%;
	background-color: var(--wp--preset--color--sage-100);
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234A7355' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
	background-size: 13px;
	background-position: center;
	background-repeat: no-repeat;
}

/* Icon tile. main.css 707-724. Here it carries the step numbers and the
   tick glyphs.

   D5. `line-height: 1` was invented here: the prototype declares no
   line-height on .icon-tile at all, so the glyph inherits the body 1.7 and
   resolves to 28.9px at the 17px tile size. The tile is a centred inline-flex
   box with a fixed height, so leading changes nothing about the geometry --
   but it is a number this layer had no reason to own, and it read as a defect
   against the prototype. Removed; the tile inherits, as the prototype does.
   Measured before: 17px. Prototype: 28.9px. */
.icon-tile {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 48px;
	height: 48px;
	border-radius: var(--wp--custom--radius--md);
	background-color: var(--wp--preset--color--forest-100);
	color: var(--wp--preset--color--forest-700);
	font-weight: 600;
}

/* main.css:721-722. The two tinted tiles, each with its own text colour so
   the glyph keeps its contrast on the tint. Consumed by card-panel-2. */
.icon-tile--accent {
	background-color: var(--wp--preset--color--terracotta-100);
	color: var(--wp--preset--color--terracotta-700);
}

.icon-tile--sage {
	background-color: var(--wp--preset--color--sage-100);
	color: var(--wp--preset--color--sage-600);
}

/* pages.css:205 -- the benefit tile. NOT an icon-tile.

   D5, corrected. The class I was given, .icon-tile--sm, is 40x40 radius 6px in
   forest (main.css:725) and does exist in the prototype 386 times -- but not in
   this band. The element that actually renders in a benefit row is
   `.benefit__icon`: 40x40, radius 10px, sage-100 on sage-600. A visitor sees
   the band, not the class name, so the rendered counterpart wins.

   .icon-tile--sm is gone rather than left behind: after this change it had no
   pattern consumer, and a selector with no consumer is the thing section 6
   exists to keep out. If a band later needs the real 6px forest rung it comes
   back with the pattern that needs it.
   Measured before: 48x48, radius 10px, forest-100/forest-700, line-height 17px.
   Prototype: 40x40, radius 10px, sage-100/sage-600, line-height 28.9px. */
.benefit__icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 40px;
	height: 40px;
	border-radius: var(--wp--custom--radius--md);
	background-color: var(--wp--preset--color--sage-100);
	color: var(--wp--preset--color--sage-600);
	font-weight: 600;
}

.icon-tile--round {
	border-radius: 50%;
}

/* Stat. main.css 818-821. The size is the `stat` fontSize preset, set on the
   block; only family, tracking and leading are component-local. */
.stat__value {
	font-family: var(--wp--preset--font-family--fraunces);
	font-weight: 600;
	line-height: 1;
	letter-spacing: -0.02em;
	color: var(--wp--custom--color--text);
}

.stat__label {
	color: var(--wp--custom--color--text-muted);
}

/* Panel. The card and CTA surface. Background comes from the block's own
   backgroundColor preset -- core prints that with !important, so this rule
   must not set one. */
.bp-panel {
	position: relative;
	border: 1px solid var(--wp--custom--color--border);
	border-radius: var(--wp--custom--radius--lg);
}

.bp-panel:has(> .cta__shape) {
	overflow: hidden;
}

/* The base .cta__shape, owed by this plan: main.css:1565, sage 0.55 over
   terracotta 0.22, both written as --rgb-* triplets the theme does not carry.
   The soft variant at main.css:1571 is already in section 5. */
.cta__shape {
	position: absolute;
	right: -6%;
	top: -30%;
	width: 26rem;
	height: 26rem;
	border-radius: 46% 54% 58% 42% / 52% 44% 56% 48%;
	background: radial-gradient(
		circle at 30% 30%,
		color-mix(in srgb, var(--wp--preset--color--sage-600) 55%, transparent),
		color-mix(in srgb, var(--wp--preset--color--terracotta-600) 22%, transparent)
	);
	opacity: 0.5;
	pointer-events: none;
}


/* --------------------------------------------------------------------------
  11  Head split -- a breakpoint, not a wrap

   `section-head-split` used core's flex layout with flexWrap:"wrap" and let
   the browser decide when to stack. Flex wrap triggers on available width, so
   the band flipped NON-MONOTONICALLY: two columns at 897, back to one at 900,
   two again at 913. The cause is the gutter step at 900 (24 -> 32px, contract
   2) shrinking the content box below the fit at exactly the system's biggest
   rung -- so at 900 it rendered the opposite of the design.

   The band now carries flexWrap:"nowrap" and declares its own direction at
   the design's rung. Core's layout support prints flex-direction,
   justify-content and align-items on a generated .wp-container-* class at
   (0,1,0); the class is repeated here to take (0,3,0) and win regardless of
   which stylesheet loads last.
   -------------------------------------------------------------------------- */

.wp-block-group.bp-head-split.bp-head-split {
	display: flex;
	flex-direction: column;
	flex-wrap: nowrap;
	align-items: flex-start;
	justify-content: flex-start;
}

@media (min-width: 900px) {
	.wp-block-group.bp-head-split.bp-head-split {
		flex-direction: row;
		align-items: flex-end;
		justify-content: space-between;
	}

	/* The text column takes the slack; the link never shrinks or wraps. */
	.bp-head-split.bp-head-split > :first-child {
		flex: 1 1 auto;
		min-width: 0;
	}

	.bp-head-split.bp-head-split > :last-child {
		flex: 0 0 auto;
	}
}


/* ==========================================================================
  12  Design-QA round 2 -- the thirteen defects

   Everything below was written against a measured difference between the
   prototype and the built page, not against a reading of the design. Each
   block names the prototype rule it reproduces so the next reader can check
   the claim rather than trust it.
   ========================================================================== */

/* --------------------------------------------------------------------------
  12.1  Dark surfaces -- D1 and D2

   The prototype keys every inverted treatment off `.on-dark`, a class it puts
   on the band. The build has no such class, but it does have something with
   exactly the same extension: core prints `has-forest-900-background-color`
   on any block whose backgroundColor preset is forest-900, which is the only
   dark surface in the palette (contract 4). So that class IS `.on-dark` here,
   and using it means a band becomes dark-aware by choosing the dark
   background -- there is no second thing for a pattern author to remember.

   D1 was a WCAG AA failure: the eyebrow inherited the light-surface sage-600
   (#4A7355) onto forest-900 for 2.82:1. main.css:311 says sage-300 on dark.
   -------------------------------------------------------------------------- */

/* D2. main.css:1658 -- body copy on dark is muted, headings stay solid, and
   the two-tone split is the hierarchy. The band carries `has-sand-50-color`,
   which core prints with !important, but that lands on the BAND; children get
   it by inheritance, and inheritance has no specificity. A class-level rule
   on the child therefore wins without needing !important of its own. */
.has-forest-900-background-color :where(p, li, figcaption, dd, dt) {
	color: var(--wp--custom--color--text-invert-muted);
}

/* Headings stay at full strength -- main.css:1659. */
.has-forest-900-background-color :where(h1, h2, h3, h4, h5, h6) {
	color: var(--wp--custom--color--text-invert);
}

/* D1. main.css:311. Declared after 12.1's paragraph rule because .bp-eyebrow
   IS a paragraph: same specificity, later wins. */
.has-forest-900-background-color .bp-eyebrow {
	color: var(--wp--preset--color--sage-300);
}

/* A tick list on dark loses its light-surface disc -- main.css:849. */
.has-forest-900-background-color .tick-list li::before {
	background-color: rgba(255, 255, 255, 0.12);
}


/* --------------------------------------------------------------------------
  12.2  Card, benefit, perk and step titles -- D3

   The prototype has THREE title treatments and the build had collapsed all
   three into the h3 role (Fraunces 24/600/30/-0.24). None of them is the h3
   role. Each class below carries only the properties that DIFFER from the
   element it sits on, so the shared properties still come from one place:

     .card__title  h3 -> Figtree 19/600/1.35, keeps h3's -0.01em tracking
     .step__title  h3 -> 19px only; family, leading and tracking stay h3's
     .benefit__title / .perk__title  <p> -> weight and colour only; 17px,
                                     1.7 leading and normal tracking are
                                     already the body role

   The last pair are paragraphs in the prototype, not headings, so they are
   paragraphs here. That is also why the document outline no longer carries
   ten entries the design never had.
   -------------------------------------------------------------------------- */

/* main.css:753 */
.card__title {
	font-family: var(--wp--preset--font-family--figtree);
	font-size: var(--wp--preset--font-size--large);
	font-weight: 600;
	line-height: 1.35;
	color: var(--wp--custom--color--text);
}

/* pages.css:196 -- Fraunces is inherited from the h3 role on purpose. */
.step__title {
	font-size: var(--wp--preset--font-size--large);
	font-weight: 600;
	color: var(--wp--custom--color--text);
}

/* pages.css:207 and pages.css:364 -- both are <p>. */
/* pages.css:158-159 -- the feature trust strip's item. Weight and leading
   only: both sizes are preset attributes on the block (medium / small), so
   this carries what a preset cannot. Consumed by trust-strip-features. */
.trust-strip__title {
	font-weight: 600;
	line-height: 1.35;
	color: var(--wp--custom--color--text);
}

.trust-strip__text {
	line-height: 1.55;
	color: var(--wp--custom--color--text-muted);
}

.benefit__title,
.perk__title {
	font-weight: 600;
	color: var(--wp--custom--color--text);
}

.benefit__title {
	margin-bottom: 2px;
}

/* On dark, a title is a heading-strength colour, not muted body copy. 12.1
   would otherwise mute the two paragraph titles. */
.has-forest-900-background-color :where(.benefit__title, .perk__title),
.has-forest-900-background-color :where(.card__title, .step__title) {
	color: var(--wp--custom--color--text-invert);
}


/* --------------------------------------------------------------------------
  12.3  The Display and Heading-1 roles -- D4

   Contract 5 gives the Display role leading 1.06 and tracking -0.02em, but
   nothing carried them: `display` is a fontSize preset, and a fontSize preset
   has no slot for leading in theme.json. The hero therefore inherited
   whichever heading element it happened to be, which after the h1->h2
   demotion meant h2's 1.14/-0.015em -- +5.44px per line at 1440.

   Attaching the values to the preset's own class fixes it AT THE ROLE: any
   block using the Display size gets Display leading, whatever heading level
   it is, and the heading level stays free to serve the document outline.
   Core prints the preset class rule as `.has-display-font-size{font-size:..}`
   with no leading of its own, so there is no conflict -- and a class beats
   theme.json's element selector (0,0,1) without !important.
   -------------------------------------------------------------------------- */

/* main.css:249 -- .h1--hero */
.has-display-font-size {
	line-height: 1.06;
	letter-spacing: -0.02em;
}

/* main.css:241 -- the non-hero h1 role, pinned to its preset for the same
   reason: the three page heroes render it on an h2. */
.has-heading-1-font-size {
	line-height: 1.1;
	letter-spacing: -0.018em;
}


/* --------------------------------------------------------------------------
  12.4  Interaction states -- D6

   Three states were absent, and the focus ring is the one that matters: it is
   on every interactive element on the site and it was the user-agent default.
   -------------------------------------------------------------------------- */

/* Focus. main.css:226. `:where()` contributes no specificity, exactly as the
   prototype's own rule does, so a component may still override the ring
   without an !important war. theme.json's elements.link :focus outline was
   REMOVED in the same change: two owners for one ring is how a 3px rule ends
   up rendering 2px. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
	outline: 3px solid var(--wp--custom--color--focus);
	outline-offset: 2px;
	border-radius: var(--wp--custom--radius--sm);
}

/* main.css:231 -- forest green on forest green is not a focus ring. */
.has-forest-900-background-color :where(a, button, input, select, textarea, [tabindex]):focus-visible {
	outline-color: var(--wp--custom--color--focus-invert);
}

/* --- D1. The resting button box. main.css:494-518 -----------------------

   Four numbers were wrong at once and all four came from a default nobody
   chose: theme.json's elements.button radius is `full`, and the padding,
   border and height were core's block stylesheet. Hover was already correct,
   so what a visitor saw was the right hover on the wrong base.

   Cascade: core's `:where(.wp-block-button__link)` carries the 9999px radius
   and the calc() padding at ZERO specificity, and theme.json's elements.button
   prints `:root :where(.wp-block-button__link)` at (0,1,0). The selector pair
   below is (0,2,0) and bands.css prints after global-styles, so no !important
   is needed anywhere in this block.

   OPEN WITH MAIN -- theme.json `styles.elements.button.border.radius` is
   `var:custom|radius|full` and needs `var:custom|radius|md` (10px). Until Main
   moves it, the editor canvas will still draw pills while the front end draws
   the design. This rule is not the long-term owner of the radius.

   Measured before: radius 999px, padding 12.672/23.328 (10.672/21.28 outline),
   no min-height, height 41.344. Prototype: radius 10px, padding 12/24,
   min-height 48, height 48.

   The 1.5px transparent border is on the base, not just the outline variant,
   because it is what keeps a filled and an outlined button the same size --
   the prototype's own reason for putting it there. */
.wp-block-button .wp-block-button__link,
.wp-block-button .wp-element-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--wp--preset--spacing--20);
	min-height: 48px;
	padding: var(--wp--preset--spacing--30) var(--wp--preset--spacing--50);
	border: 1.5px solid transparent;
	border-radius: var(--wp--custom--radius--md);
	line-height: 1;
	letter-spacing: 0.005em;
	text-align: center;
}

/* main.css:534 -- the outline button, the design's secondary action. Core's
   own `is-style-outline` is a 2px currentColor rectangle, which on this
   palette resolved to a 2px ink-900 border and ink-900 text: a heavier
   element than the accent button it sits beside. The design's secondary is
   quiet -- forest text on a 1.5px sand border, no fill.
   Measured before: 2px rgb(28,38,32) border, rgb(28,38,32) text.
   Prototype: 1.5px (resolves 1px) rgb(216,206,189), rgb(31,74,58) text. */
.wp-block-button.is-style-outline > .wp-block-button__link,
.wp-block-button.is-style-outline > .wp-element-button {
	background-color: transparent;
	color: var(--wp--custom--color--primary);
	border-color: var(--wp--custom--color--border-strong);
	padding: var(--wp--preset--spacing--30) var(--wp--preset--spacing--50);
}

/* main.css:537 -- the ghost button. Same text colour as the outline, no
   border at all. The prototype uses it for the lower-weight of two actions in
   a split band, where an outline would compete with the accent beside it. */
.wp-block-button.is-style-ghost > .wp-block-button__link,
.wp-block-button.is-style-ghost > .wp-element-button {
	background-color: transparent;
	color: var(--wp--custom--color--primary);
	border-color: transparent;
}

.wp-block-button.is-style-ghost > .wp-block-button__link:hover,
.wp-block-button.is-style-ghost > .wp-element-button:hover {
	background-color: var(--wp--preset--color--forest-100);
	box-shadow: none;
}

/* main.css:543 -- btn--outline-dark. On a forest-900 band the outline button
   inverts: sand text on a 14%-white border. This is a CONTEXT rule rather than
   a fifth style variation, because the prototype never puts an outline-dark
   button on a light surface -- the surface decides, so the surface selects. */
.has-forest-900-background-color .wp-block-button.is-style-outline > .wp-block-button__link,
.has-forest-900-background-color .wp-block-button.is-style-outline > .wp-element-button {
	color: var(--wp--custom--color--text-invert);
	border-color: var(--wp--custom--color--border-dark);
}

.has-forest-900-background-color .wp-block-button.is-style-outline > .wp-block-button__link:hover,
.has-forest-900-background-color .wp-block-button.is-style-outline > .wp-element-button:hover {
	background-color: rgba(255, 255, 255, 0.08);
	border-color: rgba(255, 255, 255, 0.4);
}

/* main.css:546 -- btn--lg. The only size rung the bands use, and the only
   place 17px appears on a button: `medium` is the 1.0625rem preset, which is
   --text-base in the prototype. Carried as a class on the wp:button block so
   that one day it can become a registered size preset without the markup
   changing shape.
   Measured before: no large rung existed -- every button was 16px/48-ish.
   Prototype: 17px, padding 16/32, min-height 56. */
.wp-block-button.bp-btn--lg > .wp-block-button__link,
.wp-block-button.bp-btn--lg > .wp-element-button {
	min-height: 56px;
	padding: var(--wp--preset--spacing--40) var(--wp--preset--spacing--60);
	font-size: var(--wp--preset--font-size--medium);
}

/* Buttons. main.css:494-520. The transition has to be declared on the resting
   state or the hover snaps. */
.wp-element-button,
.wp-block-button__link {
	transition:
		background-color var(--wp--custom--transition--base),
		border-color var(--wp--custom--transition--base),
		color var(--wp--custom--transition--base),
		transform var(--wp--custom--transition--fast),
		box-shadow var(--wp--custom--transition--base);
}

.wp-element-button:hover,
.wp-block-button__link:hover {
	transform: translateY(-1px);
	box-shadow: var(--wp--custom--shadow--sm);
}

.wp-element-button:active,
.wp-block-button__link:active {
	transform: translateY(0);
	box-shadow: none;
}

/* main.css:531 -- the accent button. A backgroundColor PRESET attribute is
   printed by core as `.has-terracotta-600-background-color{background-color:
   var(..) !important}`, so the resting colour is !important and a hover
   without one silently loses. This is why the built button had no hover at
   all. The !important here is answering core's, not decorating. */
.wp-block-button__link.has-terracotta-600-background-color:hover {
	background-color: var(--wp--preset--color--terracotta-700) !important;
}

/* main.css:535 -- the outline button. */
.wp-block-button.is-style-outline > .wp-block-button__link:hover {
	background-color: var(--wp--preset--color--forest-100);
	border-color: var(--wp--custom--color--primary);
}

/* main.css:1314-1330 -- the accordion trigger. core/accordion renders the
   trigger as a button inside the heading, so the colour has to land on the
   button.

   D4. The question is a LABEL, not a heading. `headingLevel` is a real
   core/accordion-heading attribute and the pattern already sets level 3, which
   is the level the prototype uses -- so the semantics were right and only the
   type was wrong. The h3 was inheriting the heading role's Fraunces 24px
   (22px at 390) and reading as a serif sub-head in a list of questions.
   Nothing here touches the heading element itself: the h3 stays an h3 and
   keeps its place in the document outline. Only the button inside it is typed.
   Measured before: Fraunces 24px, padding 8px 0, row 46px.
   Prototype: Figtree 17px/1.5, padding 16px 0, min-height 56, row 59px. */
.wp-block-accordion-heading__toggle {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--50);
	width: 100%;
	min-height: 56px;
	padding-block: var(--wp--preset--spacing--40);
	font-family: var(--wp--preset--font-family--figtree);
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 600;
	line-height: 1.5;
	text-align: left;
	color: var(--wp--custom--color--text);
	transition: color var(--wp--custom--transition--base);
}

/* main.css:1312-1313 -- the rules that separate one question from the next,
   and the reason the prototype's row measures 59px against a 58px trigger:
   the extra pixel is the item's own bottom border. Without them the questions
   run together as an undivided block of text.
   Measured before: no borders, row 46px. Prototype: row 59px. */
.wp-block-accordion {
	border-top: 1px solid var(--wp--custom--color--border);
}

.wp-block-accordion-item {
	border-bottom: 1px solid var(--wp--custom--color--border);
}

.has-forest-900-background-color .wp-block-accordion,
.has-forest-900-background-color .wp-block-accordion-item {
	border-color: var(--wp--custom--color--border-dark);
}

/* main.css:1333-1335 -- the animated open.

   The prototype animates `grid-template-rows: 0fr -> 1fr`, which is the only
   way to transition to an auto height. core binds the `hidden` ATTRIBUTE on
   the panel (accordion-item.php:49, `data-wp-bind--hidden`), and hidden is
   display:none in the UA sheet, so there was nothing to animate: the built
   panel opened in one frame.

   display is restored here so the grid track can run. `hidden` still does its
   real job -- the delayed `visibility` keeps the closed panel out of the
   accessibility tree and out of the tab order, which the prototype's own
   markup does NOT do. The 0s delay on close is what lets the row finish
   collapsing before the content is taken away; opening has no delay.

   grid-auto-rows carries the same two values so that a panel an editor has
   added a second block to still collapses -- an implicit row would otherwise
   size to auto and leak its content out of a closed panel.
   Measured before: display block, no grid track, no transition. */
.wp-block-accordion-panel {
	display: grid;
	grid-template-rows: 1fr;
	grid-auto-rows: 1fr;
	transition: grid-template-rows var(--wp--custom--transition--slow), visibility 0s;
}

.wp-block-accordion-panel[hidden] {
	display: grid;
	grid-template-rows: 0fr;
	grid-auto-rows: 0fr;
	transition: grid-template-rows var(--wp--custom--transition--slow), visibility 0s 380ms;
	visibility: hidden;
}

.wp-block-accordion-panel > * {
	min-height: 0;
	overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
	.wp-block-accordion-panel,
	.wp-block-accordion-panel[hidden] {
		transition: none;
	}
}

.wp-block-accordion-heading__toggle:hover {
	color: var(--wp--custom--color--primary);
}

.has-forest-900-background-color .wp-block-accordion-heading__toggle:hover {
	color: var(--wp--preset--color--sage-300);
}

/* main.css:1734 -- the prototype drops the lift, not the colour change, when
   the visitor has asked for less motion. */
@media (prefers-reduced-motion: reduce) {
	.wp-element-button:hover,
	.wp-block-button__link:hover {
		transform: none;
	}
}


/* --------------------------------------------------------------------------
  12.5  Panel geometry -- D7

   The 40.375px side padding was not a token and not a gutter: it is core's
   opinionated block stylesheet, `:where(.wp-block-group.has-background){
   padding:1.25em 2.375em}`, enabled by add_theme_support('wp-block-styles').
   2.375em x the 17px body size = 40.375px exactly. The patterns had set only
   top and bottom, so core filled the two sides it left unstated.

   Rather than drop wp-block-styles -- which would move a dozen unrelated core
   blocks in one step -- the panel states all four sides itself. Core's rule
   is wrapped in :where(), so it carries zero specificity and any class rule
   settles it. The patterns no longer carry inline padding: one owner.
   -------------------------------------------------------------------------- */

.bp-panel {
	padding: var(--wp--preset--spacing--50);
}

/* D8. The panel that sits in a split's second column: services/*.html give it
   `padding:var(--space-6)` inline, one rung above the card default. */
.bp-panel--md {
	padding: var(--wp--preset--spacing--60);
}

/* D2. The framed-statement card: main.css gives .card 24px by default, and
   about.html's mission/vision pair overrides it to --space-7 (40px) inline.
   A rung here rather than inline padding on the block keeps 12.5's rule that
   the panel has exactly one padding owner. */
.bp-panel--lg {
	padding: var(--wp--preset--spacing--70);
}

/* The CTA surface is a different component from the card: main.css:1548 gives
   it radius-xl and a square clamp, against the card's radius-lg and 24px. */
.bp-cta {
	padding: clamp(2.5rem, 6vw, 4.5rem);
	border-radius: var(--wp--custom--radius--xl);
}


/* --------------------------------------------------------------------------
  12.6  Home hero band padding -- D8

   pages.css:28 is asymmetric on purpose -- the hero sits tighter under the
   header than it does above the band below it -- and the bottom grows again
   at 1024. The band had been given `section-y-lg` on both sides, which is a
   symmetric 129.6px at 1440 and 83px too tall.

   This cannot be a block spacing attribute: core prints those inline, and an
   inline style cannot carry a media query. So the pattern states no padding
   and the class owns both numbers.
   -------------------------------------------------------------------------- */

.bp-hero-home {
	padding-top: clamp(2.5rem, 6vw, 4.5rem);
	padding-bottom: clamp(3rem, 7vw, 5.5rem);
}

/* pages.css:568 */
@media (min-width: 64rem) {
	.bp-hero-home {
		padding-bottom: clamp(4rem, 8vw, 6.5rem);
	}
}


/* --------------------------------------------------------------------------
  12.7  The in-band section head -- D5

   `section-head` and `section-head-split` were being placed as siblings of a
   band, so they rendered as bare transparent groups on the body background
   above it: the head and its grid were two surfaces where the prototype has
   one <section>. The band patterns that need a head now carry it INSIDE their
   own wrapper, which is the only arrangement that cannot come apart.

   main.css:414 -- the measure and the 48px drop to the grid.
   -------------------------------------------------------------------------- */

.bp-head {
	max-width: 56ch;
}


/* --------------------------------------------------------------------------
  13  The three static bands added by track B

   `sil-feature`, `article-header` and `property-gallery`. Every value below
   is a token or a ratio; nothing here is a hex, and the two numbers that are
   literal (the 4/3 and 16/10 aspect ratios, and the 2px thumb frame) are
   ratios and a hairline, not spacing or colour.
   -------------------------------------------------------------------------- */

/* 13.1  Media placeholders.

   Every image in these bands ships as an EMPTY core/image: a figure with no
   img inside it, so an editor sees the upload placeholder and the front end
   renders a figure with no content. Without a declared box that figure is 0px
   tall and the band measures wrong until the day someone uploads a photo. The
   ratio and the placeholder surface therefore live on the class, and they stay
   correct once a real image lands because the img is told to fill the box. */
.bp-media {
	margin: 0;
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: var(--wp--preset--spacing--50);
	border-radius: var(--wp--custom--radius--lg);
	background-color: var(--wp--preset--color--sage-100);
	border: 1px solid var(--wp--preset--color--sage-300);
	overflow: hidden;
	/* A frame may never be wider than the column it sits in. This is not
	   belt-and-braces: aspect-ratio on an auto-width box back-solves the width
	   from the height, so the moment a min-height floor exceeds what the ratio
	   would give, the box INVENTS width. Measured at 390 with the hero's 20rem
	   floor: 480px wide inside a 350px column, 110px of document overflow.
	   The prototype does exactly that too -- .hero__media .image-placeholder is
	   480x320 in a 350px parent at 390 -- and this build does not follow it
	   there, on the same grounds as the 1400px overflow: where the prototype
	   overflows and the build need not, the build is better. */
	max-width: 100%;
	/* A FLOOR, not a design default: an unmodified box must never be the
	   0px-tall figure of 13.1. Every box this theme ships names its ratio. */
	aspect-ratio: 3 / 2;
}

/* D9. main.css .image-placeholder -- the hatched surface. The prototype writes
   the stripe as `rgba(var(--rgb-sage), .10)`, and --rgb-sage is one of the five
   raw triplets deleted in 4, so it becomes a color-mix of the same sage-600.
   These are NOT the same number: rgba(74,115,85,.10) composites 10% of the
   colour over whatever is behind it, while
   `color-mix(... 10%, transparent)` produces a premultiplied rgba(74,115,85,
   0.1). Those are NOT the same rendered pixel: measured on a 36x36 crop of the
   flat interior, the prototype's stripe is rgb(212,225,213) and the
   transparent mix rendered rgb(211,224,212) -- 1/255 low on every channel,
   because the two composite in different precisions and 227*0.9 + 74*0.1 =
   211.7 falls either side of the round.

   Mixing against the frame's own opaque sage-100 fill instead of transparent
   removes the compositing step: the stripe is computed once, at full
   precision, and lands on 212 as the prototype does. Measured identical
   afterwards -- same two colours, same pixel counts.

   The paint applies only while the slot is empty of an image. A real photo
   must not sit inside 24px of hatched padding, so :has(img) takes it all back
   off -- :has() is the container-side complement of the :empty rule at 13.4,
   and unlike :empty it survives the slot holding a visible label. */
.bp-media:not(:has(img)) {
	background-image: linear-gradient(
		135deg,
		color-mix(in srgb, var(--wp--preset--color--sage-600) 10%, var(--wp--preset--color--sage-100)) 25%,
		transparent 25%,
		transparent 50%,
		color-mix(in srgb, var(--wp--preset--color--sage-600) 10%, var(--wp--preset--color--sage-100)) 50%,
		color-mix(in srgb, var(--wp--preset--color--sage-600) 10%, var(--wp--preset--color--sage-100)) 75%,
		transparent 75%
	);
	background-size: 18px 18px;
}

.bp-media:has(img) {
	padding: 0;
	display: block;
	background-image: none;
	background-color: var(--wp--custom--color--bg-soft);
	border-color: var(--wp--custom--color--border);
}

/* The brief for the photograph that belongs here. A real element, addressable
   by the page composer, because "Map -- Church Street, Parramatta" and
   "Accessible Bathroom" are different briefs; a CSS `content:` string would be
   one string for 77 slots and unreadable to a screen reader.

   No role="img"/aria-label on the frame: the label is VISIBLE text, so it is
   already in the accessibility tree, and an aria-label on the group would
   REPLACE it rather than add to it. It would also have to be an attribute on a
   core/group wrapper, which the editor regenerates from attributes on save --
   the same class of trap measured for D8. Proof of the announced name is in
   qa/bands-placeholder-measure.mjs, which reads the computed accessible name,
   not the markup. */
.bp-media__label {
	max-width: 26ch;
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	line-height: 1.45;
	text-align: center;
	color: var(--wp--custom--color--primary);
}

.bp-media:has(img) .bp-media__label {
	display: none;
}

/* The box is a GROUP, not the image block: an empty core/image renders as
   nothing at all on the front end (render_block_core_image returns '' when
   there is no img), so a figure-only placeholder measured zero elements, not
   zero height. The group always renders; the image block lives inside it and
   is what the client replaces. */
.bp-media > .wp-block-image {
	margin: 0;
	height: 100%;
}

/* The empty core/image inside a labelled frame renders zero elements (13.1),
   but the editor still draws its upload placeholder, and a flex row would sit
   it beside the label. The frame stacks while it is a placeholder. */
.bp-media:not(:has(img)) {
	flex-direction: column;
	gap: var(--wp--preset--spacing--30);
}

.bp-media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* --- the ratio rungs, main.css:652-658 --------------------------------------

   Counted across all 20 prototype documents, because the guidance I was given
   (3/2 is the default; 4/3 and 1/1 are declared and unused) was wrong in both
   halves and would have mis-cropped about a third of the 77 placeholders:

     --square-top 35   --ratio-3-2  13
     --ratio-4-3  25   --portrait    9
     --property   15   --accent      3
     --ratio-16-9 14   --dark        2
     --tall        1   --ratio-1-1   0   <- the genuinely unused one

   So 4/3 is the most common and 3/2 is fifth. The base above keeps 3/2 only as
   a FLOOR so that an unmodified box is never the 0px-tall figure that 13.1 was
   written to stop -- it is not a design default, and every box this theme ships
   states its ratio explicitly. --ratio-1-1 is not built: zero uses.

   These carry the prototype's own names. The two rungs that used to live here,
   --hero (16/10) and --wide (16/9), were this layer's invented names for
   --property and --ratio-16-9; renamed rather than aliased, because two names
   for one number is how a value ends up declared twice and half-changed. */
.bp-media--ratio-16-9 {
	aspect-ratio: 16 / 9;
}

.bp-media--ratio-4-3 {
	aspect-ratio: 4 / 3;
}

.bp-media--ratio-3-2 {
	aspect-ratio: 3 / 2;
}

.bp-media--portrait {
	aspect-ratio: 3 / 4;
}

.bp-media--property {
	aspect-ratio: 16 / 10;
}

.bp-media--tall {
	aspect-ratio: 4 / 5;
}

/* main.css:682. Used 35 times -- the most common modifier of all. It is the
   frame sitting flush on top of a card, so the bottom corners square off and
   the bottom border goes, or the card draws two lines across its own middle. */
.bp-media--square-top {
	border-radius: var(--wp--custom--radius--lg) var(--wp--custom--radius--lg) 0 0;
	border-bottom: none;
}

/* main.css:661 and 671 -- the two tinted frames. Each restates the hatch in
   its own hue, and each mixes against its OWN opaque fill rather than through
   transparent, for the rounding reason recorded above. */
.bp-media--accent:not(:has(img)) {
	background-color: var(--wp--preset--color--terracotta-100);
	background-image: linear-gradient(
		135deg,
		color-mix(in srgb, var(--wp--preset--color--terracotta-600) 8%, var(--wp--preset--color--terracotta-100)) 25%,
		var(--wp--preset--color--terracotta-100) 25%,
		var(--wp--preset--color--terracotta-100) 50%,
		color-mix(in srgb, var(--wp--preset--color--terracotta-600) 8%, var(--wp--preset--color--terracotta-100)) 50%,
		color-mix(in srgb, var(--wp--preset--color--terracotta-600) 8%, var(--wp--preset--color--terracotta-100)) 75%,
		var(--wp--preset--color--terracotta-100) 75%
	);
	border-color: color-mix(in srgb, var(--wp--preset--color--terracotta-600) 30%, transparent);
}

.bp-media--accent .bp-media__label {
	color: var(--wp--preset--color--terracotta-700);
}

.bp-media--dark:not(:has(img)) {
	background-color: var(--wp--preset--color--forest-800);
	background-image: linear-gradient(
		135deg,
		color-mix(in srgb, #fff 4%, var(--wp--preset--color--forest-800)) 25%,
		var(--wp--preset--color--forest-800) 25%,
		var(--wp--preset--color--forest-800) 50%,
		color-mix(in srgb, #fff 4%, var(--wp--preset--color--forest-800)) 50%,
		color-mix(in srgb, #fff 4%, var(--wp--preset--color--forest-800)) 75%,
		var(--wp--preset--color--forest-800) 75%
	);
	border-color: rgba(255, 255, 255, 0.16);
}

.bp-media--dark .bp-media__label {
	color: var(--wp--preset--color--sage-300);
}

/* pages.css:240-242 -- the gallery thumbnail is a CONTEXT, not a ratio: 4/3
   with a smaller radius, no border, tighter padding, and the label suppressed
   because the box is too small to carry it.

   `display:none` would take the brief away from a screen reader as well as
   from the eye, and the prototype keeps it reachable with an aria-label on the
   frame. Clipping instead renders identically -- the label is out of flow, so
   the box measures the same -- and keeps the brief announced. */
.bp-media--thumb {
	aspect-ratio: 4 / 3;
	border-radius: var(--wp--custom--radius--sm);
	border: none;
	padding: var(--wp--preset--spacing--20);
}

.bp-media--thumb .bp-media__label {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* pages.css:63 and 569 -- the home hero frame. A context in the prototype
   (`.hero__media .image-placeholder`), so a context here too: no new modifier
   name for something the band already identifies.

   THE OVERHANG, and why it is one rule and not two.

   The frame is meant to bleed past the text column -- the prototype's hero
   placeholder is 624px in a 544.891px column at 1440, overhanging by 79.11.
   That gesture is real and is reproduced here. What is NOT real is the same
   number at every other width. Swept across 13 viewports, the frame's right
   edge against the viewport's:

     390  right 500  vs 390    900  right  960 vs  900   1280 right 1307 vs 1280
     600  right 580  vs 600   1024  right 1171 vs 1024   1320 right 1327 vs 1320
     768  right 744  vs 768   1200  right 1265 vs 1200   1400 right 1367 vs 1400
                              1240  right 1286 vs 1240   1440 right 1387 vs 1440

   So from 900 to 1320 the frame ends PAST the viewport by 60 to 147px, and the
   prototype reports zero document overflow at every one of them -- because
   `.hero{overflow:hidden}` and `body{overflow-x:hidden}` slice it off. At 390
   130px of a 480px frame is cut away. That is not a bleed; it is the
   min-height/aspect-ratio artefact of 13.1 hidden behind two clips, and the
   overflow check reads clean while the frame is being cut in half.

   The boundary is arithmetic, not a guess. Above 1240 the container is fixed,
   so the frame's right edge is `viewport/2 + 667.11`; it is inside the
   viewport when `viewport/2 + 667.11 <= viewport`, i.e. from **1334.22px**.
   Rounded up to 1335. Below it the frame fills its column and stops, via the
   max-width above -- no clip, nothing hidden, nothing to reproduce, because
   the prototype shows nothing there either.

   No `overflow:hidden` is added to the band. Adopting the prototype's clip
   would buy a green overflow number by hiding the defect it is meant to find. */
.bp-hero-home .bp-media {
	border-radius: var(--wp--custom--radius--xl);
}

@media (min-width: 1335px) {
	.bp-hero-home .bp-media {
		min-height: 26rem;
		max-width: none;
	}
}

/* A card's image is flush to the card edge on three sides -- main.css:758.
   .bp-panel pads all four sides, so the media pulls that padding back out. */
.bp-panel > .bp-media {
	margin-top: calc(-1 * var(--wp--preset--spacing--50));
	margin-left: calc(-1 * var(--wp--preset--spacing--50));
	margin-right: calc(-1 * var(--wp--preset--spacing--50));
	width: auto;
	border-radius: var(--wp--custom--radius--lg) var(--wp--custom--radius--lg) 0 0;
	border-top: none;
	border-left: none;
	border-right: none;
}

/* 13.2  The dark SIL band.

   pages.css:214. The band paints forest-900 and carries a blob shape that is
   positioned outside its own box; without `overflow:hidden` here that blob is
   document-level horizontal overflow at every width, which is exactly what the
   overflow gate measures. `position:relative` is what the blob is absolute to,
   and the band's real children are lifted above it. */
.bp-sil-feature {
	position: relative;
	overflow: hidden;
}

.bp-sil-feature > :not(.cta__shape) {
	position: relative;
	z-index: 1;
}

/* pages.css:215 -- the SIL blob is placed at the other corner from the CTA's
   and is sage-only, so it overrides the shared .cta__shape geometry. */
.bp-sil-feature > .cta__shape {
	right: auto;
	top: -20%;
	left: -8%;
	width: 30rem;
	height: 30rem;
	border-radius: 52% 48% 42% 58% / 48% 52% 48% 52%;
	background: radial-gradient(
		circle at 40% 40%,
		color-mix(in srgb, var(--wp--preset--color--sage-500) 40%, transparent),
		transparent 70%
	);
	opacity: 0.6;
}

/* 13.3  A light card on a dark band.

   §12.1 recolours EVERY paragraph and heading under
   `.has-forest-900-background-color` to the invert ramp, by descendant
   selector. The SIL cards are white panels inside that band, so those rules
   would put near-white text on a white card -- unreadable, and invisible to a
   colour check that only looks at the band. The card re-asserts the light
   ramp for its own subtree, at (0,2,0) and (0,3,0) against §12's (0,1,0). */
.has-forest-900-background-color .bp-card-light :where(p, li, figcaption, dd, dt) {
	color: var(--wp--custom--color--text-muted);
}

.has-forest-900-background-color .bp-card-light :where(h1, h2, h3, h4, h5, h6),
.has-forest-900-background-color .bp-card-light .card__title {
	color: var(--wp--custom--color--text);
}

/* 13.4  Badges -- main.css:587. A badge is a paragraph here, so it needs the
   inline-flex box stated on the class; core gives a paragraph nothing. */
.badge {
	display: inline-flex;
	align-items: center;
	gap: var(--wp--preset--spacing--10);
	width: fit-content;
	padding: var(--wp--preset--spacing--10) var(--wp--preset--spacing--20);
	font-weight: 600;
	letter-spacing: 0.02em;
	line-height: 1.3;
	border-radius: var(--wp--custom--radius--sm);
	border: 1px solid transparent;
	background-color: var(--wp--preset--color--forest-100);
	color: var(--wp--preset--color--forest-700);
}

/* main.css:602 -- and these two win over 13.3 because they are declared after
   it at equal or higher specificity. */
.badge--available,
.has-forest-900-background-color .bp-card-light .badge--available {
	background-color: var(--wp--custom--color--success-bg);
	color: var(--wp--custom--color--success);
}

.badge--limited,
.has-forest-900-background-color .bp-card-light .badge--limited {
	background-color: var(--wp--custom--status--warning-bg);
	color: var(--wp--custom--status--warning);
}

/* 13.5  Property card specs -- main.css:770. The rule above the specs line is
   what separates them from the address; it is the card's own border token. */
.card__specs {
	padding-top: var(--wp--preset--spacing--40);
	border-top: 1px solid var(--wp--custom--color--border);
}

/* 13.6  Article meta -- pages.css:414. Rules above and below, muted, small. */
.wp-block-group.article-meta {
	padding-block: var(--wp--preset--spacing--40);
	border-block: 1px solid var(--wp--custom--color--border);
	align-items: center;
}

.wp-block-group.article-meta p {
	color: var(--wp--custom--color--text-muted);
}

.article-meta__name.article-meta__name {
	font-weight: 600;
	color: var(--wp--custom--color--text);
}

/* 13.7  Property gallery -- pages.css:228. Four thumbnail tracks, so a fifth
   thumbnail wraps onto a second row exactly as the prototype's does. The block
   declares a flex layout so the editor has a layout to reason about; core
   prints `display:flex` on a generated container class at (0,1,0), and the
   repeated class below takes (0,2,0) to replace it with the grid. */
.bp-gallery__thumbs.bp-gallery__thumbs {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: var(--wp--preset--spacing--30);
}

@media (max-width: 640px) {
	.bp-gallery__thumbs.bp-gallery__thumbs {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/* 13.8  The 760px measure -- written as a token, not as a number.

   `"contentSize":"var(--wp--custom--container--narrow)"` is SILENTLY DROPPED:
   wp_get_layout_style() returns the right CSS when called directly, but the
   layout support's own path emits only the `.alignfull{max-width:none}` half
   of the rule and never the max-width one, so the measure fell back to the
   global 1176 with nothing in any log. Measured: h1 actualWidth 1112 at 1440.

   Core's default constrained rule reads the measure from a custom property,
   and custom properties inherit -- so redefining it on the band's own group is
   a token reference that core cannot drop, because core never parses it. */
.bp-measure {
	--wp--style--global--content-size: var(--wp--custom--container--narrow);
}


/* --------------------------------------------------------------------------
  14  Callout and prose table -- routed from track C

   The three legal pages carry one callout each and privacy carries a table.
   Track C had rendered both from shipped classes only, which renders but does
   not match. Both are band-level components, so both live here.
   -------------------------------------------------------------------------- */

/* pages.css:400. A callout is a core/group, so it states its own box. The
   group ships `layout:{"type":"flex"}` for the icon-beside-text arrangement;
   with a single text child it reads as a padded panel, which is the shape the
   three legal pages use. */
.bp-callout {
	display: flex;
	gap: var(--wp--preset--spacing--40);
	padding: var(--wp--preset--spacing--50);
	background-color: var(--wp--preset--color--sage-100);
	border: 1px solid var(--wp--preset--color--sage-300);
	border-radius: var(--wp--custom--radius--lg);
}

/* pages.css:409 */
.bp-callout .callout__title {
	font-weight: 600;
	color: var(--wp--custom--color--text);
}

/* pages.css:410 */
.bp-callout .callout__body {
	line-height: 1.65;
	color: var(--wp--custom--color--text-body);
}

/* pages.css:411. The prototype's accent border is a terracotta at 0.3 alpha
   written from an --rgb-* triplet this theme does not carry; color-mix over
   the preset is the same colour from the token. */
.bp-callout.bp-callout--accent {
	background-color: var(--wp--preset--color--terracotta-100);
	border-color: color-mix(in srgb, var(--wp--preset--color--terracotta-600) 30%, transparent);
}

/* main.css:381-383. core/table inside the prose measure. */
.bp-measure .wp-block-table table {
	border-collapse: collapse;
	width: 100%;
	font-size: var(--wp--preset--font-size--small);
}

.bp-measure .wp-block-table :where(th, td) {
	padding: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40);
	text-align: left;
	border-bottom: 1px solid var(--wp--custom--color--border);
	vertical-align: top;
}

.bp-measure .wp-block-table th {
	font-weight: 600;
	color: var(--wp--custom--color--text);
	background-color: var(--wp--custom--color--bg-soft);
}


/* --------------------------------------------------------------------------
  15  Empty media slots that are not blocks -- the other half of §13.1

   §13.1 gave every media placeholder a box by making it a GROUP, because an
   empty core/image renders as nothing. Two slots could not take that shape and
   were missed:

   1. core/media-text's media slot is the block's OWN figure, not a container
      another block can be put inside. Empty, it collapsed to h=0 -- on the
      most reused band on the site, 30+ instances.
   2. page-hero-split's right column was a .bp-panel with no ratio: a border
      drawn around nothing, measured 50px. That one is now a .bp-media group
      like every other, so only the media-text figure needs a rule.

   `:empty` is exactly the right selector: it matches while the slot holds
   nothing and STOPS matching the moment core puts an <img> in it, so a real
   image is never constrained by a placeholder ratio.
   -------------------------------------------------------------------------- */

.wp-block-media-text.bp-split > .wp-block-media-text__media:empty {
	aspect-ratio: 3 / 2;
	background-color: var(--wp--custom--color--bg-soft);
	border: 1px solid var(--wp--custom--color--border);
	border-radius: var(--wp--custom--radius--lg);
}

/* 15.1  The team card's role line -- main.css:784. The team card is heading +
   role + portrait; the role is the one treatment the card vocabulary did not
   carry. Shipped here so the team band, wherever it is built, has it. */
.card__role {
	font-weight: 600;
	color: var(--wp--custom--color--accent);
}

.has-forest-900-background-color .bp-card-light .card__role {
	color: var(--wp--custom--color--accent);
}
