/*
Theme Name: CIFE
Author: CIFE
Description: Custom block theme for the Council for Independent Education.
Requires at least: 6.7
Tested up to: 6.9
Requires PHP: 7.2
Version: 0.1.25
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: cife-theme
Tags: full-site-editing, block-patterns, block-styles, wide-blocks, accessibility-ready, style-variations
*/

/* ---------------------------------------------------------------------------
   Layout rhythm
   WordPress inserts margin-block-start: var(--wp--style--block-gap) between the
   template's top-level sections (header part, main, footer part) — a gap that
   exists even when no markup asks for it. Zero it so sections butt edge-to-edge
   and own their vertical rhythm via padding.
--------------------------------------------------------------------------- */
.wp-site-blocks > * + * {
	margin-block-start: 0;
}

/* Inside a page, WordPress applies that same flow gap between every top-level
   block. On paper ground it reads as rhythm, but between two blocks that both
   carry a background - the mist enquiry panel meeting the ink closing CTA, say
   - it reads as a pale seam that looks like a mistake rather than spacing. Those
   bands butt edge-to-edge; sections separated by transparent ground keep the
   gap. */
.wp-block-post-content > .wp-block-group.has-background + .wp-block-group.has-background {
	margin-block-start: 0;
}

/* With that gap gone, main carries its own vertical padding so templates this
   theme does not author (WooCommerce shop, product, cart…) still clear the
   header and footer. A descendant selector, not a child one: WooCommerce wraps
   the single-product main in an extra group. Templates built from full-bleed
   sections opt out with is-flush and let the sections own the rhythm. */
.wp-site-blocks main {
	padding-block: var(--wp--preset--spacing--60) var(--wp--preset--spacing--70);
}
.wp-site-blocks main.is-flush {
	padding-block: 0;
}

/* Section bands marked alignfull escape the constrained content column and
   span the viewport, while their own constrained layout keeps the inner
   content aligned to the page container. */
.wp-site-blocks main .wp-block-group.alignfull {
	width: 100vw;
	max-width: 100vw;
	margin-left: calc(50% - 50vw);
	margin-right: calc(50% - 50vw);
}
html,
body {
	overflow-x: clip;
}

/* ---------------------------------------------------------------------------
   Editorial devices
--------------------------------------------------------------------------- */
.is-style-kicker {
	margin-block-end: 0.75rem;
	color: var(--wp--preset--color--primary);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
}
.wp-block-image.is-style-hero {
	margin-block-end: 0;
}
.wp-block-image.is-style-hero img {
	width: 100%;
	aspect-ratio: 3 / 2;
	object-fit: cover;
	border-radius: var(--wp--preset--border-radius--medium);
	box-shadow: 0 2px 8px rgba(22, 35, 63, 0.06), 0 30px 60px -30px rgba(22, 35, 63, 0.35);
}

/* ---------------------------------------------------------------------------
   Typography detail
--------------------------------------------------------------------------- */
h1,
h2,
h3 {
	text-wrap: balance;
}
p,
li {
	text-wrap: pretty;
}
h1 + p,
h2 + p,
h3 + p {
	margin-block-start: 0.75rem;
}

/* Content links read as links without shouting. Button links opt out. */
.wp-block-group :where(p, li) a:not(.wp-element-button) {
	text-underline-offset: 0.18em;
	text-decoration-thickness: from-font;
}

/* On ink bands the default (primary) link colour is illegible; use the off-white. */
:where(.has-ink-background-color) :where(p, li) a:not(.wp-element-button) {
	color: var(--wp--preset--color--on-ink);
}

/* Light-on-dark type reads optically thinner (and blooms more) than dark-on-
   light, so thicken the strokes on ink bands: headings 600 -> 700, body 400 ->
   450. We also deliberately do NOT set -webkit-font-smoothing: antialiased on
   ink bands, which would thin the text further. */
.wp-block-group.has-ink-background-color :where(h1, h2, h3, h4, h5, h6) {
	font-weight: 700;
}
.wp-block-group.has-ink-background-color :where(p, li, blockquote):not(.wp-block-site-title) {
	font-weight: 450;
}

/* Header brand wordmark: the logo is the client's SVG, which carries a viewBox
   but no intrinsic width, and WordPress's own site-logo sizing cannot help
   because wp_attachment_is_image() excludes svg, so wp_get_attachment_image_src()
   returns false and the block emits the image with no dimensions at all. A bare
   inline SVG therefore collapses to 0 inside the header's flex row. Size it here.

   The size preserves the brand lockup's own proportions, measured by pixel
   analysis of the brand's lockup files (cife-logo .png, cife-logo-large .png):
   the tagline is 1.44 x the wordmark's width, i.e. wordmark = 0.694 x tagline
   width. The header tagline renders 147.3px wide at 15.96px, so the wordmark is
   102px wide and, from the artwork's own 1733:1082 ratio, 64px tall.
   If the tagline's size ever changes, scale this with it or the ratio breaks. */
.wp-block-site-logo img.custom-logo {
	width: 102px;
	height: auto;
}

@media (max-width: 600px) {
	/* Mobile tagline renders 138.5px wide, so 0.694 x that = 96px. */
	.wp-block-site-logo img.custom-logo {
		width: 96px;
	}
}

/* Dark-band depth: a faint plum gradient (instead of a flat fill). Lightened so
   the base plum (#3f3043) is the darkest stop; off-white stays AAA (8.71:1 on
   the lightest stop). The brand-orange rule is not part of this any more: it
   lives on the header's bottom edge (Header and navigation section), and ink
   bands carry no edge of their own. */
.wp-block-group.has-ink-background-color {
	background-image: linear-gradient(160deg, #4E3D56 0%, #46364E 55%, #3f3043 100%);
}

/* Breadcrumb trail: inline ordered list, separators drawn in CSS. */
nav[aria-label="Breadcrumb"] ol {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
}
nav[aria-label="Breadcrumb"] li + li::before {
	content: "·";
	margin-inline: 0.5em;
}

/* Reading measure: long-form templates (article, news, post, profile,
   page-hero, page-course, page) declare a 43rem content column (~90 characters
   at the body size, counting spaces) in their block layout. Grids, cards, media
   and the top-level / hub / landing pages keep the 1080px site content width.
   Change the measure in those templates' group/post-content layout. Use rem,
   not em: WordPress applies contentSize as a max-width on each child, so em
   would resolve against each heading's own (large) font-size. */

/* Article and college bodies step down one scale for their own headings. A
   section heading inside a reading body is not a band heading: one article can
   carry a dozen of them, and a college profile six, down a single column, where
   the site h2 (2.25rem at the top of its clamp) reads as a page of titles
   rather than as prose.
   So a reading body's h2 takes the X-Large preset and its h3 takes Large - the
   size the accordion headings carry, and the size the original site's own
   subsection headings read at. Their levels are untouched: the article outline
   matches the original's h2/h3 pattern exactly, and the college profiles are
   flat h2/h3-free because that is the migrated original's own structure.
   Band headings outside the body (the CTA at the foot of every article,
   designed page bands) keep the site h2: this is scoped to the reading bodies,
   by body class, the way the college profile measures are scoped below. Both
   sizes are preset tokens, so they move with the scale rather than holding a
   second copy of it. */
.single-article .wp-block-post-content h2,
.single-college .wp-block-post-content h2 {
	font-size: var(--wp--preset--font-size--x-large);
}
.single-article .wp-block-post-content h3,
.single-college .wp-block-post-content h3 {
	font-size: var(--wp--preset--font-size--large);
}

/* ---------------------------------------------------------------------------
   Buttons
--------------------------------------------------------------------------- */
.wp-element-button,
.wp-block-button__link {
	transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.wp-block-button.is-style-outline .wp-block-button__link:hover {
	background: var(--wp--preset--color--primary);
	border-color: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--paper);
}
/* The chooser's course filter (mu-plugins/cife-chooser.php): the chip that is the active filter takes
the solid button. That is not a new treatment — it is the one an outline chip already has on
hover — so the filtered state reads in the page's own vocabulary and the chips stay one set of
nine. Keyed to `aria-current`, not a bare class: the state is announced as well as shown, and
the only chips that ever set it to `true` are these. */
.wp-block-button.is-style-outline .wp-block-button__link[aria-current="true"],
.wp-block-button.is-style-outline .wp-block-button__link[aria-current="true"]:hover {
	background: var(--wp--preset--color--primary);
	border-color: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--paper);
}

/* ---------------------------------------------------------------------------
   Cards (college cards in Query Loops)
--------------------------------------------------------------------------- */
.wp-block-post-template.is-layout-grid {
	--card-gap: clamp(1rem, 2.4vw, 1.75rem);
	gap: var(--card-gap);
}
/* ---------------------------------------------------------------------------
   College cards in the course and location archives
   The chooser page's sizing - the shared college-card pattern, spacing|50 of
   padding, the theme's own grid gap, and now the same content column - wearing
   the panel language from the home and courses grids: the surface tint instead
   of card chrome, no radius, no shadow, no lift, and the brand orange drawn as
   rules in the gutters. The chooser page keeps the card treatment: there a
   college is a door to open, here it is a row in a comparison.
--------------------------------------------------------------------------- */
body.tax-course {
	--panel-rule: 2px;
	--panel-rule-color: var(--wp--preset--color--accent-light);
}
body.tax-course .wp-block-post-template.is-layout-grid > .wp-block-post {
	position: relative;
}
/* The rules are centred in the gutters, and the offset is (gutter + rule) / -2:
   the rule's own width has to be added, not subtracted. (gutter - rule) / -2
   looks right and is not, because it leaves the rule a rule-width right of
   centre. Colour and weight come from the same two dials the panels use.
   Every cell draws both rules and the grid's own `overflow: clip` removes exactly
   the two that fall outside it - the first column's vertical rule and the first
   row's horizontal rule. Nothing depends on a child's index, which is what the
   previous `nth-child` arithmetic did: the editor's Query Loop renders an
   invisible 0х0 duplicate of the first post, so every index shifted by one and a
   row rule landed above the wrong card (D119, D122). Core owns the column count
   - it collapses these grids at 600px - so nothing here is breakpoint-dependent. */
body.tax-course .wp-block-post-template.is-layout-grid {
	overflow: clip;
}
body.tax-course .wp-block-post-template.is-layout-grid > .wp-block-post::before,
body.tax-course .wp-block-post-template.is-layout-grid > .wp-block-post::after {
	--rule-bleed: calc((var(--card-gap) + var(--panel-rule)) / -2);
	content: "";
	display: block;
	position: absolute;
	background-color: var(--panel-rule-color);
}
/* The vertical rule: every cell, centred in the gutter to its left. The first
   column's is clipped away, and at one column every one of them is. */
body.tax-course .wp-block-post-template.is-layout-grid > .wp-block-post::before {
	top: 0;
	bottom: 0;
	left: var(--rule-bleed);
	width: var(--panel-rule);
}
/* The horizontal rule: every cell, centred in the gutter above it. Its width is
   100vw on purpose - the clip trims it to the grid's right edge, so it never
   breaks at a gutter and never needs to know how many columns there are. The
   first row's is clipped away; every later one is the divider between rows. */
body.tax-course .wp-block-post-template.is-layout-grid > .wp-block-post::after {
	top: var(--rule-bleed);
	height: var(--panel-rule);
	left: 0;
	width: 100vw;
}

/* Course route panels: the nine route columns on the courses page and the six
   on the homepage band. The padding is the point: it is what stops the text of
   one column running into the next, which is the failure the panels are there
   to fix.

   The fill is decided by the band the panel sits on, because one fill cannot
   read as a panel in both places. On the white courses page the panel has to be
   a tint - paper there would vanish and leave only the rules. On the tinted
   homepage band it has to be the lighter lift: surface is barely a shade off
   mist, so the cards dissolved into the band and read as loose text (D163).
   Two rules, one token each, and no per-instance colour on any block. */
.wp-block-column.is-style-panel {
	position: relative;
	background-color: var(--wp--preset--color--surface);
	padding: var(--wp--preset--spacing--50);
}

/* A panel on a tinted band is paper: the band supplies the tint, so the panel
   only has to be lighter than it. Keyed on the ancestor band rather than on the
   home page, so a panel placed on any mist band behaves the same way, and the
   courses page - whose panels sit on white - is untouched. */
.has-mist-background-color .wp-block-column.is-style-panel {
	background-color: var(--wp--preset--color--paper);
}

/* Panels on white (the courses page) flow naturally — spacing is the editor
   block gap. Panels on a mist band (the homepage course cards) pin their
   trailing "…course details" link to the bottom edge instead: the panel is a
   flex column and the description paragraph takes the slack, so a row of cards
   whose descriptions wrap differently still lands every footer on one bottom
   line. Scoped to the mist band so the courses-page panels — whose last block
   is a list, not a link — keep their natural flow. */
.has-mist-background-color .wp-block-column.is-style-panel {
	display: flex;
	flex-direction: column;
}

.has-mist-background-color .wp-block-column.is-style-panel > p:not(:last-child) {
	flex: 1 1 auto;
}

/* The grid rules have two dials: weight and colour. 2px matches the trust
   strip's rule weight, 1px recedes to a hairline. `accent` is the brand orange
   the trust strip and the pillar columns use; `accent-light` is the same family
   a step softer, which lets the grid sit behind the content rather than frame
   it. The centred offsets below are derived from the weight, so changing either
   value keeps every rule centred in its gutter. */
.wp-block-columns:has(> .wp-block-column.is-style-panel) {
	--panel-rule: 2px;
	--panel-rule-color: var(--wp--preset--color--accent-light);
}

/* The structure is drawn in the brand accent, matching the hairlines the trust
   strip and the pillar columns already use, rather than with a border on each
   panel. A border per panel can only draw the outside of a cell, and two
   adjacent borders in one gutter read as nothing at all; a rule in the gutter
   belongs to the grid instead of to one of the two cells it separates. */
.wp-block-columns:has(> .wp-block-column.is-style-panel) + .wp-block-columns:has(> .wp-block-column.is-style-panel) {
	position: relative;
	margin-block-start: var(--wp--preset--spacing--50);
}

/* Horizontal rule between rows, centred in the row gutter. */
.wp-block-columns:has(> .wp-block-column.is-style-panel) + .wp-block-columns:has(> .wp-block-column.is-style-panel)::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	top: calc((var(--wp--preset--spacing--50) + var(--panel-rule)) / -2);
	height: var(--panel-rule);
	background-color: var(--panel-rule-color);
}

/* Vertical rule between columns, centred in the column gutter. Only above the
   width where core stops stacking columns, since stacked columns need the
   horizontal form instead. */
@media (min-width: 782px) {
	.wp-block-column.is-style-panel + .wp-block-column.is-style-panel::before {
		content: "";
		position: absolute;
		top: 0;
		bottom: 0;
		left: calc((var(--wp--preset--spacing--50) + var(--panel-rule)) / -2);
		width: var(--panel-rule);
		background-color: var(--panel-rule-color);
	}
}

/* Below that width the same rule turns horizontal, so stacked cells are still
   separated by the grid rather than by tint alone. */
@media (max-width: 781px) {
	.wp-block-column.is-style-panel + .wp-block-column.is-style-panel::before {
		content: "";
		position: absolute;
		left: 0;
		right: 0;
		top: calc((var(--wp--preset--spacing--50) + var(--panel-rule)) / -2);
		height: var(--panel-rule);
		background-color: var(--panel-rule-color);
	}
}

/* ---------------------------------------------------------------------------
   College profiles
--------------------------------------------------------------------------- */
/* The breadcrumb trail is the profile's first strip; main runs flush so the
   full-bleed hero can begin immediately below it, which leaves the trail to
   own its own breathing room. */
.single-college .wp-site-blocks main.is-flush > .wp-block-group:first-child {
	padding-block: var(--wp--preset--spacing--50) var(--wp--preset--spacing--40);
}

/* That strip and the hero meet flush: the flow block-gap would otherwise open
   a pale stripe between the trail and the top of the band. The trail is
   transparent, so this cannot be written as a background-band rule; the hero
   carries its own `profile-hero` class instead (D88, superseding the
   ink-scoped selector the hero needed while it was plum). */
.single-college main.is-flush > .wp-block-group.profile-hero {
	margin-block-start: 0;
}

/* Above the college name the location line reads as a tracked label rather
   than a stray link (post-terms has no colour support of its own). Profile
   heroes have been mist since D88, which is the only surface this now applies
   to. */
.has-mist-background-color .wp-block-post-terms {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
}
/* Profile heroes have been mist since D88, so the label keeps its treatment in
   slate there (7.8:1 on mist) instead of light-on-plum. */
.has-mist-background-color .wp-block-post-terms {
	color: var(--wp--preset--color--slate);
}
.has-mist-background-color .wp-block-post-terms a {
	color: inherit;
	text-decoration: none;
}
.has-mist-background-color .wp-block-post-terms a:hover {
	text-decoration: underline;
}

/* Course chips (the D8 terms on a profile) — a native post-terms style. */
.is-style-chips a {
	display: inline-block;
	margin: 0 0.4rem 0.4rem 0;
	padding: 0.35rem 0.85rem;
	border: 1px solid var(--wp--preset--color--line);
	border-radius: 999px;
	background: var(--wp--preset--color--mist);
	color: var(--wp--preset--color--slate);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	text-decoration: none;
	transition: border-color 0.18s ease, background-color 0.18s ease, color 0.18s ease;
}
.is-style-chips a:hover {
	border-color: var(--wp--preset--color--primary);
	background: var(--wp--preset--color--paper);
	color: var(--wp--preset--color--primary);
}

/* Migrated profile bodies put the facts (principal, address, map, social) in a
   narrow sidebar column beside the narrative. The template widens the body to
   60rem so the narrative column lands near the site reading measure; a profile
   whose body is flat prose instead (the house format) keeps the long-form
   measure, because its prose is a direct child of the body rather than nested
   inside the columns. On phones the columns stack, and the narrative should
   come first — which is what the migrated "mlrg-reverse-mobile" class was for. */
.single-college .wp-block-post-content > :where(p, h2, h3, h4, ul, ol, blockquote) {
	max-width: 43rem;
}
.single-college .wp-block-post-content .mlrg-reverse-mobile {
	align-items: flex-start;
}
@media (max-width: 781px) {
	.single-college .wp-block-post-content .mlrg-reverse-mobile {
		flex-direction: column-reverse;
	}
}

/* ---------------------------------------------------------------------------
   Header and navigation
--------------------------------------------------------------------------- */
/* The orange rule under the menu is NOT here on purpose. It is a border block
   attribute on the header group in parts/header.html, which is what makes it
   appear in the editor's Border panel and stay editable there. A stylesheet
   rule would render in the canvas but be invisible to every control - and would
   silently fight the attribute if an editor changed it.
   Current value in the file: border.bottom = 4px accent (D87; it was 2px between
   D82 and D87). The footer's mirror rule - border.top = 4px accent on the footer
   group in parts/footer.html (D85/D87) - works the same way. Keep both values in
   those files: editing either one in the Site Editor saves a custom template
   part that then shadows the file (D83/D84).
   The trust strip in patterns/trust-strip.php is a separate device and keeps its
   own inline 2px accent rules top and bottom. */
/* The tagline carries the brand orange as part of the logo lockup (chosen
   treatment) but in the LIGHT value: accent #DD7E12 is only 3.32:1 against the
   top of the ink gradient, under AA for small text, while accent-light #EFA24C
   is 4.68:1 there and 5.80:1 at the foot of the band. The old 0.7 muting is what
   originally muddied it against the ink. */
header.wp-block-group .wp-block-site-tagline {
	opacity: 1;
}

/* The header's right-hand stack: the site search with the phone number beneath it.
   The stacking is the base rule and not a breakpoint, because the header asks for the
   phone *below* the field at every width - left as a wrapping row the two would sit
   side by side on a wide screen. The row is flex-wrap: wrap, so it wraps rather than
   overflowing if the two do not fit. The 19rem field this replaced sat 304px wide at
   the 766px width the header was annotated at, where the row's content box is
   689.75px and 385.75px is left for the logo lockup; the 722-802px band has never been
   rasterised by any instrument here, so that was arithmetic rather than a screenshot. */
.is-style-header-cta {
	flex-direction: column;
	align-items: flex-end;
}

/* The header field is deliberately not the site's form field. It is a small control in
   a dark band: one hairline border in the brand accent, the placeholder in the same
   accent, and a magnifier inside the field instead of a button beside it, so the whole
   control is one 15rem box. Everything here is scoped to the header stack - the site's
   own search field on the 404 page keeps the shared .wp-block-search__input rules. */
.is-style-header-cta .wp-block-search {
	width: 15rem;
	max-width: 100%;
}
.is-style-header-cta .wp-block-search__inside-wrapper {
	border: 1px solid var(--wp--preset--color--accent);
	border-radius: var(--wp--preset--border-radius--small);
	background: var(--wp--preset--color--paper);
	padding: 0;
}
.is-style-header-cta .wp-block-search__input {
	border: 0;
	background: transparent;
	/* 1rem and not the small preset: the preset only reaches 1rem on the widest
	   screens and bottoms out at 0.9375rem, and iOS Safari zooms any focused field it
	   measures under 16px. */
	font-size: 1rem;
	padding: 0.4rem 0.7rem;
}
/* The placeholder is `muted`, not the accent. Measured with scripts/wcag-contrast.php:
   accent on paper is 2.97:1, under the 4.5:1 that AA requires at this size - the accent
   is a brand colour for fills and rules, not for small type on white, which is the same
   failure the audit already records for accent as small text and for the tagline on the
   ink band. muted on paper is 5.66:1 and passes, and unlike a darkened orange it does
   not read as text someone has already typed. So the accent stays where it carries no
   meaning - the hairline border. The field's *boundary* is carried by its white fill
   against the ink band instead, and that pair measures 12.24:1. */
.is-style-header-cta .wp-block-search__input::placeholder {
	color: var(--wp--preset--color--muted);
	opacity: 1;
}
/* The field's own border is gone, so focus is shown on the box that is left. Without
   this the field would have no visible focus state at all. */
.is-style-header-cta .wp-block-search__inside-wrapper:focus-within {
	border-color: var(--wp--preset--color--primary);
	box-shadow: 0 0 0 1px var(--wp--preset--color--primary);
}
.is-style-header-cta .wp-block-search__input:focus-visible {
	outline: none;
}
/* The magnifier. The theme's button element carries a filled primary background and
   0.85rem/1.5rem of padding - most of what made the field bulky - so it is flattened
   to the icon itself, coloured like secondary text and taking the accent on hover. */
.is-style-header-cta .wp-block-search__button.has-icon {
	background: transparent;
	color: var(--wp--preset--color--muted);
	padding: 0 0.55rem;
	min-width: 0;
	width: auto;
}
/* Hover/focus is `primary`, not the accent. Measured: accent on paper is 2.97:1, under
   the 3:1 that WCAG 1.4.11 asks of non-text information the visitor needs, while primary
   is 7.10:1 and is already what this theme uses for anything interactive. Focus on the
   icon also lights the wrapper's own ring through :focus-within, so the field's focus
   indicator never depends on this colour change alone. */
.is-style-header-cta .wp-block-search__button.has-icon:hover,
.is-style-header-cta .wp-block-search__button.has-icon:focus-visible {
	background: transparent;
	color: var(--wp--preset--color--primary);
}
/* Core floors this icon at min-width/min-height 24px, so a smaller width here renders
   at 24 anyway - the declaration looks like it works and does not. Both floors are
   cleared, which is the only reason the size below is the size that renders. */
.is-style-header-cta .wp-block-search__button.has-icon svg {
	width: 1.25rem;
	height: 1.25rem;
	min-width: 0;
	min-height: 0;
	fill: currentColor;
}

/* Below 640px the stack takes the whole header row, so the field stretches with it and
   the phone line sits flush under it instead of squeezing beside the branding. */
@media (max-width: 640px) {
	.is-style-header-cta {
		align-items: stretch;
	}
	.is-style-header-cta .wp-block-search {
		width: 100%;
	}
}

/* The primary nav takes its own row in the header and needs 582.125px of item text
   plus five gaps. That gap is spacing|50 - clamp(1.5rem, 1.25rem + 1vw, 2rem) - so it
   narrows with the viewport, and the row stops fitting once the viewport drops below
   about 802px. Core's own responsive nav only takes over below 600px and it hardcodes
   that breakpoint (no attribute, no CSS variable to move it), so from there to ~802px
   the row simply cannot fit: being flex-wrap: wrap it wraps to two lines rather than
   overflowing. That is what it did at 768px, an iPad-portrait width.

   Tightening the gap only where the row can still fit on one line buys the single line
   back without touching the roomy spacing the nav keeps at every larger width. Both
   bounds are measured, not chosen:
     - 802px  above this the row fits at the full spacing|50 gap, so no override is
              needed: 582.125 + 5 x (1.25rem + 1vw) <= viewport - 80.
     - 722px  below this even a 12px gap cannot fit 582.125px of item text inside the
              header's 40px side padding, so wrapping is unavoidable there and the
              override would only make the two rows look cramped.
   spacing|30 (0.75rem = 12px) rather than spacing|40 (20px): at 768px a 20px gap leaves
   only 6px of slack, which a persistent (non-overlay) scrollbar eats, while 12px leaves
   46px. Recompute both bounds if the nav labels or the nav font-size ever change.

   The selector outranks core's .wp-container-core-navigation-is-layout-* (one class,
   specificity 0,1,0) at 0,2,1, so this wins on specificity rather than on stylesheet
   order - do not shorten it to a single class or it silently stops applying. */
@media (min-width: 722px) and (max-width: 802px) {
	.wp-block-navigation ul.wp-block-navigation__container {
		gap: var(--wp--preset--spacing--30);
	}
}

/* ---------------------------------------------------------------------------
   Imagery (theme.json border styles don't emit for media blocks)
--------------------------------------------------------------------------- */
.wp-block-image img,
.wp-block-post-featured-image img {
	border-radius: var(--wp--preset--border-radius--medium);
}

/* The college profile featured image is a banner, not a full-bleed hero.
   In templates/single-college.html it sits outside any constrained group, so
   unconstrained flow stretched it to the whole viewport - 1040x695 at desktop -
   while the copy beneath it sits in a 60rem column. Match that column and cap
   the height, letting object-fit crop rather than squash the photograph.
   The cap also stops the smaller sources (Bales at 1024x484) being stretched
   well past their native size, which showed as softness at the old width. */
.single-college .wp-block-post-featured-image {
	box-sizing: border-box;
	margin-inline: auto;
	max-width: 60rem;
	/* Match the root padding rather than a spacing preset: the copy column below
	   is inset by styles.spacing.padding in theme.json, so the image lines up
	   with the text and stays lined up if that value is ever retuned. */
	padding-inline: var(--wp--style--root--padding-left) var(--wp--style--root--padding-right);
}

.single-college .wp-block-post-featured-image img {
	width: 100%;
	height: clamp(12rem, 30vw, 24rem);
	object-fit: cover;
}

/* ---------------------------------------------------------------------------
   Stat band (social proof)
--------------------------------------------------------------------------- */
/* Each figure is a cell: an accent hairline, the value set in the heading serif,
   then the label in body copy. Cells stack into a list on phones. */
.wp-block-group.is-style-stat-cell {
	border-top: 1px solid var(--wp--preset--color--accent);
	padding-top: var(--wp--preset--spacing--40);
}

.wp-block-paragraph.is-style-stat-value {
	color: var(--wp--preset--color--ink);
	font-family: var(--wp--preset--font-family--heading);
	font-size: var(--wp--preset--font-size--xx-large);
	font-weight: 600;
	line-height: 1;
	letter-spacing: -0.01em;
}

.wp-block-paragraph.is-style-stat-label {
	color: var(--wp--preset--color--slate);
	font-size: var(--wp--preset--font-size--small);
	line-height: 1.45;
}

/* The flow block-gap would float the label well below its figure; the pair
   reads as one unit at a tighter step. */
.is-style-stat-value + .is-style-stat-label {
	margin-block-start: 0.4rem;
}

/* ---------------------------------------------------------------------------
   Jump nav (in-page topic pills)

   Hub pages put their articles behind a lot of vertical scroll. A slim strip of
   pill links under the hero lets a reader go straight to the topic they came
   for. It is a functional bar, not a section: no band of its own, no hairline -
   the pills delimit themselves and the paper ground is shared with the band
   below, so the page does not gain another visual seam.
--------------------------------------------------------------------------- */
html {
	scroll-behavior: smooth;
}

/* Pills read as jump targets: a quiet outline at rest, ink fill on hover, so
   they are not mistaken for the accent CTAs on the hero just above.

   The outline is what makes a pill read as a button at all, so it has to be an
   EDGE, not a divider. `line` is the hairline the page draws between things:
   against mist it measures 1.15:1 and against paper 1.31:1 - it is there to be
   barely seen. A control's boundary has to be perceived. `muted` is what the
   form fields already use for theirs (4.98:1 on mist, 5.66:1 on paper), and 3:1
   is the target the focus ring below already argues from. */
.wp-block-button.is-style-pill-link .wp-block-button__link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 2.75rem;
	border: 1px solid var(--wp--preset--color--muted);
	border-radius: 999px;
	background-color: transparent;
	color: var(--wp--preset--color--ink);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	line-height: 1.4;
	padding: 0.5em 1.15em;
	text-decoration: none;
}
.wp-block-button.is-style-pill-link .wp-block-button__link:hover,
.wp-block-button.is-style-pill-link .wp-block-button__link:focus {
	border-color: var(--wp--preset--color--ink);
	background-color: var(--wp--preset--color--ink);
	color: var(--wp--preset--color--paper);
}
/* Keyboard users need the ring back; the ink fill alone is not a focus cue.
   Primary rather than accent: the ring sits outside the pill against the page
   background, where accent measures 2.97:1 - under the 3:1 needed to perceive
   the ring at all. */
.wp-block-button.is-style-pill-link .wp-block-button__link:focus-visible {
	outline: 2px solid var(--wp--preset--color--primary);
	outline-offset: 2px;
}
.is-style-jump-nav-label {
	align-self: center;
	margin-block-start: 0;
	margin-block-end: 0;
	color: var(--wp--preset--color--slate);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

/* D140: the subject directory's four group labels are h3s and keep the label treatment, but a
   heading also takes the serif family and the 1.15 line-height that theme.json sets on the
   heading element — neither of which the label rule above declares, so both would arrive from
   the wrong place. Pinning them to the body's own values keeps the label looking exactly as it
   did as a paragraph. Scoped to the heading so the paragraph instances are untouched. */
.wp-block-heading.is-style-jump-nav-label {
	font-family: var(--wp--preset--font-family--body);
	line-height: 1.65;
}

/* ---------------------------------------------------------------------------
   Forms
--------------------------------------------------------------------------- */
.cife-enquiry input[type="text"],
.cife-enquiry input[type="email"],
.cife-enquiry input[type="tel"],
.cife-enquiry input[type="url"],
.cife-enquiry input[type="number"],
.cife-enquiry input[type="date"],
.cife-enquiry select,
.cife-enquiry textarea,
.wp-block-search__input {
	border: 1px solid var(--wp--preset--color--muted);
	border-radius: var(--wp--preset--border-radius--small);
	padding: 0.6rem 0.75rem;
	background: var(--wp--preset--color--paper);
	color: var(--wp--preset--color--ink);
	/* Controls arrive in the browser's own typography (Arial 13.3px, monospace
	   textarea) - off the site's type scale, and under 16px it is enough for
	   iOS Safari to zoom the page when the field is focused. Match the body
	   face and the body size token (>=17px, so no focus zoom). */
	font-family: inherit;
	font-size: var(--wp--preset--font-size--medium);
	line-height: 1.4;
}
.cife-enquiry input:focus-visible,
.cife-enquiry select:focus-visible,
.cife-enquiry textarea:focus-visible,
.wp-block-search__input:focus-visible {
	border-color: var(--wp--preset--color--primary);
}

/* --- Enquiry form (Contact Form 7) ------------------------------------------
   CF7 owns the form and its behaviour; the theme owns how it looks. CF7's own
   stylesheet is switched off in inc/enquiry-form.php, so everything the form
   needs is here. The field rules above already apply, because the CF7 form
   template is wrapped in .cife-enquiry exactly like the form it replaced —
   that is what keeps the styling unchanged. */
.cife-enquiry form p {
	margin-bottom: 1rem;
}
.cife-enquiry label {
	display: block;
	font-weight: 600;
	margin-bottom: 0.25rem;
}
.cife-enquiry input[type="text"],
.cife-enquiry input[type="email"],
.cife-enquiry input[type="tel"],
.cife-enquiry input[type="url"],
.cife-enquiry input[type="number"],
.cife-enquiry input[type="date"],
.cife-enquiry select,
.cife-enquiry textarea {
	width: 100%;
	max-width: 32rem;
	/* The fields are padded, so the border box must own the width - with the
	   default content-box, padding + border pushed the inputs and the textarea
	   past the form's right edge on phones. (select is border-box by default,
	   which is why only the others overflowed.) */
	box-sizing: border-box;
}
/* Choice fields — checkbox and radio. CF7 renders these as a group of
   <span class="wpcf7-list-item"> wrappers, each holding the control plus a
   <span class="wpcf7-list-item-label"> for the option text. There is no <label>
   anywhere in an option, so the label rule above does not reach it; the group's
   own label, authored in the form, is the only label there is. CF7's stylesheet is
   off here, so its layout has to be replaced rather than inherited: CF7 spaces its
   items with whitespace pseudo-elements and a leading 1em margin, whereas this uses
   a real gap and no leading margin, so the first option sits flush with the field
   column exactly like every other control. */
.cife-enquiry .wpcf7-list-item {
	display: inline-block;
	margin: 0 1.25rem 0.25rem 0;
}
.cife-enquiry .wpcf7-list-item input[type="checkbox"],
.cife-enquiry .wpcf7-list-item input[type="radio"] {
	width: 1.15rem;
	height: 1.15rem;
	margin: 0 0.45rem 0 0;
	vertical-align: -0.25em;
	/* Native controls, so the palette has to be handed to the browser. */
	accent-color: var(--wp--preset--color--primary);
}
/* The acceptance field — CF7's consent checkbox — is the one option that wraps its
   control in a real <label>, which is also why it is the one option whose text is
   clickable and which has an accessible name. That is an *option* label, not a field
   label, so the block/bold rule further up has to be undone for it: otherwise the
   consent sentence drops onto its own line in bold, underneath its own checkbox.
   Measured before this rule existed: display block, weight 600, margin-bottom 4px,
   cursor default where the whole sentence is a click target. */
.cife-enquiry .wpcf7-list-item label {
	display: inline;
	font-weight: 400;
	margin: 0;
	cursor: pointer;
}
/* The plain checkbox and radio options are themselves <label class="wpcf7-list-item">
   after the markup rewrite in inc/enquiry-form.php (D147), so the field-label rule above
   would set each option's text in bold — an option is not a field label. Only these two
   properties are corrected: the item's own layout still comes from the .wpcf7-list-item
   rule further up, which must keep its inline-block display. */
.cife-enquiry label.wpcf7-list-item {
	font-weight: 400;
	cursor: pointer;
}

/* A border colour is invisible on a native checkbox, so the invalid state that the
   theme marks on every other control is an outline here. */
.cife-enquiry .wpcf7-list-item input.wpcf7-not-valid {
	outline: 2px solid #b32d2e;
	outline-offset: 2px;
}

/* File upload. A native file input arrives as an OS-grey button, off the site's
   palette and type scale, and it is the one control the grouped rules above cannot
   reach, because it is not a text field. The wrapper takes the same treatment as the
   other controls and the button is re-dressed with ::file-selector-button. Browsers
   without that pseudo-element (Safari before 14.1) keep the native button — a
   fallback, not a break. Focus needs nothing here: the input:focus-visible rule above
   is already type-agnostic. */
.cife-enquiry input[type="file"] {
	width: 100%;
	max-width: 32rem;
	box-sizing: border-box;
	border: 1px solid var(--wp--preset--color--muted);
	border-radius: var(--wp--preset--border-radius--small);
	padding: 0.4rem 0.5rem;
	background: var(--wp--preset--color--paper);
	color: var(--wp--preset--color--ink);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--medium);
	line-height: 1.4;
	cursor: pointer;
}
.cife-enquiry input[type="file"]::file-selector-button {
	font-family: inherit;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	color: var(--wp--preset--color--primary);
	background: transparent;
	border: 1px solid var(--wp--preset--color--primary);
	border-radius: var(--wp--preset--border-radius--small);
	padding: 0.35rem 0.8rem;
	margin-right: 0.75rem;
	cursor: pointer;
}
.cife-enquiry input[type="file"]::file-selector-button:hover {
	background: var(--wp--preset--color--mist);
}

.cife-enquiry__call {
	display: block;
	margin-top: 0.25rem;
}
.cife-enquiry__more {
	margin-bottom: 1rem;
}
.cife-enquiry__more summary {
	cursor: pointer;
	font-weight: 600;
}

/* Form-level notices. CF7's response output takes the same shape as the
   error notice the block renders when it is misconfigured.

   Scoped to .wpcf7 rather than .cife-enquiry (D142): CF7 prints its response output as a
   *sibling* of the .cife-enquiry wrapper, never a child of it, so the .cife-enquiry
   selector could never match — this styling was written, documented and dead, which is why
   a failed submit showed the message as bare body text butted against the submit button.
   CF7's hidden fieldset is scoped the same way, for the same reason. */
.cife-enquiry__notice,
.wpcf7 .wpcf7-response-output {
	border-left: 4px solid;
	padding: 0.75rem 1rem;
}
.cife-enquiry__notice--error,
.wpcf7 .wpcf7-response-output {
	border-color: #b32d2e;
	background: #fdf0f0;
}
.wpcf7 .wpcf7-response-output {
	/* CF7's default is 2em 0.5em 1em, which sits oddly in the band. */
	margin: 1rem 0 0;
}
/* The success state keys off the FORM, not off the output. CF7 puts its status class on the
   <form> (contact-form.php:689-690 maps the mail_sent status to `sent` — the same class CF7's
   own stylesheet reads) and never on the output: form_response_output() builds exactly
   `wpcf7-response-output` and appends nothing to it. So the old .wpcf7-mail-sent-ok selector
   could never match, and a successful submission fell through to the red error treatment
   above — a "Thank you" in a red box. Same bug class as D142, and the same lesson: check the
   class CF7 actually renders. The descendant relationship this relies on is the one already
   proven to work by the `form.init` rule below. */
.wpcf7 form.sent .wpcf7-response-output {
	border-color: var(--wp--preset--color--primary);
	background: transparent;
}

/* A spam block is not the visitor's mistake, so it reads differently from a validation error
   rather than identically to one. CF7's own colour for the state is #f56e28 (D144, stakeholder
   choice from the audit in D143); the tint is the theme's error tint — H 0°, S 76%, L 97% —
   rebuilt at that orange's own hue (H 20.5°), so the two notices stay one family instead of
   being an orange rule over a pink box. The site's `spam` message already reads
   "Something looked automated about that submission…", so the shape is the only thing that
   needed changing. */
.wpcf7 form.spam .wpcf7-response-output {
	border-color: #f56e28;
	background: #fdf5f0;
}

/* CF7 paints nothing here until it has something to say, and hides the markup in these
   states. Its own stylesheet is off here, so without this the notice above would render
   empty on every page load — a red left border and a tint with nothing inside it. The
   states are CF7's, reproduced: a fresh page load is `init`, a failed submit is `invalid`,
   and `invalid` is deliberately absent from this list. */
.wpcf7 form.init .wpcf7-response-output,
.wpcf7 form.resetting .wpcf7-response-output,
.wpcf7 form.submitting .wpcf7-response-output {
	display: none;
}

/* Per-field validation. CF7 marks the input itself and adds a tip below it. */
.cife-enquiry .wpcf7-not-valid {
	border-color: #b32d2e;
}
.cife-enquiry .wpcf7-not-valid-tip {
	display: block;
	margin-top: 0.25rem;
	color: #b32d2e;
	font-size: var(--wp--preset--font-size--small);
}

/* Spam honeypot: off-screen, never display:none, so bots still fill it. */
.cife-hp {
	position: absolute;
	left: -9999px;
	top: auto;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

/* CF7 wraps its own hidden inputs (form id, version, unit tag, posted-data hash) in a
   <fieldset> it emits *before* the form body, so the fieldset sits outside .cife-enquiry
   and has to be scoped to .wpcf7 instead. A fieldset paints a default border even when it
   holds nothing but hidden inputs, so without this it shows as an empty frame — CF7's own
   stylesheet normally hides it, and that stylesheet is off here. */
.wpcf7 .hidden-fields-container,
.wpcf7 .wpcf7-spinner {
	display: none;
}

/* CF7's accessible live region. It holds the "Please check the highlighted fields and try
   again." status line plus a bulleted "This field is required." for every failed field.
   CF7's own stylesheet is off here, and it shipped the only rule that keeps this off-screen,
   so without it a failed submit prints those sentences at the top of the form — the same
   words each field already shows underneath itself. The region must stay in the DOM: it is
   the aria-live announcement screen readers depend on, so hide it visually and never
   display:none. Like CF7's hidden fieldset above, it sits outside .cife-enquiry, so it is
   scoped to .wpcf7. Declarations are CF7's own, reproduced rather than reinvented. */
.wpcf7 .screen-reader-response {
	position: absolute;
	overflow: hidden;
	clip: rect(1px, 1px, 1px, 1px);
	clip-path: inset(50%);
	height: 1px;
	width: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	word-wrap: normal !important;
	word-break: normal !important;
}

/* CF7 submits with an <input type="submit">, not a button block, so core never names it
   `wp-element-button` and the treatment in `theme.json` (`styles.elements.button` -
   background, radius, padding, size, hover) could not reach it. `inc/enquiry-form.php`
   names it now (D255), so those values come from the same place every other button's do,
   plus the shared transition at the top of this file.

   Only what is specific to an <input> is left here: a form control inherits no font, and
   the UA gives it a border and its own cursor. **The treatment that used to be spelled out
   here is gone rather than re-tuned** - that duplicate is exactly what let this button sit
   at 8px / 18px while every other button on the site moved to 10px / 16px. One source, and
   it is the one the design system owns. `scripts/cf7-audit.php` asserts the pairing, so if
   CF7 ever stops rendering an input of this shape the failure is named rather than seen. */
.cife-enquiry input[type="submit"],
.cife-enquiry .wpcf7-submit {
	border: 0;
	font-family: inherit;
	cursor: pointer;
}
.cife-enquiry input[type="submit"]:focus-visible,
.cife-enquiry .wpcf7-submit:focus-visible {
	outline: 2px solid var(--wp--preset--color--accent);
	outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   The excerpt "read more" field, in the editor
   core/post-excerpt always offers an author a "more text" field and offers no
   attribute to switch it off (block-library.js renders it unconditionally), so
   the theme hides the blank field instead. Two homes, one problem (D126): the
   hero bands, and the college cards, whose copies live in page content as
   ordinary blocks an author can click into and type a stray "read more" into by
   accident. The theme never uses it: every
   excerpt here is a curated summary, and patterns/college-card.php opts out in
   markup with moreText:"" (D111).
   WordPress 7.1 renders the field's placeholder as an inner
   <span data-rich-text-placeholder>, so the field itself is never :empty and it
   is the placeholder that has to be matched. That attribute exists only while
   the field is blank, and only in the editor - rendered HTML never carries it -
   so a filled link and every front-end page are untouched. The :empty pair
   covers versions that hang the attribute on the field itself.
   The archive and index query loops
   keep the field, because a list of teasers may legitimately want one.
   :is() rather than a second comma-list keeps one specificity (0,2,0), so adding
   the cards cannot reorder anything against core's own display rule.
--------------------------------------------------------------------------- */
:is(.has-mist-background-color, .cife-college-panel) .wp-block-post-excerpt__more-link > [data-rich-text-placeholder],
:is(.has-mist-background-color, .cife-college-panel) .wp-block-post-excerpt__more-text:has(> .wp-block-post-excerpt__more-link > [data-rich-text-placeholder]),
:is(.has-mist-background-color, .cife-college-panel) .wp-block-post-excerpt__more-link:empty,
:is(.has-mist-background-color, .cife-college-panel) .wp-block-post-excerpt__more-text:has(> .wp-block-post-excerpt__more-link:empty) {
	display: none;
}

/* ---------------------------------------------------------------------------
   Accessibility
--------------------------------------------------------------------------- */
:focus-visible {
	outline: 2px solid var(--wp--preset--color--primary);
	outline-offset: 2px;
}
/* The primary ring is invisible on ink; switch to paper there. */
:where(.has-ink-background-color) :focus-visible,
header.wp-block-group :focus-visible {
	outline-color: var(--wp--preset--color--paper);
}
.skip-link.screen-reader-text:focus {
	position: fixed;
	top: 0.5rem;
	left: 0.5rem;
	z-index: 100000;
	padding: 0.75rem 1rem;
	background: var(--wp--preset--color--paper);
	color: var(--wp--preset--color--ink);
	border-radius: var(--wp--preset--border-radius--small);
}

/* The cife mark used to stand in for a missing photograph, so the grid kept its rhythm where a college
   had none — a production concern: six of the colleges there had no photograph. Every college in this
   build has one (D104), so the stand-in was never doing anything, and the stakeholder asked for it to be
   removed (D132). Nothing takes its place, and nothing moves when a college has no image: the photograph
   slot's 3:2 shape comes from the block's own aspect-ratio, and the card simply has no photograph. */

/* ---------------------------------------------------------------------------
   College listing: the course-route panel language
   Adopted from the chooser mock-up, 16-09-26. The brief names
   /courses-at-cife-colleges/ as the style anchor, and its grid is the site's own
   orange-line device: flat surface panels with no chrome, divided by 2px
   accent-light rules drawn in the gutters rather than by a border on each cell -
   a border can only outline a panel, sits off-centre in the gutter, and two
   adjacent borders in one gutter read as nothing at all. Same two dials
   (--panel-rule, --panel-rule-color) and the same offsets as the route panels,
   so the college grid and the route grid read as one system. What this adds is
   the flush photograph, the cife-mark stand-in for the six colleges that have no
   photograph, and the gutters this grid's own count needs. Column count is
   explicit rather than auto-fill because the rules are drawn against it: three
   up, two in the middle, one - a list - below 620px. The panel frame and the
   stack's 7/6/6 spacing come from the pattern's own block attributes, not from
   here.
--------------------------------------------------------------------------- */
.cife-college-grid {
	--panel-rule: 2px;
	--panel-rule-color: var(--wp--preset--color--accent-light);
}

/* The route panels' gutter, not the card grid's tighter one: both grids should
   divide at the same rhythm, and the rule offsets below are derived from it.
   `grid-auto-rows: 1fr` evens the row heights out: without it each row is only as tall as
   its own tallest card, so the grid reads as strips of different heights with a different
   foot line in each. This way the tallest profile in the grid sets every card's height. */
.cife-college-grid .wp-block-post-template.is-layout-grid {
	gap: var(--wp--preset--spacing--50);
	grid-auto-rows: 1fr;
}

.cife-college-panel {
	/* A column, so the text block can take the height left under the photograph and the
	   card's action can sit at its foot. */
	display: flex;
	flex-direction: column;
	height: 100%;
	/* The photograph sits flush to the panel's top edge, so the panel's corners
	   clip it or a rounded image shows through square ones. */
	overflow: hidden;
	background-color: var(--wp--preset--color--surface);
}

.cife-college-panel .wp-block-post-featured-image img {
	border-radius: 0;
}

/* The panel is a directory entry, not the profile. At three columns the prose ran
   to seven or eight lines and two thirds of the panel, so it is set a step down:
   the name and the course list lead, and the excerpt is the quiet closing line,
   whole, including its "ask cife" sentence. */
.cife-college-panel .wp-block-post-excerpt {
	font-size: var(--wp--preset--font-size--small);
	line-height: 1.6;
}

/* The card's action, at its foot. It is `core/read-more`, not a Button block: inside a
   Query Loop a Button cannot take the current post's permalink (the URL attribute is not
   bindable), while read-more links to the post it renders in. The anchor itself carries
   `.wp-block-read-more` — there is no wrapper element — and the theme already dresses that
   class as a plain centred link for article cards, so this rule is panel-scoped and has to
   override it. Dressed to match the theme's `elements.button`: primary, paper text, 10px
   radius, 600, at small size, with more clearance than the stack's 6px step, so the action
   does not read as the last line of the text run. */
.cife-college-panel a.wp-block-read-more {
	display: inline-block;
	/* Centred on the panel's axis like the name, and shrunk to its label rather than stretching
	   to the column — the column is a flex container and would otherwise stretch every child. */
	align-self: center;
	/* The card's foot, not wherever the copy happens to end — see the column rule below. */
	margin-block-start: auto;
	text-align: start;
	background-color: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--paper);
	/* A step below the page's buttons: small label, tight frame, still 600 weight. */
	padding: 0.5rem 1rem;
	border-radius: 10px;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	text-decoration: none;
}

/* A boxed action wants a straight row of feet, not one button per copy length. The text block
   takes the leftover height under the photograph, the action takes the leftover height above
   itself, and every button in a row lands on the same line. `auto` replaces the 6px flow step
   on that one block, so the excerpt carries a 12px margin-bottom to keep the floor. The
   `margin-inline: 0` undoes the auto side margins core's constrained layout gives its children:
   inside a flex column those centre every line instead of leaving it flush. */
.cife-college-panel > .wp-block-group {
	display: flex;
	flex-direction: column;
	flex: 1;
}

/* Every line in the panel is full width, so nothing sits where it does by accident. Core's
   constrained layout gives these children `margin-left/right: auto` *with* `!important`, and
   inside a flex column auto margins absorb the free space before alignment runs: the box
   shrink-wraps and centres, while its lines still start at the box's left edge. That was the
   tell — a one-line name looked centred while a two- or three-line one read as flush left beside
   it, which is what "some of the names aren't centered" pointed at. Zeroing these margins and
   centring the name's text puts every name on the same axis; the action joins it with
   `align-self: center` instead. */
.cife-college-panel > .wp-block-group > :where(.wp-block-post-title, .wp-block-post-terms, .wp-block-post-excerpt, a.wp-block-read-more) {
	margin-inline: 0 !important;
}

.cife-college-panel .wp-block-post-title {
	text-align: center;
}

.cife-college-panel .wp-block-post-excerpt {
	margin-block-end: 0.75rem;
}

.cife-college-panel a.wp-block-read-more:hover {
	background-color: var(--wp--preset--color--primary-deep);
	color: var(--wp--preset--color--paper);
}

/* theme.json gives every post title a 0.4rem bottom margin, and that margin is
   added to the layout's block gap — so the name had 26.4px of air under it against
   20px under the course list. Nothing in a panel's stack should be further from
   the name than from the line below it. The margin goes; the group's block gap is
   spacing|30 (12px), which is then the same distance above and below the course
   list, and tighter than the 20px it was. */
.cife-college-panel .wp-block-post-title {
	margin-bottom: 0;
}

.cife-college-grid .wp-block-post {
	position: relative;
}

/* Both rules are drawn by EVERY cell, and the grid clips whatever falls outside its own
   box. That removes exactly two of them: the vertical rule of the first column and the
   horizontal rule of the first row, which both sit one gutter outside the grid. Nothing
   here counts children.

   D119 replaced an `nth-child` scheme that drew the rules on selected cells and stretched
   them across the gutters. Index arithmetic assumed the grid's children were only cards,
   and in the block editor they are not: the editor's Query Loop renders an invisible 0×0
   duplicate of the first post, which shifted every index by one and put the rule belonging
   to the second row above the first row's third card. Drawing on every cell is index-free,
   so a placeholder, a spinner or an appender in the editor cannot move a rule again — and
   the column counts below are the only breakpoint-dependent part left. */
.cife-college-grid .wp-block-post-template.is-layout-grid {
	overflow: clip;
}

.cife-college-grid .wp-block-post::before,
.cife-college-grid .wp-block-post::after {
	content: "";
	display: block;
	position: absolute;
	background-color: var(--panel-rule-color);
}

/* The vertical rule: every cell, centred in the gutter to its left. The first
   column's is clipped away. */
.cife-college-grid .wp-block-post::before {
	top: 0;
	bottom: 0;
	left: calc((var(--wp--preset--spacing--50) + var(--panel-rule)) / -2);
	width: var(--panel-rule);
}

/* The horizontal rule: every cell, centred in the gutter above it. Its width is
   100vw on purpose — the clip trims it to the grid's right edge, so it never breaks
   at a gutter and never needs to know how many columns there are. The first row's is
   clipped away. */
.cife-college-grid .wp-block-post::after {
	top: calc((var(--wp--preset--spacing--50) + var(--panel-rule)) / -2);
	height: var(--panel-rule);
	left: 0;
	width: 100vw;
}

/* Column counts. The rules need to know nothing about them. */
@media (min-width: 1024px) {
	.cife-college-grid .wp-block-post-template.is-layout-grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

/* Two columns in the middle. */
@media (min-width: 620px) and (max-width: 1023px) {
	.cife-college-grid .wp-block-post-template.is-layout-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/* One column: the same grid stacked, which is what the route panels do on a phone.
   The horizontal rule becomes the divider between cards; the vertical one has nothing
   left to divide and is clipped. */
@media (max-width: 619px) {
	.cife-college-grid .wp-block-post-template.is-layout-grid {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* ---------------------------------------------------------------------------
   Easter revision comparison rows

   The comparison used to be a table. On a phone that table was a horizontally
   scrolling grid of eight colleges, and the one thing a reader on a phone
   cannot do is compare columns they cannot see at once. It is now one
   collapsible row per college: the summary carries the two facts that decide
   between them - class size and from-price - and the rest of the card waits
   behind the disclosure.

   The disclosure is core/details, which is a native <details>, so this needs
   no script, the collapsed copy stays in the DOM and stays indexed, and the
   editor can open and edit it. Collapsed, the eight rows measure about 850px
   on a 390px phone against roughly 7,500px with every card open.

   `is-style-revision-row` is registered in functions.php so the treatment can
   be applied and removed from the editor's Styles panel.
--------------------------------------------------------------------------- */
.wp-block-details.is-style-revision-row {
	background: var(--wp--preset--color--paper);
	border: 1px solid var(--wp--preset--color--line);
	border-radius: var(--wp--preset--border-radius--medium);
	overflow: hidden;
}

/* The summary is the row the visitor scans: the college name, then the facts,
   then the chevron. Flex, so the facts hold the right edge and the name takes
   what is left - which is what makes eight collapsed rows line up and read as
   the comparison the table used to be. */
.wp-block-details.is-style-revision-row > summary {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.4rem 0.6rem;
	padding: 0.8rem 1.1rem;
	cursor: pointer;
	list-style: none;
	color: var(--wp--preset--color--muted);
	font-size: var(--wp--preset--font-size--small);
	transition: background-color 0.18s ease;
}

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

.wp-block-details.is-style-revision-row > summary:hover {
	background: var(--wp--preset--color--mist);
}

.wp-block-details.is-style-revision-row > summary:focus-visible {
	outline: 2px solid var(--wp--preset--color--primary);
	outline-offset: -2px;
}

/* The college name is the only strong run in a summary and the only part set
   in the heading serif, so the row reads as a name followed by its facts
   rather than as one long sentence. */
.wp-block-details.is-style-revision-row > summary strong {
	flex: 1 1 auto;
	min-width: 0;
	color: var(--wp--preset--color--ink);
	font-family: var(--wp--preset--font-family--heading);
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 600;
	line-height: 1.25;
}

/* The chevron is drawn, not typed. U+203A is a small mark even set large, and
   this one has to be findable at a glance, so two borders on a rotated square
   give an exact size and weight instead of whatever the font decides. */
.wp-block-details.is-style-revision-row > summary::after {
	content: "";
	flex: 0 0 auto;
	align-self: center;
	width: 0.6rem;
	height: 0.6rem;
	border-right: 2.5px solid var(--wp--preset--color--primary);
	border-bottom: 2.5px solid var(--wp--preset--color--primary);
	transform: rotate(-45deg);
	transition: transform 0.18s ease;
}

/* Open, the row becomes the card's own header: tinted, and separated from the
   body it now sits above instead of closing the panel. */
.wp-block-details.is-style-revision-row[open] > summary {
	background: var(--wp--preset--color--mist);
	border-bottom: 1px solid var(--wp--preset--color--line);
}

.wp-block-details.is-style-revision-row[open] > summary::after {
	transform: rotate(45deg);
}

/* The rows are one comparison, not eight sections: they stack at a tighter step
   than the theme's flow gap, which would float each college a section apart.
   Core's own flow rule is wrapped in :where(), so this class rule wins on
   specificity without needing !important. */
.cife-revision-list > * + * {
	margin-block-start: 0.5rem;
}

/* The disclosure's own flow gap would land on top of this padding, leaving the
   card 24px heavier above its first paragraph than below its last. The padding
   is the card's inset; the gap goes. */
.wp-block-details.is-style-revision-row > .cife-revision-body {
	margin-block-start: 0;
}

.cife-revision-body {
	padding: 1.1rem;
}

.cife-revision-body > * + * {
	margin-block-start: var(--wp--preset--spacing--40);
}

/* One fact: a fixed label column, so class sizes and prices line up down the
   page - the job the table was doing. Flex rather than a grid, because a value
   is sometimes one line and sometimes a list. */
.cife-spec {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.3rem 0.9rem;
}

.cife-spec__label {
	flex: 0 0 6.5rem;
	margin-block-start: 0;
	color: var(--wp--preset--color--ink);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

.cife-spec__value {
	flex: 1 1 12rem;
	min-width: 0;
	margin-block-start: 0;
	/* A value is sometimes one line and sometimes a list, and a browser indents a
	   list by 40px to make room for its markers - so a one-line value like
	   "6 (max 9)" stood a whole 40px left of the bulleted lines in the same
	   column and the facts ran on two vertical edges. Every value takes that
	   marker gutter, list or not, so a class size lines up with the bullets it
	   sits between. Declared on the value rather than left to the browser so the
	   list and the paragraph cannot drift apart. */
	padding-inline-start: 2.5rem;
}

.cife-spec__value p {
	margin-block-start: 0;
}

/* The card's CTAs are one row of controls at the foot of a panel, not a
   section's call to action, so they run a step tighter than the theme's
   button gap and padding. */
.cife-revision-body .wp-block-buttons {
	gap: 0.5rem;
}

.cife-revision-body .wp-element-button {
	padding: 0.62rem 1rem;
	font-size: var(--wp--preset--font-size--small);
}

/* Core gives a filled button no border and an outline button a 2px one, which
   leaves three buttons in a row 4px out of step with each other. The filled
   button takes the same 2px edge in its own colour - invisible on the solid
   fill, but it restores the height - so the row lines up. */
.cife-revision-body .wp-block-button:not(.is-style-outline) .wp-element-button {
	border: 2px solid var(--wp--preset--color--primary);
}

.cife-revision-body .wp-block-button:not(.is-style-outline) .wp-element-button:hover {
	border-color: var(--wp--preset--color--primary-deep);
}

@media (max-width: 640px) {
	/* Two lines on a phone - the name, then the facts beneath it. The chevron
	   leaves the flow and pins itself to the right edge, so the facts do not
	   push it onto a third line of their own. */
	.wp-block-details.is-style-revision-row > summary {
		display: block;
		position: relative;
		padding: 0.72rem 2.1rem 0.78rem 0.95rem;
	}

	.wp-block-details.is-style-revision-row > summary strong {
		display: block;
		font-size: var(--wp--preset--font-size--medium);
	}

	.wp-block-details.is-style-revision-row > summary::after {
		position: absolute;
		top: 50%;
		right: 0.9rem;
		transform: translateY(-50%) rotate(-45deg);
	}

	.wp-block-details.is-style-revision-row[open] > summary::after {
		transform: translateY(-50%) rotate(45deg);
	}

	.cife-revision-body {
		padding: 0.95rem;
	}

	/* Label above value: a 6.5rem label column at 390px leaves a cost line too
	   narrow to hold a figure without breaking it. */
	.cife-spec {
		display: block;
	}

	.cife-spec__label {
		display: block;
		margin-block-end: 0.1rem;
	}
}

/* ---------------------------------------------------------------------------
   Core's accordion wears the site's accordion language
---------------------------------------------------------------------------
   The FAQ page and the scholarships page already build their questions with
   core's own Accordion blocks (21 and 20 items) and arrive with its default
   look: no hover, no focus ring, a typed + at the end of the line, a 24px gap
   between items, and nothing under an answer. The Easter comparison rows are
   the site's accordion language, so the same values are applied here - the same
   hover tint, the same focus ring, the same drawn chevron, the same tint on the
   open row, the same inset under the answer.

   They are the same cards the comparison rows are, because that is what the
   treatment is: the paper ground, the hairline border, the radius, the inset
   inside it, and the tighter step between one card and the next.

   Core's markup is styled, never replaced. The + glyph is hidden and the
   chevron drawn beside it, so the block keeps working across core updates and
   the editor keeps the block; `is-open` is the class core's own interactivity
   API puts on the item while it is open.
--------------------------------------------------------------------------- */

/* Each question is its own card, exactly as each comparison row is. `overflow:
   hidden` keeps the open header's tint inside the rounded corners, and
   `box-sizing` stops the 1px border adding two pixels to a row that already
   fills the column - the overflow that made the first attempt look wrong. */
.wp-block-accordion-item {
	background: var(--wp--preset--color--paper);
	border: 1px solid var(--wp--preset--color--line);
	border-radius: var(--wp--preset--border-radius--medium);
	box-sizing: border-box;
	overflow: hidden;
}

.wp-block-accordion-item + .wp-block-accordion-item {
	margin-block-start: 0.5rem;
}

/* The question's size is set in theme.json, not here: `core/accordion-heading`
   takes the theme's `large` preset, because without a style of its own the block
   inherits the h2/h3 heading scale and a list of questions outweighs the prose. */

/* The row inset is the card's inset: without it the question sits hard on the
   border and an open answer runs to the tint's edge. */
.wp-block-accordion-heading__toggle {
	padding: 0.8rem 1.1rem;
	cursor: pointer;
	transition: background-color 0.18s ease;
}

.wp-block-accordion-heading__toggle:hover,
.wp-block-accordion-item.is-open .wp-block-accordion-heading__toggle {
	background: var(--wp--preset--color--mist);
}

/* Open, the header is the card's own header: tinted, and separated from the
   answer it now sits above instead of closing the card. */
.wp-block-accordion-item.is-open .wp-block-accordion-heading__toggle {
	border-bottom: 1px solid var(--wp--preset--color--line);
}

.wp-block-accordion-heading__toggle:focus-visible {
	outline: 2px solid var(--wp--preset--color--primary);
	outline-offset: -2px;
}

/* The site says "more" with two borders on a rotated square, not with a glyph
   whose size the font decides. */
.wp-block-accordion-heading__toggle-icon {
	display: none;
}

.wp-block-accordion-heading__toggle::after {
	content: "";
	flex: 0 0 auto;
	align-self: center;
	margin-inline-start: auto;
	width: 0.6rem;
	height: 0.6rem;
	border-right: 2.5px solid var(--wp--preset--color--primary);
	border-bottom: 2.5px solid var(--wp--preset--color--primary);
	transform: rotate(-45deg);
	transition: transform 0.18s ease;
}

.wp-block-accordion-item.is-open .wp-block-accordion-heading__toggle::after {
	transform: rotate(45deg);
}

/* The comparison body's inset, on all four sides, and the disclosure's own flow
   gap removed: with it, the answer would sit 24px further down than the header's
   padding intends and the card would be heavier above it than below. */
.wp-block-accordion-panel {
	margin-block-start: 0;
	padding: 1.1rem;
}

/* ---------------------------------------------------------------------------
   Responsive
--------------------------------------------------------------------------- */
@media (max-width: 782px) {
	.wp-site-blocks main {
		padding-block: var(--wp--preset--spacing--50) var(--wp--preset--spacing--60);
	}
}

/* The chevron turns as a row opens, and the row tints; both are decoration on
   a state the browser changes on its own, so a reduced-motion reader keeps the
   state and loses only the animation between the two. */
@media (prefers-reduced-motion: reduce) {
	html {
		scroll-behavior: auto;
	}
	.is-style-header-cta,
	.wp-element-button,
	.wp-block-button__link,
	.wp-block-details.is-style-revision-row > summary,
	.wp-block-details.is-style-revision-row > summary::after,
	.wp-block-accordion-heading__toggle,
	.wp-block-accordion-heading__toggle::after {
		transition: none;
	}
}
