/*
 * USTRC 2026 — component styles.
 *
 * Everything here backs a registered block style variation or a pattern.
 * Colour, type and spacing values come from theme.json presets — no raw
 * hex or px literals for anything a token already covers.
 */

/* ---------------------------------------------------------------- bands */

.wp-block-group.is-style-ustrc-band-dark {
	background-color: var(--wp--preset--color--deep);
	color: var(--wp--preset--color--on-dark-muted);
}

.is-style-ustrc-band-dark :is(h1, h2, h3, h4, h5, h6) {
	color: var(--wp--preset--color--on-dark);
}

.is-style-ustrc-band-dark a:not(.wp-element-button) {
	color: var(--wp--preset--color--green-bright);
}

.is-style-ustrc-band-dark a:not(.wp-element-button):hover {
	color: var(--wp--preset--color--clay-soft);
}

/* Text links. theme.json sets them charcoal and underlined; this is what the
   underline looks like: green, 2px, sitting a little below the baseline.

   Until 8 Sep 2026 links were green with no underline. Green at 15px is
   3.21:1 on limestone, 2.94:1 on sand and 3.65:1 on paper, all under AA, and
   the a11y pass and the brand steward arrived at the same fix from opposite
   directions: green keeps its identity job in the underline and never
   carries the glyphs. Dark bands keep green-bright text (5.5 to 7.3:1) and
   underline in the same colour. Navigation, footer links, buttons, headings
   and logo tiles are standalone links and opt out below. */
a:where(:not(.wp-element-button)) {
	text-decoration-color: var(--wp--preset--color--green);
	text-decoration-thickness: 2px;
	text-underline-offset: 0.16em;
}

.is-style-ustrc-band-dark a:not(.wp-element-button) {
	text-decoration-color: currentColor;
}

:is(h1, h2, h3, h4) a,
.wp-block-navigation a,
.ustrc-footer a:not(.wp-element-button),
.ustrc-logo-link,
.wp-block-post-title a {
	text-decoration: none;
}

:is(h1, h2, h3, h4) a:hover,
.wp-block-post-title a:hover {
	text-decoration: underline;
}

/* Standalone text links ("The full schedule", "Partner with us") are 15px
   Archivo in a paragraph of their own, about 22px tall, under the 24px WCAG
   2.5.8 asks for. Vertical padding with a matching negative margin grows the
   target without moving the text. */
p.has-display-font-family.has-small-font-size > a,
.ustrc-cols-aligned h3 > a {
	display: inline-block;
	padding-block: 0.2rem;
	margin-block: -0.2rem;
}

/* Footer links stay green on night: 4.83:1 at label size, an identity
   moment the steward passed. theme.json's charcoal would vanish there. */
.ustrc-footer a:not(.wp-element-button) {
	color: var(--wp--preset--color--green);
}

.ustrc-footer a:not(.wp-element-button):hover {
	color: var(--wp--preset--color--green-bright);
}

/* Core ships .screen-reader-text in the block library stylesheet; declared
   here too so a link's "(opens in a new tab)" never depends on load order. */
.screen-reader-text {
	position: absolute !important;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(1px, 1px, 1px, 1px);
	clip-path: inset(50%);
	border: 0;
	word-wrap: normal !important;
}

/* Contour motif. Drawn rather than an image so it costs nothing and
   recolours with the band. Sits behind content, never over it.

   Two faults, both found in the 31 Aug walk. The SVG was set not to preserve
   its aspect ratio, under `background-size: cover`, so the curves stretched to
   whatever height the band happened to be — on the tall closing band they came
   out as near-straight diagonals wandering across the CTA, which is what Jay
   saw as stray lines. And at stroke #39463F under opacity 0.45 the effective
   contrast against night is about 1.3:1: too faint to read as a motif, just
   visible enough to look like a rendering artefact.

   The curves now keep their own aspect, sit against the bottom edge where a
   ridgeline belongs, and are drawn at a weight that reads as deliberate. Still
   quiet — this is texture behind a CTA, not an illustration. If it is not
   wanted at all, drop the has-ustrc-contours class from the eight templates and
   parts that carry it; nothing else depends on it. */
.is-style-ustrc-band-dark.has-ustrc-contours {
	position: relative;
	isolation: isolate;
	overflow: hidden;
}

.is-style-ustrc-band-dark.has-ustrc-contours::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	opacity: 0.9;
	background-repeat: no-repeat;
	background-position: left bottom;
	background-size: 100% auto;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 340'%3E%3Cg fill='none' stroke='%234C5C53' stroke-width='1.5'%3E%3Cpath d='M-20 250 C 220 210, 420 246, 640 204 S 1040 142, 1260 170 S 1420 132, 1460 118'/%3E%3Cpath d='M-20 222 C 220 182, 420 218, 640 176 S 1040 114, 1260 142 S 1420 104, 1460 90'/%3E%3Cpath d='M-20 194 C 220 154, 420 190, 640 148 S 1040 86, 1260 114 S 1420 76, 1460 62'/%3E%3Cpath d='M-20 166 C 220 126, 420 162, 640 120 S 1040 58, 1260 86 S 1420 48, 1460 34'/%3E%3C/g%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------- cards */

/* Square corners, a hard 1px rule and a flat fill is how a document looks.
   A small radius, a softer edge and one very diffuse shadow is how a card
   looks. 6px is deliberate restraint — the type here is course-marker heavy
   and trail signage, and pill corners would fight it. overflow:hidden makes
   a photograph's corners follow the card instead of poking through.

   No shadow. Strava carries exactly one box-shadow on its entire marketing
   page, and ours had seven — depth doing work the border already does, and
   the main reason nineteen cards read as clutter instead of structure. */
.wp-block-group.is-style-ustrc-card {
	background-color: var(--wp--preset--color--paper);
	border: 1px solid var(--wp--preset--color--rule);
	border-radius: 6px;
	overflow: hidden;
}

.is-style-ustrc-band-dark .wp-block-group.is-style-ustrc-card {
	background-color: #232A28;
	border-color: #47534E;
	/* A drop shadow on a dark ground reads as dirt. Depth comes from the
	   fill being lighter than the band instead. */
	box-shadow: none;
}

/* A logo tile is a card without saying so. */
.ustrc-logo-cell {
	border-radius: 6px;
}

/* The presenting credit is not a wall tile — it is a signature. No plate, no
   border, and it sits on whatever band it is placed in. */
.ustrc-logo-cell.is-ustrc-bare {
	background: none !important;
	border: 0 !important;
	border-radius: 0;
	min-height: 0 !important;
	padding: 0 !important;
	display: block;
}

.ustrc-logo-cell.is-ustrc-bare > img {
	width: auto;
	max-width: 100%;
	height: 3.25rem;
	object-fit: contain;
	object-position: left center;
}

/* --------------------------------------------------------------- labels */

/* Keyed on our own class alone. This used to require .wp-block-paragraph as
   well — a class WordPress happens to add to paragraph blocks, but which is
   core's to change and which the static preview does not emit at all. The
   style should not hinge on it. */
.is-style-ustrc-eyebrow {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--label);
	font-weight: 500;
	letter-spacing: 0.19em;
	text-transform: uppercase;
	line-height: 1.5;
	color: var(--wp--preset--color--clay);
}

.is-style-ustrc-band-dark .is-style-ustrc-eyebrow {
	color: var(--wp--preset--color--green-bright);
}

/* Over a photograph, clay measures about 1.8:1 at label size — a flat colour
   band is a controlled ground, a photograph is not. The hero's eyebrow takes
   the same near-white as the rest of the hero text. */
.wp-block-cover.ustrc-photo-slot .is-style-ustrc-eyebrow {
	color: var(--wp--preset--color--on-dark);
}

.ustrc-endorse-footer {
	margin-block-start: 0.375rem;
	line-height: 0;
}

.ustrc-endorse-footer img {
	display: block;
	width: 188px;
	max-width: 100%;
	height: auto;
}

.ustrc-endorse {
	margin: 0;
	line-height: 0;
}

.ustrc-endorse img {
	display: block;
	width: 132px;
	max-width: 100%;
	height: auto;
}

/* Below 900px the mark itself drops to 104px, which would set this line at
   about 12px. One line survives that better than Marathon's two did, but the
   nav is a burger by then and the endorsement is also in the footer, so it
   goes rather than sets small. */
@media (max-width: 900px) {
	.ustrc-endorse {
		display: none;
	}
}

/* The HERO's eyebrow only, one step up the locked scale (12px to 16px) with
   the tracking eased to match.

   Ashley Arnold, 9 Sep 2026, on the hero: "there's this weird spacing where we
   have the tiny text, the big text, like two tiny text … that's the one thing
   I would probably try to change there and make larger." She also asked for
   the date "a bit more front and center", and this line IS the date, so both
   notes land on the same element.

   It pulls the same way as the contrast problem rather than against it. This
   is the line that fails on a busy frame — 2.08:1 on the Boulder five-runner
   candidate, measured — because it is the smallest type in the hero and sits
   highest in the frame, where a bottom-up scrim has run out. Bigger type does
   not lower the 4.5:1 floor at 16px, but it does make the failure less
   punishing and the line easier to place. Do not confuse this with a licence
   to shrink the scrim.

   Scoped to the sticky hero, not to every eyebrow: the section eyebrows are a
   repeating label and enlarging those would undo the hierarchy they exist to
   set. Three of them came out of the home page entirely in the same round. */
.wp-block-cover.ustrc-hero-sticky .is-style-ustrc-eyebrow {
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.16em;
}

/* ---------------------------------------------------------------- stats */

.wp-block-heading.is-style-ustrc-stat {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 800;
	font-size: clamp(2.25rem, 3.6vw, 3.25rem);
	letter-spacing: -0.03em;
	line-height: 1;
	font-variant-numeric: tabular-nums;
	margin: 0;
}

/* -------------------------------------------------------------- buttons */

.wp-block-button.is-style-ustrc-ghost .wp-element-button {
	background-color: transparent;
	color: var(--wp--preset--color--on-dark);
	border: 1.5px solid #5C6862;
	padding-block: calc(1.0625rem - 1.5px);
	padding-inline: calc(2.125rem - 1.5px);
}

.wp-block-button.is-style-ustrc-ghost .wp-element-button:hover {
	background-color: transparent;
	border-color: var(--wp--preset--color--on-dark);
}

/* ------------------------------------------------------- schedule rhythm */

.ustrc-schedule-row {
	display: flex;
	gap: var(--wp--preset--spacing--30);
	align-items: baseline;
}

.ustrc-schedule-row > time {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--label);
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--ink-faint);
	/* 3.25rem broke "10:45 AM" onto two lines in the narrow card column,
	   which is also what made the three day cards different heights. */
	flex: 0 0 4.25rem;
}

/* Cards sitting side by side in a columns block should end level. Without
   this the tallest card sets the row and the others float, so the Saturday
   card ran 22px past its neighbours. */
.wp-block-column > .is-style-ustrc-card {
	height: 100%;
}

/* ------------------------------------------------------- full schedule */

/* One session on the full schedule page: a time gutter and the session
   itself. Grid rather than flex so the time column stays a single width
   down the whole day and the rows line up; it collapses to one column on
   phones, where a 7rem gutter would leave the titles too narrow to read. */
.ustrc-session {
	display: grid;
	grid-template-columns: 9.5rem 1fr;
	gap: var(--wp--preset--spacing--40);
	align-items: start;
	padding-block: var(--wp--preset--spacing--40);
	border-block-start: 1px solid var(--wp--preset--color--rule);
}

.ustrc-session:first-of-type {
	border-block-start: 0;
}

@media (max-width: 600px) {
	.ustrc-session {
		grid-template-columns: 1fr;
		gap: var(--wp--preset--spacing--20);
	}

	/* "Day three · Saturday" is longer than the other two, so at 390px its
	   date wrapped under the title while Thursday and Friday kept theirs
	   inline. All three stack the same way now. */
	.ustrc-day-header {
		flex-direction: column;
		align-items: flex-start !important;
	}
}

/* Highlighted rows: the two keynotes, and nothing else. Until 8 Sep 2026 five
   rows were marked on the schedule and three on home, from two different
   lists, and the only cue was colour: for a deuteranope 11px clay and 11px
   grey-green are near-identical (a11y review, WCAG 1.4.1). Marking only the
   keynotes means every marked row already says "Keynote" in its title, which
   is the non-colour cue, and both pages now agree.

   !important is load-bearing here, not laziness. The time carries
   has-ink-faint-color, and core writes its preset colour classes with
   !important; without matching it, every one of the twenty-nine times
   rendered the same grey and the highlight silently did nothing. */
.ustrc-session.is-ustrc-marked > .ustrc-session__time {
	color: var(--wp--preset--color--clay) !important;
}

/* --------------------------------------------------------------- tables */

/* The tier comparison table. Wide content scrolls inside its own box so the
   page body never scrolls sideways. */
.ustrc-table-scroll {
	overflow-x: auto;
	border: 1px solid var(--wp--preset--color--rule);
	background-color: var(--wp--preset--color--paper);
}

.ustrc-table-scroll table {
	border-collapse: collapse;
	width: 100%;
	min-width: 40rem;
}

.ustrc-table-scroll :is(th, td) {
	text-align: left;
	padding: 0.8125rem 0.9375rem;
	border-bottom: 1px solid var(--wp--preset--color--rule);
	vertical-align: top;
	font-variant-numeric: tabular-nums;
}

.ustrc-table-scroll th {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--label);
	font-weight: 700;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	background-color: var(--wp--preset--color--charcoal);
	color: var(--wp--preset--color--on-dark);
	white-space: nowrap;
}

.ustrc-table-scroll tr:last-child td {
	border-bottom: 0;
}

/* ---------------------------------------------------------- logo lockups */

/* One lockup height per tier — the live site ran logos from 84px to 783px
   wide in a single row, which is what made it read as a classifieds page.

   The height must be a TARGET, not a cap. `max-height` with `width: auto`
   only ever shrinks: the image lays out at its intrinsic size, and since
   WordPress hands the browser a srcset, the browser is free to pick the
   150px thumbnail and render it at 150px. That is exactly what happened —
   Sin Miedo came out 22px wide in a 223px tile while Race Result sat next
   to it at 99px. An explicit `height` plus `object-fit: contain` gives every
   mark the same vertical budget and scales the small ones UP to meet it,
   with no distortion and no dependence on which srcset candidate wins. */
.ustrc-logo-wall {
	display: grid;
	grid-template-columns: repeat(var(--ustrc-logo-cols, 4), minmax(0, 1fr));
	gap: var(--wp--preset--spacing--40) var(--wp--preset--spacing--30);
	align-items: start;
}

.ustrc-logo-cell > img {
	display: block;
	width: 100%;
	height: 3rem;
	object-fit: contain;
	object-position: center;
}

/* Per-tier fallback. Pages override this inline with an area-matched
   height, so a square mark comes out taller than a wide one and the row
   reads as one weight. These values apply to a cell nobody has sized. */
.ustrc-logo-wall[data-tier="presenting"] .ustrc-logo-cell > img { height: 4rem; }
.ustrc-logo-wall[data-tier="exhibitor"]  .ustrc-logo-cell > img { height: 3.25rem; }

/* One height per row. The cells used to stretch to the tallest neighbour,
   which at 390px, in a two-column grid with area-matched inline heights,
   gave a jagged wall of tiles at three different heights with holes in it
   (four reviewers, independently, 8 Sep 2026). A fixed block-size ends the
   negotiation; the image is object-fit: contain inside it. !important because
   pages write a per-cell inline height. */
.ustrc-logo-wall .ustrc-logo-cell {
	box-sizing: border-box; /* the inline padding differs per tier; keep it inside the height */
	block-size: 8.5rem !important;
	min-height: 0 !important;
	overflow: hidden;
}

.ustrc-logo-wall[data-tier="presenting"] .ustrc-logo-cell {
	block-size: 9.5rem !important;
}

.ustrc-logo-wall .ustrc-logo-cell > img {
	max-block-size: 100%;
}

/* A reverse lockup (white on its own dark ground) fills its tile: the tile is
   painted the lockup's colour inline, so the image edges disappear, and the
   mark keeps the same visual height as its white-tile neighbours. */
.ustrc-logo-wall .ustrc-logo-cell.is-ustrc-reverse {
	border-color: transparent !important;
}

.ustrc-logo-wall .ustrc-logo-cell.is-ustrc-reverse > img {
	width: 100%;
	max-block-size: 100%;
}

/* One mark on its own, as ATRA's on the scholarship page: the tile takes the
   column and the mark takes the tile. The shared per-tier heights above are
   for rows of logos; here they left a 160px mark floating in a 240x136 box
   (Jay, 8 Sep 2026: "needs to be bigger, fill its space"). */
.ustrc-logo-wall--feature .ustrc-logo-cell {
	block-size: auto !important;
	padding: 2rem !important;
}

.ustrc-logo-wall--feature .ustrc-logo-cell > img {
	width: 100% !important;
	height: auto !important;
	max-block-size: none;
}

/* A caption under a tile: "Official Apparel", "Official Headwear". The
   category used to live only in alt text, so a buyer could not see which
   categories were taken (brand review F1, F7). */
.ustrc-logo-caption {
	display: block;
	margin-block-start: var(--wp--preset--spacing--20);
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--label);
	font-weight: 500;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	line-height: 1.4;
	text-align: center;
	color: var(--wp--preset--color--ink-muted);
}

/* Five columns at 390px turns each tile into a 72px sliver. The column count
   arrives as an inline custom property, and an inline value beats a
   stylesheet — so the media query has to override the resolved property
   rather than the variable behind it. */
@media (max-width: 700px) {
	.ustrc-logo-wall {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}

	.ustrc-logo-wall[data-tier="presenting"] {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* ------------------------------------------------------------- hero scrim */

/* A flat dim treats the whole frame the same, so text over the brightest
   part of a photograph is always the weakest text on the page. This adds a
   vertical gradient on top of the dim. It used to run 0.55 at the top to
   0.10 at the bottom, "heaviest where the text sits", but the lede sits at
   60 to 65% of the hero's height and the buttons at 75 to 85%, across the lit
   right half of the room, and the a11y pass measured 15 to 16% of the
   background under the lede too light for 4.5:1 at 1440. Heavier at the
   bottom now, and the cover's dim went from 70 to 80. This is the placeholder
   photograph's scrim; when the real hero lands, composed with a quiet left
   half (see the brand steward's photography brief), drop the dim to zero
   rather than keep this as insurance. */
.wp-block-cover.ustrc-photo-slot.is-ustrc-filled > .wp-block-cover__background {
	background-image: linear-gradient(
		180deg,
		rgb(21 26 24 / 0.45) 0%,
		rgb(21 26 24 / 0.40) 50%,
		rgb(21 26 24 / 0.55) 100%
	);
}

/* ------------------------------------------------------------ photo slot */

/* A photo band is a photograph, not a text ground: none of the hero's
   full-frame scrim. What it does need, since 9 Sep 2026, is enough shade in
   the bottom eighth to hold a caption — the place the photograph was taken,
   which is the whole point of running a series of these from different parts
   of the country. Transparent by 22% up, so the picture is untouched
   everywhere it matters. */
/* The caption's scrim, and it is a ::before on the cover rather than a
   gradient on the background span — which is where it lived until 9 Sep 2026,
   the first day real photographs went behind it.

   That version could not work at any alpha. The span carries core's
   `has-background-dim-10` from the block's dimRatio, which is
   `opacity: .1`, and the gradient rode inside it: 0.62 declared, 0.06
   effective. Strengthening the stops to 0.86 changed the measured contrast by
   0.03 of a ratio point, which is what sent me to the computed value —
   `opacity: 0.1` on the span, exactly the trap CLAUDE.md records.

   Raising dimRatio instead is the wrong fix: 10 is deliberate, and the hero
   already taught this project that a dim high enough to carry type turns the
   photograph into a texture behind a colour field.

   Measured before: 3.6–4.0:1 on the mean ground under the caption and
   **2.0:1 on the brightest 5%** — dry grass on the home band, desert gravel
   on the schedule one — against the 4.5:1 that 11px text needs. Note that
   `tools/measure.py` read 0 contrast failures throughout, because its column
   compares declared colours and this is type composited over a photograph.
   Sample the pixels behind the hidden glyphs; do not trust the scorecard for
   anything sitting on an image.

   The caption sits 4.8%–8.1% up from the band's bottom edge, so the ramp
   holds full strength through the bottom third and is gone by 42%. */
.wp-block-cover.ustrc-photo-band.is-ustrc-filled::before {
	content: "";
	position: absolute;
	inset-inline: 0;
	inset-block-end: 0;
	block-size: 42%;
	background-image: linear-gradient(
		0deg,
		rgb(21 26 24 / 0.88) 0%,
		rgb(21 26 24 / 0.88) 30%,
		rgb(21 26 24 / 0.52) 58%,
		transparent 100%
	);
	pointer-events: none;
	z-index: 1;
}

/* Core gives the inner container z-index 1, and the ::before above comes
   later in the box tree, so on a tie it would paint over the caption it
   exists to make readable. */
.wp-block-cover.ustrc-photo-band > .wp-block-cover__inner-container {
	z-index: 2;
}

/* The caption sits at the bottom of the frame and on the page's content
   column. Bottom-alignment is align-self on the inner container rather than
   the block's contentPosition attribute, because that attribute switches core
   to flex alignment and takes the constrained layout — and its 1160px centred
   measure — away with it, which drops the caption onto the band's 40px
   padding while every other line on the page starts at 140px. */
.wp-block-cover.ustrc-photo-band > .wp-block-cover__inner-container {
	align-self: flex-end;
	inline-size: 100%;
	/* Core's has-global-padding on the inner container adds its own gutter on
	   top of the band's, which put the caption at 64px on a 390px screen while
	   every other band's text starts at 40px. The band owns the gutter; the
	   container just centres inside it. */
	padding-inline: 0 !important;
}

/* Mono, small, letterspaced — and given a shadow as well as the gradient,
   because a photograph can put anything behind it and a caption that
   disappears over snow is a caption nobody sees. */
.ustrc-photo-caption {
	text-shadow: 0 1px 3px rgb(21 26 24 / 0.7);
}

/* The photographer's name is quieter than the place. Both are facts; only one
   of them is the argument the series is making. */
.ustrc-photo-credit {
	color: var(--wp--preset--color--on-dark-muted);
}

@media (max-width: 600px) {
	/* On a phone the place stays and the credit goes: at 390px the line wraps
	   and a wrapped caption reads as body copy that has lost its paragraph. */
	.ustrc-photo-credit {
		display: none;
	}
}

@media (max-width: 700px) {
	.wp-block-cover.ustrc-photo-band {
		min-height: 38vh !important;
	}
}

/* ---- Statement variant: the band that carries copy --------------------
   patterns/photo-band-statement.php, 9 Sep 2026. Jay on the home page's
   first trail band: "I don't like photos just shoved into the design without
   a purpose... It should either carry some text or have some other purpose."

   Two things the wordless band's rules get wrong for it.

   1. The scrim. photo-band's ::before covers the bottom 42% for a single
      11px caption line. A heading and a lede need the upper frame too, and
      they need more of it: measured on the Boulder frame, the caption strip
      alone left the heading's ground at the top of its range. Four classes
      here rather than three on purpose — the base rule is (0,3,0) and an
      equal-weight override would be decided by source order, which WordPress
      does not let us promise because it inlines block styles per page. This
      is (0,4,0) and wins outright. Same lesson as the triptych's mobile
      strip.

   2. The mobile floor. 38vh is right for a photograph with a caption on it
      and far too short for one carrying two paragraphs — the copy would
      overflow the frame it is set on. 62vh on a phone, and the copy goes
      full-width there via the existing .ustrc-band-copy rule. */
.wp-block-cover.ustrc-photo-band.ustrc-photo-band-statement.is-ustrc-filled::before {
	block-size: 100%;
	background-image: linear-gradient(
		0deg,
		rgb(21 26 24 / 0.92) 0%,
		rgb(21 26 24 / 0.88) 34%,
		rgb(21 26 24 / 0.62) 62%,
		rgb(21 26 24 / 0.22) 100%
	);
}

@media (max-width: 700px) {
	.wp-block-cover.ustrc-photo-band.ustrc-photo-band-statement {
		min-height: 62vh !important;
	}
}

/* ---- Triptych band: three portrait frames, three places ----------------
   patterns/photo-band-triptych.php. The companion to the single full-bleed
   band, and it exists because the archive's best photographs are portrait
   (reference/photo-review-2026-09-09.md): a vertical frame cropped into a
   56vh band keeps the middle and discards the sky and the trail, which in a
   trail photograph is the whole picture. */

/* 3:4 rather than the frames' native 2:3. At 1440 a 2:3 frame is 445x667 and
   the band lands near 800px, taller than the hero's own next-band reveal; 3:4
   gives 445x593 and crops 11% off a 2:3 original, split top and bottom. The
   ratio holds whether or not a photograph has landed, so a filled frame and a
   labelled one are the same height — the same rule the keynote cards follow,
   for the same reason. */
.ustrc-photo-slot.ustrc-triptych-frame {
	min-height: 0;
	aspect-ratio: 3 / 4;
}

/* Bias the crop upward by a touch. These frames are runners in landscape, so
   the sky and the ridge are the top of the picture and the interest is above
   centre; a centred window on a 2:3 original takes as much off the sky as off
   the trail. */
.ustrc-photo-slot.ustrc-triptych-frame > figure > img,
.ustrc-photo-slot.ustrc-triptych-frame > .wp-block-image > img,
.ustrc-photo-slot.ustrc-triptych-frame > img {
	object-position: center 42%;
}

@media (max-width: 1000px) {
	/* Each frame is under 300px here, where "BOULDER, COLORADO · TWAIN
	   WILKINS" wraps to three lines of 10px mono. The place is the argument
	   the series makes; the credit is the courtesy. Below 782px the frames
	   go full width and the credit comes back. */
	.ustrc-triptych-caption .ustrc-photo-credit {
		display: none;
	}
}

/* Below core's own column breakpoint the row becomes a snap-scrolling strip
   rather than three stacked verticals.

   Stacking is what core does and it is wrong here twice over: three 3:4
   frames at 390px wide is about 1,650px of band, in the middle of a home page
   that already carries nine of them; and the section's whole point is three
   places seen together, which stacking dismantles. The alternative considered
   was hiding frames two and three, which is cheaper and quietly deletes two
   photographs and two photographers' credits from every phone.

   So: one frame and a bit visible, swipe for the rest, no JavaScript.

   The selectors below carry the band's class as well as the row's, which is
   deliberate and not decoration. Core's two rules are
   `.wp-block-columns { flex-wrap: wrap !important }` at (0,1,0) and
   `.wp-block-columns:not(.is-not-stacked-on-mobile) > .wp-block-column
   { flex-basis: 100% !important }` at (0,3,0) — and the second TIES with the
   obvious `.ustrc-triptych-row.wp-block-columns > .wp-block-column`, which
   leaves it to be settled by which stylesheet WordPress happens to emit
   second. Core inlines its block styles per-page, so that order is not ours
   to promise. The extra class takes these to (0,2,0) and (0,4,0) and settles
   it on specificity instead. Computed flex-basis on the mirror is the check,
   not this comment: core's layout classes have won six arguments with this
   stylesheet already (CLAUDE.md). */
@media (max-width: 781px) {
	.ustrc-photo-triptych .ustrc-triptych-row.wp-block-columns {
		flex-wrap: nowrap !important;
		overflow-x: auto;
		scroll-snap-type: x mandatory;
		/* No scrollbar gutter reserved: the strip is the interaction, and a
		   permanent trough under a photograph reads as a mistake. */
		scrollbar-width: none;
	}

	/* 80% of the row, capped. The percentage alone is right on a phone — a
	   248px frame at 390 with the next one peeking — and wrong just under the
	   breakpoint, where 80% of 701px is a 561x748 frame and the band jumps
	   from 520px to 905px over one pixel of resize. Any design that changes
	   column count changes height at its breakpoint; the cap is what keeps
	   the step small. 22rem holds the frame at 352px, so the strip reads the
	   same from 560px up and the peek gets wider rather than the frame
	   taller. */
	.ustrc-photo-triptych .ustrc-triptych-row.wp-block-columns > .wp-block-column {
		flex-basis: min(80%, 22rem) !important;
		flex-grow: 0;
		flex-shrink: 0;
		scroll-snap-align: start;
	}

	.ustrc-photo-triptych .ustrc-triptych-row.wp-block-columns::-webkit-scrollbar {
		display: none;
	}
}

/* Page headers with a photograph: the image sits a little high in the frame,
   since the title and lede occupy the lower two thirds. */
.wp-block-cover.ustrc-page-header > img {
	object-position: center 35%;
}

/* Placeholder for a photograph that has not been shot yet. Visible in the
   editor and on staging; the label is what tells the photographer what the
   slot needs. Remove the wrapper once a real image lands. */
.ustrc-photo-slot {
	position: relative;
	background-color: var(--wp--preset--color--deep);
	min-height: 12rem;
	overflow: hidden;
}

/* Speaker headshots crop square so the roster grid stays even. The uniform
   1:1 crop is the point — it is what the flipbox plugin never enforced. */
.ustrc-photo-slot.is-ustrc-square {
	min-height: 0;
	aspect-ratio: 1 / 1;
}

/* An empty slot does not need to be as tall as a filled one. Three unfilled
   keynote boxes at full height put more flat grey on the home page than the
   headline above them could carry. A filled slot keeps its full height. */
.ustrc-photo-slot:not(.is-ustrc-filled) {
	min-height: 8.5rem;
}

/* A slot with a real photograph in it. Pages fill these, not patterns —
   the pattern ships the empty slot so a new page starts labelled, and the
   image ID lives in page content where it belongs. That is also what keeps
   the theme portable: no media IDs baked into the theme. */
.ustrc-photo-slot > figure,
.ustrc-photo-slot > .wp-block-image {
	margin: 0;
	height: 100%;
}

.ustrc-photo-slot > figure > img,
.ustrc-photo-slot > .wp-block-image > img,
.ustrc-photo-slot > img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Bias the square crop upward. Fifteen of the twenty-three headshots are
   portrait — Todd Janssen's is 356x640, where a centred 1:1 crop throws away
   22% off the top and the same off the bottom. Faces sit in the upper third
   of a portrait, so a centred window crops foreheads and keeps torso. 30%
   holds the face and drops the surplus from below.

   Landscape frames are unaffected: an image wider than its box is cropped
   horizontally, and the vertical position does nothing. */
.ustrc-photo-slot.is-ustrc-square > figure > img,
.ustrc-photo-slot.is-ustrc-square > .wp-block-image > img,
.ustrc-photo-slot.is-ustrc-square > img {
	object-position: center 30%;
}

/* Per-photograph focus. The block editor validates a block by re-running its
   save() and comparing the HTML, and core's image save() emits no
   object-position at all — so an inline style="object-position:..." on the img
   made every filled slot an invalid block ("Block contains unexpected or
   invalid content", Jase Miller, 9 Sep 2026). A class is a first-class block
   attribute and validates, so the crop lives here instead, one rule per value
   in tools/media-map.json's `focus` map. check.sh fails the build if a value
   there has no rule here; a cover does not need one, because a cover has
   core's own focalPoint.

   !important because the two crop defaults above -- the square bias and the
   triptych's -- are three classes deep (`.ustrc-photo-slot.is-ustrc-square >
   .wp-block-image > img`), and a focus rule written at the obvious two ties
   with them rather than winning. Measured: written without this, home's
   keynote crops took their focus and every square headshot on /speakers/ and
   /advisory-committee/ silently kept the 30% default. A per-photograph crop
   is the more specific intent and should not depend on staying below a
   generic rule in the file. */
.ustrc-photo-slot > figure.ustrc-focus-40-30 > img { object-position: 40% 30% !important; }
.ustrc-photo-slot > figure.ustrc-focus-45-75 > img { object-position: 45% 75% !important; }
.ustrc-photo-slot > figure.ustrc-focus-50-0 > img { object-position: 50% 0% !important; }
.ustrc-photo-slot > figure.ustrc-focus-50-5 > img { object-position: 50% 5% !important; }
.ustrc-photo-slot > figure.ustrc-focus-50-30 > img { object-position: 50% 30% !important; }
.ustrc-photo-slot > figure.ustrc-focus-50-40 > img { object-position: 50% 40% !important; }
.ustrc-photo-slot > figure.ustrc-focus-50-45 > img { object-position: 50% 45% !important; }
.ustrc-photo-slot > figure.ustrc-focus-50-50 > img { object-position: 50% 50% !important; }
.ustrc-photo-slot > figure.ustrc-focus-55-5 > img { object-position: 55% 5% !important; }
.ustrc-photo-slot > figure.ustrc-focus-60-30 > img { object-position: 60% 30% !important; }

/* The label is generated content, which Chrome, Firefox and Safari all expose
   to the accessibility tree; a screen reader announced "Sally McRae portrait
   · photo to come" three times on the home page. The / "" alternative text
   keeps it visual only.

   It used to read attr(data-shot), which named the exact frame the slot
   wanted. That attribute is gone — an unknown attribute on a block's root
   element is what the editor's validator objects to — and the shot label now
   travels in the block's metadata.name, which the List View shows the editor
   and the browser never sees. Generic text is the better trade twice over:
   the specific label was an internal build note on a public page. */
.ustrc-photo-slot::after {
	content: "Photograph to come" / "";
	position: absolute;
	inset-inline-end: 0.75rem;
	inset-block-end: 0.75rem;
	font-family: var(--wp--preset--font-family--mono);
	font-size: 0.5625rem;
	letter-spacing: 0.13em;
	text-transform: uppercase;
	color: var(--wp--preset--color--on-dark-muted);
	background-color: rgb(21 26 24 / 0.72);
	border: 1px solid #47534E;
	padding: 0.3125rem 0.5625rem;
}

/* Filled slots drop the label. Declared after the rule that draws it — an
   override that loses on source order is not an override. */
.ustrc-photo-slot.is-ustrc-filled::after {
	content: none;
}

/* Keynote cards are 4:3 whether or not a photograph has landed, so a filled
   card and a labelled one stay the same height: equal weight is a card-size
   rule (CLAUDE.md). The pattern's 280px spacer held the shape before; the
   aspect ratio does now, and the spacer collapses (inline height, hence
   !important). On a phone the placeholder is 232px, not 280 (RD review,
   finding 21: three near-empty screens in a row). */
.ustrc-overhang .ustrc-photo-slot {
	aspect-ratio: 4 / 3;
	min-height: 0;
}

.ustrc-overhang .ustrc-photo-slot > .wp-block-spacer {
	height: 0 !important;
}

/* A logo cell with the real mark in it. One lockup height per tier is the
   whole point of the wall — the live site ran logos from 84px to 783px wide
   in a single row, which is what made it read as a classifieds page. */
/* ----------------------------------------------------------- a11y & motion */

/* Two-tone ring from existing tokens. The old 2px green outline was 2.94:1
   against sand and 3.21:1 against limestone (WCAG 1.4.11 wants 3:1), and
   :where() gave it zero specificity, so any future outline rule would have
   beaten it. theme.json used to add a green :focus outline as well, which
   fired on pointer focus too (core moves focus into the open menu after a
   tap, and that is not :focus-visible), so a green ring sat on "Home" every
   time the menu opened; it is gone, and rings are keyboard-only. Charcoal outline with a limestone halo on light bands, inverted
   on dark ones. tabindex="-1" is excluded: it marks programmatic targets such
   as the skip link's <main>, which should not light up edge to edge. */
:is(a, button, .wp-element-button, [tabindex]:not([tabindex="-1"])):focus-visible {
	outline: 2px solid var(--wp--preset--color--charcoal);
	outline-offset: 2px;
	box-shadow: 0 0 0 4px var(--wp--preset--color--limestone);
}

.is-style-ustrc-band-dark :is(a, button, .wp-element-button, [tabindex]:not([tabindex="-1"])):focus-visible,
.ustrc-footer :is(a, button, .wp-element-button, [tabindex]:not([tabindex="-1"])):focus-visible {
	outline-color: var(--wp--preset--color--limestone);
	box-shadow: 0 0 0 4px var(--wp--preset--color--charcoal);
}

/* The open menu is a limestone surface, so it takes the light-band ring; core
   zeroes box-shadow on menu links, and the halo alone would have been
   invisible there. */
.wp-block-navigation__responsive-container.is-menu-open :is(a, button):focus-visible {
	outline-color: var(--wp--preset--color--charcoal);
	box-shadow: 0 0 0 4px var(--wp--preset--color--limestone) !important;
}

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}


/* ------------------------------------------------------- section seams */

/* A band change between two colours this close together does not need a
   hard edge — it needs a hairline. The hard edges were doing the work of a
   page break, which is exactly the impression to avoid. */
.wp-block-group.has-sand-background-color.alignfull {
	border-block-start: 1px solid var(--wp--preset--color--rule);
	border-block-end: 1px solid var(--wp--preset--color--rule);
}

/* Where a dark band meets a light one, a short gradient carries the eye
   across instead of stopping it. Applied on the dark side so the light
   ground stays clean. */
.wp-block-group.is-style-ustrc-band-dark.alignfull {
	position: relative;
}

/* Removed 31 Aug 2026. A 96px darkening gradient at the foot of every dark
   full-bleed band was meant to carry the eye across a boundary. Where a dark
   band meets a light one it did nothing the edge did not already do, and where
   two dark bands meet — Nashville (#1B211F) into Dirt Circus (#151A18), four
   steps apart — it turned the join into a muddy vignette with a seam through
   the middle of it. Two adjacent darks want a clean edge or a real change of
   material, not a fade.

.wp-block-group.is-style-ustrc-band-dark.alignfull::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	inset-block-end: 0;
	height: 6rem;
	pointer-events: none;
	background: linear-gradient(180deg, transparent 0%, rgb(21 26 24 / 0.45) 100%);
}
*/


/* ----------------------------------------------------------- overhang */

/* Content that straddles the seam between two bands. Every section on this
   site is a full-bleed rectangle of flat colour, and stacking those reads as
   slides rather than as a page — one element crossing a boundary is what
   tells the eye it is looking at one continuous surface.

   The guard matters: an overhanging grid in the last band would hang over
   the footer, so it only applies where a band has something after it. Below
   the columns breakpoint the cards stack one per row and there is no seam
   left to straddle, so it switches off. */
/* Two things this took three attempts to get right, both worth keeping.
   A negative bottom margin will not do it: it collapses through the band and
   drags the band's own edge up, so the cards sit tidily inside a shorter
   band. And the band's padding is authored inline, so no stylesheet can zero
   it — the pattern that overhangs sets its own bottom padding to 0, and this
   rule only moves the cards. A relative offset shifts them visually and
   leaves their layout box in place, so the band keeps its height. */
.wp-block-group.alignfull:not(:last-child) .ustrc-overhang {
	position: relative;
	z-index: 2;
	inset-block-start: var(--wp--preset--spacing--50);
}

@media (max-width: 781px) {
	.wp-block-group.alignfull:not(:last-child) .ustrc-overhang {
		inset-block-start: var(--wp--preset--spacing--50);
	}

	.wp-block-group.alignfull:not(:last-child):has(.ustrc-overhang) {
		padding-block-end: var(--wp--preset--spacing--80);
	}

	/* Also true of the browsers without :has() — they get the band's own
	   padding and no overhang, which is a correct page, just a flatter one. */
}


/* Dark hairlines. The dark card's rule was #3B4744 on #232A28, 1.4:1, and the
   featured tier card's inner rules #333E3A on deep, 1.3:1: the six and two
   "faint rules" left on home and partner after the light token was retuned.
   #47534E is 1.9:1 on the dark card and 2.1:1 on deep. */

/* Lede measure. Section ledes and page-header ledes ran 110 to 155
   characters per line at 21px across the full 1160px content column, against
   a rubric target of 75; the reference site holds about 65. The constrained
   layout centres any child with a max-width (auto margins), so the measure
   is set with end padding instead, which keeps the left edge. border-box,
   because padding added outside the 1160px max-width overflowed the column
   and the auto margins collapsed to zero, sliding every lede to the band's
   edge (measured, then reverted, 8 Sep 2026). At 1440 the measure comes out
   near 780px, about 75 characters. Only direct children of a band: statement
   rows and card copy set their own measure. */
@media (min-width: 900px) {
	.alignfull.is-layout-constrained > p.has-large-font-size,
	.wp-block-cover__inner-container > p.has-large-font-size,
	main .is-layout-flex > p.has-large-font-size {
		box-sizing: border-box;
		padding-inline-end: 28%;
	}

	/* A body-size paragraph set straight on a band (the tier note on the
	   partner page) ran 155 characters a line. */
	.alignfull.is-layout-constrained > p.has-small-font-size {
		box-sizing: border-box;
		padding-inline-end: 40%;
	}
}

/* Session descriptions and cast lines sit in a 900px column beside the time
   column and ran about 120 characters a line at 15px; news excerpts the full
   1160px. These containers are flow layouts, so a max-width keeps the left
   edge. 75ch of Newsreader is about 560px, a little under 80 characters. */
.ustrc-session .has-small-font-size,
.ustrc-news-item .wp-block-post-excerpt {
	max-width: 75ch;
}

/* --------------------------------------------------------- band seams */

/* Full-bleed sections must butt against each other. The global block gap
   puts a margin between every top-level block, which drew a strip of the
   page background under each band — nine light seams down the home page,
   including one between the dark header and the dark hero. Each band already
   carries its own generous padding, so the gap adds nothing and costs the
   page its continuity. */
.wp-site-blocks > .alignfull,
.entry-content > .alignfull,
.wp-block-post-content > .alignfull {
	margin-block-start: 0;
	margin-block-end: 0;
}

/* The same gap sits between the three top-level blocks themselves: header
   part, <main>, footer part. Core sets it with :where(.wp-site-blocks) > *,
   at zero specificity, so a 24px strip of limestone showed between the last
   band of every page and the closing band; the brand steward read it on all
   nine pages as the tell of "a block theme with a palette". Under the light
   header the same strip is invisible, which is why nobody reported it there.
   Bands touch; the change of colour happens in one pixel row. The footer
   part's own two bands are closed up by blockGap 0 in parts/footer.html, and
   the second rule is the fallback for a part rendered without it. */
.wp-site-blocks > * {
	margin-block-start: 0;
}
.ustrc-footer > * + * {
	margin-block-start: 0;
}


/* ------------------------------------------------------ statement rows */

/* A label beside its statement, hairline between. Mission runs three lines
   and Vision runs ten, so a column grid gave three ragged bottoms; a row
   takes whatever length the copy is.

   The hairline was 1px of --color--rule (#D3D8CF) on limestone, which
   measures 1.25:1 — present in the markup, absent to the eye, so the rows
   read as text floating in space with an occasional smudge between them.
   Moss at 1px is 5.4:1 and matches the column rules in whos-in-the-room and
   rd-path, so the site divides its sections the same way throughout.

   The token itself is left alone deliberately: --color--rule is also the
   border on cards and logo cells, where a barely-there edge is the right
   answer, and the palette is Jay's call rather than something to retune from
   a CSS file. */
.ustrc-statement {
	display: grid;
	grid-template-columns: 11rem 1fr;
	gap: var(--wp--preset--spacing--50);
	align-items: start;
	border-block-start: 1px solid var(--wp--preset--color--rule);
}

.ustrc-statement:last-child {
	border-block-end: 1px solid var(--wp--preset--color--rule);
}

@media (max-width: 700px) {
	.ustrc-statement {
		grid-template-columns: 1fr;
		gap: var(--wp--preset--spacing--20);
	}
}


/* --------------------------------------------------------- photo bands */

/* Copy occupies the left panel, which is the half of the frame every
   candidate is screened on. Not centred: a headline centred over a
   photograph fights the picture instead of sitting in the space left for it. */
.ustrc-photo-band .ustrc-band-copy {
	max-width: 55%;
}

@media (max-width: 900px) {
	.ustrc-photo-band .ustrc-band-copy {
		max-width: 100%;
	}
}

/* Default is vertically centred. is-ustrc-top pins the copy high, for a
   frame with something low in the copy zone — the trail shot has a distant
   figure about 20% in from the left, which a centred headline lands on. */
.ustrc-photo-band.is-ustrc-top .wp-block-cover__inner-container {
	align-self: flex-start;
}

/* No scrim by default: legibility is bought when the frame is chosen, and a
   blanket overlay muddies the photograph. This is for the frame that will
   not carry it on its own — a left panel that is mid-blue rather than dark,
   say — and it is weighted to the copy side so the subject stays clean. */
.ustrc-photo-band.is-ustrc-scrim > .wp-block-cover__background {
	background-image: linear-gradient(
		90deg,
		rgb(21 26 24 / 0.55) 0%,
		rgb(21 26 24 / 0.35) 45%,
		rgb(21 26 24 / 0) 75%
	);
	opacity: 1;
}

/* ------------------------------------------------------- footer nav */

/* The footer row is a flex list of one-word links in mono caps. With only a
   gap between them the row read as one undifferentiated string — "ABOUT NEWS
   CLIMATE CHAMPIONS CARBON FOOTPRINT" — and two of the four labels were two
   words, so there was no way to tell where one link ended. A separator
   between items is the whole fix. It is generated rather than typed so
   editors adding a link in place get one for free.

   It hangs off the END of each item but the last. As a ::before on each item
   but the first, a wrapped row at 390px began with a stray "·" (brand steward
   and a11y review, 8 Sep 2026); a trailing dot at a line end is ordinary
   typography. The / "" keeps screen readers from announcing it. */
.ustrc-footer-nav > p:not(:last-child)::after {
	content: "·" / "";
	margin-inline-start: var(--wp--preset--spacing--30);
	color: var(--wp--preset--color--ink-muted);
}

/* ------------------------------------------------------------- news list */

/* A hairline between items, at the recipients' rhythm. The list was a query
   loop at default spacing: 160px between items with nothing to hold them. */
.ustrc-news-item + .ustrc-news-item {
	border-block-start: 1px solid var(--wp--preset--color--rule);
}

/* Pagination targets. "1 2 3 … 11" at 11px mono were four 8x18px targets
   about 10px apart (WCAG 2.5.8 wants 24px), in a colour at 3.21:1. */
.wp-block-query-pagination .page-numbers,
.wp-block-query-pagination-previous,
.wp-block-query-pagination-next {
	display: inline-grid;
	place-items: center;
	min-inline-size: 1.5rem;
	min-block-size: 1.5rem;
	padding-inline: 0.25rem;
}

.wp-block-query-pagination .page-numbers.current {
	color: var(--wp--preset--color--clay);
	text-decoration: none;
}

/* --------------------------------------------------------- quote rows */

/* Three attributed quotes side by side. The attribution has to stay welded
   to the bottom of its own quote or the row reads as six loose fragments,
   so each column is a flex box and the quote takes the slack. */
.ustrc-quote {
	display: flex;
	flex-direction: column;
}

.ustrc-quote > p:first-child {
	flex: 1 1 auto;
}

/* --------------------------------------------------- linked logo cells */

/* Sponsors paid to be here, so their marks link out. The anchor sits between
   the wall's grid and the cell, which makes the anchor the grid item — it has
   to carry the stretch itself or the cell collapses to its own content height
   and the row goes ragged. The cell keeps its data-brand attribute untouched,
   which is what the build step matches on when it swaps the text fallback for
   a real mark. */
.ustrc-logo-wall > .ustrc-logo-link {
	display: block;
	height: 100%;
	text-decoration: none;
	color: inherit;
	border-radius: 2px;
}

.ustrc-logo-wall > .ustrc-logo-link > .ustrc-logo-cell {
	height: 100%;
	transition: border-color 120ms ease;
}

.ustrc-logo-wall > .ustrc-logo-link:hover > .ustrc-logo-cell,
.ustrc-logo-wall > .ustrc-logo-link:focus-visible > .ustrc-logo-cell {
	border-color: var(--wp--preset--color--clay) !important;
}

/* ------------------------------------------------------ sticky header */

/* Jay asked for the navigation to stay put while scrolling. The first attempt
   put position:sticky on .ustrc-header, did nothing on the live site, and
   tested fine in the preview. The reason is the DOM:

       .wp-site-blocks
         └─ header.wp-block-template-part      the part wrapper
              └─ header.ustrc-header           where sticky was

   A sticky element travels inside its own parent's box, and the template-part
   wrapper is exactly as tall as the header inside it — so there was no room to
   travel and the header scrolled straight off. The preview has no wrapper, its
   header being a direct child, so it stuck there and looked right. That is the
   fourth time this harness has disagreed with the site about chrome: check the
   header against staging, never against the preview.

   The wrapper is therefore the sticky element. :has() picks it out by what it
   contains rather than by position, so reordering the template cannot break
   it, and .ustrc-header keeps its own declaration for a template that renders
   it without a wrapper.

   z-index clears .ustrc-overhang, which lifts the keynote cards to 2 so they
   can hang past their band.

   There was a shrink-on-scroll state here, driven by assets/js/header.js and
   a sticky sentinel, because a 161px band could not stay on screen. The band
   is 83px now and the whole mechanism was solving a problem the smaller
   lockup removed — so the script, the enqueue and the two states are gone.
   One tight bar, no JavaScript, nothing to fall out of step. */
.wp-site-blocks > .wp-block-template-part:has(> .ustrc-header),
.ustrc-header {
	position: sticky;
	inset-block-start: 0;
	z-index: 50;
}

/* Logged in, the admin bar is fixed at the top and a sticky header at 0 slides
   underneath it: the lockup's top was cut off on every scroll for Jay and Kim,
   who are always logged in when they review (seen 8 Sep 2026). Core exposes
   the bar's height as a custom property; 32px is its desktop value. */
.admin-bar .wp-site-blocks > .wp-block-template-part:has(> .ustrc-header),
.admin-bar .ustrc-header {
	inset-block-start: var(--wp-admin--admin-bar--height, 32px);
}

:root:has(body.admin-bar) {
	scroll-padding-block-start: calc(5.5rem + var(--wp-admin--admin-bar--height, 32px));
}

/* The lockup. 132x72 of artwork in an 83px band, down from 170x128 in 161px —
   Jay's note on that was "too much chunky white space", and most of the 161px
   was empty limestone. The file changed as well as the size; see
   parts/header.html for why. Sourced at 487x264, so it is still oversampled
   more than three times at 2x. */
.ustrc-lockup {
	margin: 0;
	flex: 0 0 auto;
	line-height: 0;
}

.ustrc-lockup img {
	display: block;
	width: 132px;
	max-width: 100%;
	height: auto;
}

/* Anchor targets have to clear the bar, or #tiers and #partners land with
   their first line behind it. */
:root {
	scroll-padding-block-start: 5.5rem;
}

/* The image block writes its width inline (style="width:132px"), and an
   inline declaration beats every stylesheet rule — so the responsive widths
   below need !important or they never apply. Measured 132px at 390 before
   this was understood. Fifth time on this project an inline style has won
   silently; check computed values, not the CSS. */
@media (max-width: 900px) {
	.ustrc-lockup img {
		width: 104px !important;
	}
}

/* --------------------------------------------- four-up column alignment */

/* whos-in-the-room and rd-path are four columns of rule, heading and
   paragraph. The headings run from one word ("Runners") to three ("Growing
   without breaking"), so each paragraph started at whatever depth its own
   heading happened to end and the row read as four blocks that had drifted
   apart. Jay asked for it neater.

   The fix is subgrid: the columns block becomes the grid and defines three
   rows, and each column opts into its parent's rows rather than its own. Rule,
   heading and paragraph then sit on three shared baselines, and a row is only
   as tall as the tallest thing in it — so when every heading fits on one line
   there is no reserved second line going spare.

   core ships wp:columns as flex, so this overrides display as well. blockGap
   still resolves to `gap`, which grid honours.

   The selector carries core's own two classes on purpose. Core's rule is
   `.wp-block-columns.is-layout-flex { display: flex }`, two classes deep, and
   a one-class `.ustrc-cols-aligned { display: grid }` lost to it on every
   page from the day it was written: the preview, which has no layout classes,
   showed a grid, and staging showed four independent flex columns whose auto
   rows stretched to the column height, which is why the paragraphs sat at
   four different depths (Jay, 8 Sep 2026, on the RD cards). Measured
   computed display: flex. Sixth time a fix has looked right in the harness
   and lost on the site; check the computed value.

   The fallback reserves two lines of heading instead. It is looser, but it
   aligns, and subgrid has been in all three engines since 2023. */
.wp-block-columns.ustrc-cols-aligned.is-layout-flex,
.wp-block-columns.ustrc-cols-aligned,
.ustrc-cols-aligned {
	display: grid;
	grid-template-columns: repeat(var(--ustrc-cols, 4), minmax(0, 1fr));
	grid-template-rows: auto auto auto;
	align-items: start;
}

.ustrc-cols-aligned > .wp-block-column {
	display: grid;
	grid-row: span 3;
	grid-template-rows: subgrid;
	row-gap: var(--wp--preset--spacing--20);
	min-width: 0;
}

.ustrc-cols-aligned > .wp-block-column > h3 {
	align-self: start;
	text-wrap: balance;
}

/* Only a column that sets a rule gets a rule. This used to be a bare
   border-top-style: solid on every aligned column, and a style with no
   declared width means the initial width, medium, which is 3px: the RD-path
   columns, whose 2px rule sat on an inner group, grew a second 3px
   charcoal rule on the column above it. The brand steward saw "column rules
   in two weights" without knowing why. Measured 8 Sep 2026. */
.ustrc-cols-aligned > .wp-block-column[style*="border-top-width"] {
	border-top-style: solid;
}

@supports not (grid-template-rows: subgrid) {
	.ustrc-cols-aligned > .wp-block-column {
		grid-row: auto;
		grid-template-rows: auto auto 1fr;
		align-content: start;
	}
	.ustrc-cols-aligned > .wp-block-column > h3 {
		min-height: 2.5em;
	}
}

/* One column at the narrow end, where core would have stacked the flex
   version anyway. */
@media (max-width: 780px) {
	.ustrc-cols-aligned {
		--ustrc-cols: 1;
		grid-template-rows: none;
	}
	.ustrc-cols-aligned > .wp-block-column {
		grid-row: auto;
		grid-template-rows: none;
	}
	.ustrc-cols-aligned > .wp-block-column > h3 {
		min-height: 0;
	}
}

/* ------------------------------------------------- header, small screens */

/* At 390px the header measured 169px — a fifth of the phone screen, and
   sticky, so a fifth of every screen. The row wrapped: 40px of side padding
   each way left 310px, and lockup + hamburger + Register came to about 304px,
   so any rounding tipped the Register onto a second line under the logo.

   Padding is set inline from block attributes, so the override needs
   !important; core's generated layout class supplies the flex-wrap, so that
   does too. Tighter side padding buys 48px, the lockup gives up 12px, and the
   Register button gives up its desktop padding. The row no longer wraps and
   the header comes in near 60px, about 7% of the viewport. */
@media (max-width: 600px) {
	.ustrc-header {
		padding-inline: var(--wp--preset--spacing--30) !important;
	}
	.ustrc-header > .wp-block-group {
		flex-wrap: nowrap !important;
		gap: var(--wp--preset--spacing--30) !important;
	}
	.ustrc-header .ustrc-lockup img,
	.ustrc-lockup img {
		width: 92px !important;
	}
	.ustrc-header .wp-block-button__link {
		padding: 0.5625rem 0.875rem !important;
		font-size: 0.875rem !important;
	}
}

/* ----------------------------------------------- header, tablet widths */

/* Core's navigation block collapses to a hamburger below 600px and not a
   pixel above. At 768 — iPad portrait — all eight items render inline, and
   eight items plus Register plus the lockup do not fit in 736px, so the row
   stacked three deep: 202px of header, a fifth of the screen, and sticky.

   The block has no attribute for a different breakpoint, so this reproduces
   core's under-600 behaviour up to 1000px: show the open button, hide the
   inline list unless the menu is open. Core's own overlay rules are keyed on
   .is-menu-open and live outside any media query, so once the button toggles
   the class the fixed overlay, the close button and the colours set on the
   block (overlayBackgroundColor, overlayTextColor) all apply unchanged. The
   toggle itself is core's script and is width-agnostic.

   1080 rather than 1024: at exactly 1024 the inline row needs about 900px of
   a 944px content width, which is one label change from wrapping again.

   **That label change arrived on 9 Sep 2026** (Jay: "add Find a Trail Race to
   our menu somewhere"), four words where every other item was one. Measured,
   the row then needed 1187px and the band went 119px to 199px below that, so
   the breakpoint went to 1260 — and it stayed there for about an hour, until
   Jay asked for the navigation to be consolidated and the answer made the
   number wrong again in the other direction.

   The menu is now six items and 419px rather than nine and 612px: Home went
   (the lockup has always linked to /), and Advisory and Scholarship went to
   the footer, where they already were. So the whole row is about 705px of a
   1360px content width, and it fits comfortably down to the low 800s.

   **Still 1000, but the headroom is now 12px, not 48px.** The type scale went
   up on 9 Sep 2026 and `small` went 15px to 16px, which widened every nav
   label: the seven-item row stacked at 952px before and stacks at **984px**
   now. The note below still says 48px of clearance; it is 12px. That makes the
   rule about re-measuring HEIGHT load-bearing rather than cautionary, because
   a single longer nav label will now break the row.

   **The brand block stays 132px, and here is the sweep in case that is
   reopened.** Jay said Marathon Printing's credit "gets lost" in the dark
   band. Colour was not the fault -- the knockout filter measures the ink at
   17.33:1 against the band, and his screenshot predated the upload. Size is
   the only other lever, because the credit sits under the mark at the mark's
   width (his placement decision, matching production) and its artwork has just
   3px of croppable margin. Measured, brand width against where the row
   stacks: 132px/984, 140/992, 148/1000, 156/1008, 160/1012, 172/1024, with the
   credit's second line scaling 11.0px to 14.3px across that range. So anything
   above 140px needs the breakpoint to move with it, and 160px would need 1016
   -- which still keeps 1024px laptops inline, but with 8px to spare. Jay
   looked at the rendered white credit at 132px and accepted it, so none of
   that was spent. If it is reopened, take the breakpoint up rather than
   shrinking a sold sponsor's credit.

   **The original reasoning, 1000 rather than 1080.** 900 was the tempting number — this stylesheet
   already breaks there, where the lockup drops to 104px and Marathon
   Printing's credit hides — but the row stacks at 919px with six items,
   because just above 900 the brand block is still at its full width with the
   credit under it. Then Home came back and the label shortened to "Find a
   Race" (Jay, 9 Sep 2026), which is seven items at 427px and needs 952px.
   1000 clears that by 48px and still sits below 1024, the narrowest common
   laptop, so every real laptop keeps the inline row. Below 600 core's own
   overlayMenu:"mobile" takes over, so this block covers 600–1000.

   **Measure this with the media query forced OFF.** Bisecting for the
   threshold while the query is active finds the bottom of the search range
   and nothing else, because below the breakpoint the nav is display:none and
   the header is never tall — which produced a confident "fits from 981px",
   i.e. exactly the lower bound that had been passed in.

   Either way the inline row comes back to every laptop and to tablets in
   landscape, which is what the 1080 and 1260 rules had been taking away.

   Re-measure the HEADER HEIGHT, not the item widths, if another label lands:
   the items have never been what wrapped — it is the header's own flex row
   that gives way. */
/* The hamburger is core's 24x24 SVG with padding 0: exactly the AA minimum
   and nothing to spare, beside an 82x40 clay button. A 44px hit area with no
   visual change. */
.ustrc-header .wp-block-navigation__responsive-container-open {
	min-inline-size: 44px;
	min-block-size: 44px;
	align-items: center;
	justify-content: center;
}

/* Register inside the open menu. Core's overlay is fixed and full-viewport,
   so it covers the header's own Register: a phone user who opened the menu to
   register saw eight links and no button (a11y review, finding 10). The menu
   carries a ninth item, className ustrc-nav-register, styled as the button
   and hidden wherever the header button is visible. */
.wp-block-navigation .wp-block-navigation-item.ustrc-nav-register {
	display: none; /* core sets .wp-block-navigation .wp-block-navigation-item to flex; match its weight */
}

.wp-block-navigation .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item.ustrc-nav-register {
	display: block;
	margin-block-start: var(--wp--preset--spacing--30);
}

.wp-block-navigation__responsive-container.is-menu-open .ustrc-nav-register > .wp-block-navigation-item__content {
	display: inline-block;
	background: var(--wp--preset--color--clay);
	color: #fff !important;
	border-radius: 2px;
	padding: 0.6875rem 1.375rem;
}

@media (min-width: 600px) and (max-width: 1000px) {
	.ustrc-header .wp-block-navigation__responsive-container-open {
		display: flex !important;
	}
	.ustrc-header .wp-block-navigation__responsive-container:not(.is-menu-open) {
		display: none !important;
	}
	.ustrc-header .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-close {
		display: flex !important;
	}
}

/* ==========================================================================
   Full-bleed split bands: align the text to the page's content column
   ==========================================================================
   The Nashville band is a 50/50 split — copy left, photograph bleeding off the
   right edge — so it is laid out full width rather than through the constrained
   layout every other band uses. That left its headline starting at x=40 on a
   1440px screen while every other headline on the page starts at x=140: a
   100px step that reads as a mistake the moment you scroll past it (measured
   9 Sep 2026).

   The gutter is recomputed here rather than hard-coded: contentSize is 1160px
   (72.5rem), so a centred content column leaves (100% − 72.5rem) / 2 either
   side, with the band's own 2.5rem padding as the floor once the viewport is
   narrower than the content. The percentage resolves against the flex
   container, not the viewport, so a desktop scrollbar cannot push it out of
   step with the constrained bands the way 100vw would.

   !important because the column carries the padding as an inline style, which
   a stylesheet loses to at any specificity (CLAUDE.md, seventh instance). */
.ustrc-split-text {
	padding-inline-start: max(2.5rem, calc((100% - 72.5rem) / 2)) !important;
}

@media (max-width: 781px) {
	/* Stacked: the column is full width, so the centring term goes negative
	   and max() falls back to the band gutter on its own. Stated anyway so a
	   future change to contentSize cannot make the mobile padding wrong. */
	.ustrc-split-text {
		padding-inline-start: 2.5rem !important;
	}
}

/* A slot that should read as a plain colour band until its photograph lands.
   The "· photo to come" label is right for a portrait card, where the missing
   face is the whole point of the card; it is wrong for a band that already
   works as a flat colour field and only gets better with a photograph behind
   it. Marking those .is-ustrc-quiet keeps the label out of a published page
   while leaving the slot in the markup for tools/wp.py to fill. */
.ustrc-photo-slot.is-ustrc-quiet::after {
	content: none;
}

/* ==========================================================================
   Motion: scroll-driven, CSS only
   ==========================================================================
   Jay, 9 Sep 2026: "experiment with some kind of interactivity as we scroll."

   This is built on CSS scroll-driven animations (animation-timeline: view()),
   not JavaScript and not an IntersectionObserver. Three reasons, in the order
   that matters here:

   1. Nothing to hydrate. The theme ships no JavaScript of its own today, and
      a reveal-on-scroll script is the classic way to end up with a page whose
      content is invisible when the script fails, is deferred behind WordPress's
      own bundles, or is stripped by a caching plugin. Here the animation is
      the browser's, tied to the element's own position in the scrollport.
   2. It degrades to the page as it is now. Everything below sits inside
      @supports (animation-timeline: view()); a browser without support simply
      paints the finished state.
   3. It respects the reader. Everything also sits inside
      prefers-reduced-motion: no-preference, on top of the blanket reduce rule
      further up this file.

   The distances are deliberately small — a 1.5rem rise and a 4% scale. The
   reference sites Jay sent (Satisfy, norda, Cirque Series, Golden Trail World
   Series) all move photographs and type by a few pixels, not a few hundred;
   the effect should be noticed as quality, not as an animation. */
@media (prefers-reduced-motion: no-preference) {
	@supports (animation-timeline: view()) {

		@keyframes ustrc-rise {
			from {
				opacity: 0;
				transform: translate3d(0, 1.5rem, 0);
			}
			to {
				opacity: 1;
				transform: none;
			}
		}

		/* Section furniture and cards rise as they enter. Covers are excluded:
		   the hero and the inner-page headers are above the fold, and an
		   element already in view at load sits at the end of its range and
		   paints its finished state, but there is no reason to give the
		   browser the work. */
		main :is(
			.wp-block-group.alignfull > .is-style-ustrc-eyebrow,
			.wp-block-group.alignfull > h2,
			.wp-block-group.alignfull > .wp-block-group > h2,
			.wp-block-columns > .wp-block-column,
			.ustrc-logo-wall > .ustrc-logo-link,
			.ustrc-logo-wall > .ustrc-logo-cell,
			.ustrc-session
		) {
			animation: ustrc-rise linear both;
			animation-timeline: view();
			animation-range: entry 0% entry 60%;
		}

		/* Stagger a row by shifting where each column's range starts, which
		   ties the delay to scroll position rather than to a timer: scroll
		   slowly and the cards arrive one at a time; scroll fast and they
		   arrive together, which is what the eye expects. */
		main .wp-block-columns > .wp-block-column:nth-child(2) { animation-range: entry 6% entry 66%; }
		main .wp-block-columns > .wp-block-column:nth-child(3) { animation-range: entry 12% entry 72%; }
		main .wp-block-columns > .wp-block-column:nth-child(4) { animation-range: entry 18% entry 78%; }

		/* Photographs settle out of a slight scale as they come up the page.
		   The slot is already overflow: hidden, so nothing spills. */
		@keyframes ustrc-settle {
			from { transform: scale(1.06); }
			to   { transform: none; }
		}

		main .ustrc-photo-slot > img,
		main .ustrc-photo-slot > .wp-block-cover__image-background {
			animation: ustrc-settle linear both;
			animation-timeline: view();
			animation-range: entry 0% cover 35%;
		}

		/* Read progress, as a hairline of clay under the sticky header. Two
		   pixels, the CTA colour, and the only thing on the page that reports
		   where you are in it. */
		@supports (animation-timeline: scroll()) {
			@keyframes ustrc-progress {
				to { transform: scaleX(1); }
			}

			.ustrc-header::after {
				content: "";
				position: absolute;
				inset-block-end: -1px;
				inset-inline-start: 0;
				inline-size: 100%;
				block-size: 2px;
				background-color: var(--wp--preset--color--clay);
				transform: scaleX(0);
				transform-origin: 0 50%;
				animation: ustrc-progress linear both;
				animation-timeline: scroll(root block);
			}
		}
	}
}

/* ==========================================================================
   Motion: pointer
   ==========================================================================
   Hover state is not decoration on this site, it is the answer to "is this
   thing a link?" — which the logo wall already answered and the cards did not.
   Kept to transforms and colour so nothing reflows under the pointer. */
@media (prefers-reduced-motion: no-preference) {

	/* A photograph inside a card leans in slightly when the card is hovered.
	   The transition lives on the image so the pointer-out is as slow as the
	   pointer-in; 600ms is long enough to read as a move rather than a jump. */
	.ustrc-photo-slot > img,
	.ustrc-photo-slot > .wp-block-cover__image-background {
		transition: transform 600ms cubic-bezier(0.2, 0.6, 0.2, 1);
	}

	.wp-block-column:hover > .ustrc-photo-slot > img,
	.is-style-ustrc-card:hover .ustrc-photo-slot > img,
	a:hover .ustrc-photo-slot > img {
		transform: scale(1.04);
	}

	/* The onward arrows. They are aria-hidden, so this moves a decoration and
	   never the link text — the underline stays where the reader put it. */
	main a > span[aria-hidden="true"] {
		display: inline-block;
		transition: transform 220ms cubic-bezier(0.2, 0.6, 0.2, 1);
	}

	main a:hover > span[aria-hidden="true"],
	main a:focus-visible > span[aria-hidden="true"] {
		transform: translateX(0.22em);
	}
}

/* ----------------------------------------------------------- hero height */

/* The block attribute says 88vh, which is the right idea and the wrong sum:
   the header band sits above the hero, so 88vh + a 119px header puts the next
   section's top edge 11px BELOW the fold at 1440x900 (measured 9 Sep 2026) —
   which loses the one thing a viewport-relative hero is for. A reader has to
   see that there is a page under the picture.

   Subtracting the header leaves the next band peeking by roughly 55px at every
   size. 11rem covers the 119px desktop band with air; 8rem covers the 67px
   band below 900px, where Marathon Printing's credit drops out of the header
   and the mark shrinks.

   !important because the cover block writes min-height as an inline style, and
   a stylesheet loses to that at any specificity (CLAUDE.md, eighth instance). */
.ustrc-hero-sticky {
	min-height: calc(100vh - 11rem) !important;
}

@media (max-width: 900px) {
	.ustrc-hero-sticky {
		min-height: calc(100vh - 8rem) !important;
	}
}

/* On a phone the min-height stops mattering: the headline wraps to four lines
   and the content is taller than the floor, so the hero came out at 94% of the
   screen with nothing of the next section showing — the shape that makes
   people think they have seen the whole page. The band padding is what to give
   back, not the type. */
@media (max-width: 600px) {
	.ustrc-hero-sticky {
		padding-block: var(--wp--preset--spacing--60) !important;
	}
}

/* ------------------------------------------- the presenting partner credit */

/* "PRESENTED BY Marathon Printing, Inc.", back in the header on 9 Sep 2026 at
   Jay's call, set under the mark rather than beside it. parts/header.html
   carries the reasoning; these are the mechanics.

   The credit stacks with the mark inside .ustrc-brand, so it costs height and
   not width — the nav and the Register button keep the room they had. At 132px
   the artwork is 24px tall, which takes the header band from about 96px to
   about 120px.

   Below 900px it goes. The mark itself drops to 104px there, which would set
   "Marathon Printing, Inc." at around 9px, and a sponsor credit nobody can
   read is worse for the sponsor than one that steps aside on a phone. */
.ustrc-brand {
	margin: 0;
	flex: 0 0 auto;
}

/* ==========================================================================
   The hero holds while the page slides over it
   ==========================================================================
   Jay, 9 Sep 2026: "major sections to slide up or down over others when you
   scroll over them." The pattern is a sticky section with the next one
   overlapping it — no library, no scroll hijacking, no JavaScript. The hero
   pins at the top of the scrollport and every band after it rides over on its
   own opaque ground, so the first scroll of the page reads as one surface
   moving rather than a column of slides.

   Two things make it work, and both are easy to undo by accident:

   1. Every band after the hero needs a background of its own. Two of the nine
      on the home page took their ground from the page rather than from a
      background class, and a transparent band over a sticky hero is a window
      onto it. .ustrc-band-opaque paints those two limestone, which is the
      colour they were already showing.
   2. The stacking order has to be explicit. The hero sits at z-index 0 and
      everything after it at 1; the header stays at 50 and is unaffected.

   It is inside prefers-reduced-motion because a section that holds still while
   the page moves over it is exactly the kind of parallax that the setting
   exists for. Turned off, the hero scrolls away like any other band. */
@media (prefers-reduced-motion: no-preference) {

	/* Only the home hero, by its own class. Two earlier versions of this
	   selector were wrong in opposite directions and both failed silently.
	   `main > …` matched nothing at all, because the bands are children of
	   .entry-content, core's wrapper inside <main>, and not of <main> itself.
	   Then `.entry-content > .wp-block-cover.alignfull:first-child` matched
	   too much: every inner page opens on a cover too, so the schedule and
	   scholarship page headers pinned as well, and those pages have bands
	   with no ground of their own — so their body copy scrolled across a
	   photograph and was unreadable (Jay, 9 Sep 2026, with screenshots).
	   A named class cannot make either mistake. */
	.ustrc-hero-sticky {
		position: sticky;
		inset-block-start: 0;
		z-index: 0;
	}

	/* Left unscoped on purpose. A sticky element is positioned, so it paints
	   above its non-positioned siblings no matter what order they are in; the
	   bands after it have to be positioned too or the hero covers them. On a
	   page with no sticky hero this changes nothing, which is cheaper than
	   making it conditional on :has() and finding out what happens in a
	   browser that has sticky and not :has(). */
	main > *:not(:first-child):not(.ustrc-band-overhang),
	.entry-content > *:not(:first-child):not(.ustrc-band-overhang) {
		position: relative;
		z-index: 1;
	}

	/* A band whose content straddles the seam into the next one has to sit
	   above that next one, or the overhang is painted over. This is what
	   happened the moment the bands got an explicit z-index: the keynote
	   cards hang 64px past their band, the schedule band below is opaque and
	   was at the same z-index, so document order won and the cards were cut
	   off mid-sentence (Jay, 9 Sep 2026, with a screenshot). The exclusion
	   above keeps this rule from being outranked rather than fighting it with
	   !important. */
	main > .ustrc-band-overhang,
	.entry-content > .ustrc-band-overhang {
		position: relative;
		z-index: 2;
	}

	/* The hero's own type fades as the next band comes up over it, so the
	   headline is gone before it would be sliced by the incoming edge. Tied to
	   the root scroll rather than to the element, because a sticky element's
	   own view() ranges stop meaning what they normally mean. */
	@supports (animation-timeline: scroll()) {
		@keyframes ustrc-hero-recede {
			to {
				opacity: 0;
				transform: translate3d(0, -2rem, 0);
			}
		}

		.ustrc-hero-sticky > .wp-block-cover__inner-container {
			animation: ustrc-hero-recede linear both;
			animation-timeline: scroll(root block);
			/* Gone by a third of a viewport, which is well before the incoming
			   band's edge reaches where the headline sits. At 58vh the type was
			   still faintly there when the band arrived and read as a paint bug
			   rather than a fade. */
			animation-range: 2vh 32vh;
		}
	}
}

/* A band that would otherwise show the page's ground through itself. Painted
   the colour it was already showing, so nothing changes except that it is now
   opaque — which is what lets it pass over the sticky hero. */
.ustrc-band-opaque {
	background-color: var(--wp--preset--color--limestone);
}

/* ==========================================================================
   Line breaking
   ==========================================================================
   Jay, 9 Sep 2026: "Fix the line breaks on the various headlines throughout."
   The examples were the giveaway: "The sport is national. The / conference is
   too." broke after the word "The", and "You put on the races. Come find out
   how / everyone else does it." broke after "how". Both strand a function word
   at the end of a line, which in 52px display type is the most visible kind of
   bad rag there is.

   `text-wrap: balance` rather than a hand-placed <br> or a non-breaking space:
   it evens the line lengths and redoes it at every viewport, so one headline
   is set correctly at 1440 and at 390 without two hand-tuned break points. On
   the first example it breaks at the sentence instead, which is where a reader
   would break it.

   **This existed and was scoped to almost nothing.** There was already a
   `text-wrap: balance` in this file, on
   `.ustrc-cols-aligned > .wp-block-column > h3` -- the four-up column headings
   and nothing else. Every display headline on the site was wrapping greedily.
   That narrow rule stays because it also sets align-self; it is now redundant
   for the wrapping.

   Browsers cap balancing at a few lines (Chromium stops at six), which is the
   range headlines live in and is why this covers headings plus ledes rather
   than body copy. `pretty` on the paragraphs does not balance, it just refuses
   to leave one word alone on the last line -- the right job for a measure,
   where balancing would fight it.

   No JavaScript, and nothing to go stale: both properties fall back to normal
   wrapping where unsupported, which is the rag the site has today. */
h1,
h2,
h3,
.ustrc-photo-band-statement .ustrc-band-copy h2 {
	text-wrap: balance;
}

.entry-content p,
.ustrc-photo-band-statement .ustrc-band-copy p {
	text-wrap: pretty;
}

/* ---- Statement band: give the headline room -------------------------------
   Jay, 9 Sep 2026, on the About band: "Spread the headline out a little."
   Measured before changing anything: the copy block was 748px, 55% of the
   1360px content column, carrying 52px display type on 55px of leading, which
   is 1.06. At that measure the mission runs to four tight lines and reads as a
   brick rather than a statement.

   Two changes, both modest. 76% takes the measure to about 1034px, which is
   still short of the full column so the headline does not run edge to edge and
   fight the photograph -- the reason .ustrc-band-copy is capped at all. And
   1.16 leading opens the lines without making the block look loose at display
   weight.

   The mobile override has to be repeated here rather than inherited: the
   existing max-width:100% rule for phones is .ustrc-photo-band .ustrc-band-copy
   at (0,2,0), and the rule above it here is (0,3,0), so without this the
   headline would sit at 76% on a 390px screen. */
.wp-block-cover.ustrc-photo-band-statement .ustrc-band-copy {
	max-width: 76%;
}

.ustrc-photo-band-statement .ustrc-band-copy h2 {
	line-height: 1.16;
}

@media (max-width: 900px) {
	.wp-block-cover.ustrc-photo-band-statement .ustrc-band-copy {
		max-width: 100%;
	}
}

/* Speaker cards carry each speaker's own bio verbatim, inside a core Details
   block (17 Sep 2026, Carrie: include the full bios the attendees supplied;
   pulled from the live /speakers/ page, where they are published in full).
   A collapsed disclosure rather than the bio in the card body, because the
   bios run 242 to 1,078 characters and dropping them straight into a card
   grid destroys the uniform height the grid is built on. Every word is still
   in the page source, so it indexes and reads the same.

   Core ships the block unstyled, which lands a 19px Newsreader line and the
   browser's own disclosure triangle inside a card where everything else is
   16px muted. This matches it to the card. No JavaScript: details/summary is
   native and the +/- is CSS on [open], same rule as every other motion on
   this site. */
.is-style-ustrc-card details.wp-block-details > summary {
	list-style: none;
	cursor: pointer;
	display: flex;
	align-items: baseline;
	gap: 0.45rem;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
	color: var(--wp--preset--color--charcoal);
}

.is-style-ustrc-card details.wp-block-details > summary::-webkit-details-marker {
	display: none;
}

/* The mark is a real glyph rather than a rotating triangle: at 16px a rotated
   caret reads as a rendering artefact next to Archivo's flat terminals. */
.is-style-ustrc-card details.wp-block-details > summary::before {
	content: "+";
	color: var(--wp--preset--color--clay);
	font-weight: 700;
	line-height: 1;
	width: 0.7em;
	flex: none;
}

.is-style-ustrc-card details.wp-block-details[open] > summary::before {
	content: "\2212"; /* minus sign, not a hyphen: it matches the plus's width */
}

.is-style-ustrc-card details.wp-block-details > summary:hover {
	color: var(--wp--preset--color--clay);
}

.is-style-ustrc-card details.wp-block-details > summary:focus-visible {
	outline: 2px solid var(--wp--preset--color--clay);
	outline-offset: 3px;
}
