/*
 * Supplemental styles.
 *
 * Everything here exists because theme.json and block attributes cannot express
 * it. Each rule carries its reason. If a rule stops needing its reason, delete
 * the rule.
 *
 * No literal colours, sizes, durations, or font stacks appear below — only
 * tokens from theme.json.
 */

/* ---------------------------------------------------------------------------
 * Accessibility
 * ------------------------------------------------------------------------ */

/*
 * WHY: theme.json has no representation for :focus-visible. A visible keyboard
 * focus ring is a hard requirement in CLAUDE.md.
 */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
	outline: var(--wp--custom--focus-ring) solid var(--wp--preset--color--primary);
	outline-offset: var(--wp--custom--focus-ring);
	border-radius: var(--wp--custom--radius--sm);
}

/*
 * WHY: suppressing the default outline is only safe once the rule above exists.
 * Kept adjacent so a later edit cannot separate them.
 */
:where(a, button, input, select, textarea, summary, [tabindex]):focus:not(:focus-visible) {
	outline: none;
}

/*
 * WHY: the skip link is printed by WordPress core, not by a block, so no block
 * attribute can reach it.
 */
.skip-link.screen-reader-text:focus {
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--primary);
	padding: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40);
	z-index: 100000;
}

/* ---------------------------------------------------------------------------
 * Reading
 * ------------------------------------------------------------------------ */

/*
 * The 68ch reading cap that used to live here is gone.
 *
 * CLAUDE.md asks for a ~68ch measure on body copy, and that is the right number
 * for long-form reading. Sajid asked for content to sit at the full width
 * instead, so the cap was removed rather than left fighting the layout — a rule
 * that quietly overrides the width set in theme.json is worse than no rule.
 *
 * The `measure` token is still defined in theme.json. Restoring the cap for
 * blog posts alone is a matter of putting this block back, scoped to single.html
 * rather than to every entry-content.
 */

/*
 * WHY: text-wrap is not a theme.json property. Balancing headings stops a
 * lone orphaned word on the second line, which is very visible at display
 * sizes; pretty-wrapping body copy avoids orphans without reflowing the block.
 */
:where(h1, h2, h3, .wp-block-post-title) {
	text-wrap: balance;
}

:where(p, li) {
	text-wrap: pretty;
}

/*
 * WHY: neither the selection pseudo-element nor its colours can be set from
 * theme.json.
 */
::selection {
	background-color: var(--wp--preset--color--primary-soft);
	color: var(--wp--preset--color--contrast);
}

/* ---------------------------------------------------------------------------
 * Links
 * ------------------------------------------------------------------------ */

/*
 * WHY: text-underline-offset and text-decoration-thickness have no theme.json
 * equivalent. The default underline sits tight against a serif descender and
 * reads as a mistake; offsetting it is what makes body links look typeset
 * rather than default-styled.
 *
 * The colour transition is here for the same reason — theme.json expresses the
 * hover colour but not how it gets there.
 */
.ias-entry-content a:where(:not(.wp-element-button)),
.wp-block-post-excerpt a:where(:not(.wp-element-button)) {
	text-underline-offset: var(--wp--custom--underline-offset);
	text-decoration-thickness: from-font;
	transition: color var(--wp--custom--motion--duration-fast) var(--wp--custom--motion--ease),
		text-decoration-color var(--wp--custom--motion--duration-fast) var(--wp--custom--motion--ease);
}

/*
 * WHY: navigation links are deliberately underline-free until interaction —
 * an underline on every nav item competes with the mono tracking. theme.json
 * has no :hover text-decoration control for the navigation block.
 */
.wp-block-navigation .wp-block-navigation-item__content {
	text-decoration: none;
	transition: color var(--wp--custom--motion--duration-fast) var(--wp--custom--motion--ease);
}

.wp-block-navigation .wp-block-navigation-item__content:hover,
.wp-block-navigation .current-menu-item .wp-block-navigation-item__content {
	color: var(--wp--preset--color--primary);
	text-decoration: underline;
	text-underline-offset: var(--wp--custom--underline-offset);
}

/* ---------------------------------------------------------------------------
 * Interactive states
 * ------------------------------------------------------------------------ */

/*
 * WHY: theme.json expresses a hover colour but not a transition between
 * states. Transform and colour only — never width, height, or position — so
 * the browser can composite it without a layout pass.
 */
.wp-element-button,
.wp-block-button__link {
	transition: background-color var(--wp--custom--motion--duration-fast) var(--wp--custom--motion--ease),
		color var(--wp--custom--motion--duration-fast) var(--wp--custom--motion--ease),
		transform var(--wp--custom--motion--duration-fast) var(--wp--custom--motion--ease);
}

/*
 * WHY: a press needs to register within ~100ms to feel connected to the tap.
 * A 1% scale is deliberately almost invisible; overshoot would read as
 * decoration on a site whose argument is restraint.
 */
.wp-element-button:active,
.wp-block-button__link:active {
	transform: scale(0.99);
}

/*
 * WHY: card hover. theme.json block style variations cannot express a hover
 * state at all. The lift is one pixel of translation and a border that gains
 * contrast — enough to say "this is a link", not enough to bounce.
 */
.wp-block-group.is-style-ias-card {
	border: var(--wp--custom--hairline) solid transparent;
	transition: border-color var(--wp--custom--motion--duration) var(--wp--custom--motion--ease),
		transform var(--wp--custom--motion--duration) var(--wp--custom--motion--ease);
}

.wp-block-group.is-style-ias-card:hover {
	border-color: var(--wp--preset--color--primary-soft);
	transform: translateY(calc(var(--wp--custom--hairline) * -2));
}

/*
 * WHY: images inside cards need to fill the frame consistently regardless of
 * source aspect ratio. object-fit and aspect-ratio are not theme.json
 * properties, and reserving the box prevents layout shift as images load.
 */
.wp-block-group.is-style-ias-card .wp-block-post-featured-image img {
	aspect-ratio: 16 / 10;
	object-fit: cover;
	width: 100%;
}

/* ---------------------------------------------------------------------------
 * Touch targets and grid rhythm
 * ------------------------------------------------------------------------ */

/*
 * WHY: buttons computed to 43px tall — one pixel under the 44px minimum touch
 * target. theme.json can set padding but not a floor, and a floor is what this
 * needs: it holds regardless of what the font size becomes later.
 */
.wp-element-button,
.wp-block-button__link {
	min-height: var(--wp--custom--touch-target);
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

/*
 * WHY: navigation links measured 22px of hit area. The extra padding is
 * invisible but doubles the target, which matters most on the mobile overlay
 * where these are the only navigation. No theme.json equivalent for the
 * navigation item's inner element.
 */
.wp-block-navigation .wp-block-navigation-item__content {
	padding-block: calc((var(--wp--custom--touch-target) - 1.375rem) / 2);
}

/*
 * WHY: the navigation overlay's open and close buttons are icon-only and render
 * at 24x24 — well under the 44px minimum, and they are the only way to reach
 * navigation on a phone. Core sizes them from the icon, so only CSS can set a
 * floor. The icon itself is unchanged; the hit area grows around it.
 */
.wp-block-navigation__responsive-container-open,
.wp-block-navigation__responsive-container-close {
	min-width: var(--wp--custom--touch-target);
	min-height: var(--wp--custom--touch-target);
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

/*
 * WHY: grid children do not stretch to the row height on their own, so cards in
 * a row ended up ragged and the surface colour stopped at different heights.
 * The Query Loop's grid layout comes from block supports; only CSS can reach
 * its generated list items.
 */
.wp-block-post-template.is-layout-grid > li {
	display: grid;
}

.wp-block-post-template.is-layout-grid > li > .wp-block-group {
	height: 100%;
}

/*
 * WHY: taxonomy terms render as links and inherit the body link underline,
 * which on a row of three mono uppercase tags reads as noise rather than
 * navigation. Colour and the mono treatment carry the affordance instead.
 */
.wp-block-post-terms a {
	text-decoration: none;
}

.wp-block-post-terms a:hover {
	text-decoration: underline;
	text-underline-offset: var(--wp--custom--underline-offset);
}

/*
 * WHY: the patterns already ask for muted terms — `{"textColor":"muted"}` — but
 * that colour lands on the wrapper, and the anchors inside it take the global
 * link colour instead. The result was a row of up to twelve mono uppercase tags
 * in full primary, louder than the card title above them: the eye reached the
 * stack list before the name of the project.
 *
 * Inheriting from the wrapper is what the pattern was asking for in the first
 * place. This is a correction, not a restyle — hover still goes to primary, so
 * the link affordance is unchanged.
 */
.wp-block-post-terms.has-text-color a {
	color: inherit;
}

.wp-block-post-terms.has-text-color a:hover,
.wp-block-post-terms.has-text-color a:focus-visible {
	color: var( --wp--preset--color--primary );
}

/*
 * WHY: grid rows take the height of their tallest card, and these excerpts run
 * from three lines to eight. One long card was leaving a third of its row-mates
 * as empty background — visible emptiness, because the cards are filled.
 *
 * Clamping the teaser to five lines converges the heights without touching the
 * content: the full text is one click away on the entry itself, and no excerpt
 * on the site is written to be read in full from a card. line-clamp needs the
 * -webkit- prefixed box model even in browsers that support the standard
 * property, so both are set.
 */
.wp-block-group.is-style-ias-card .wp-block-post-excerpt__excerpt {
	display: -webkit-box;
	overflow: hidden;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 5;
	line-clamp: 5;
}

/* ---------------------------------------------------------------------------
 * Core block corrections
 * ------------------------------------------------------------------------ */

/*
 * WHY: core ships a brand colour per social service as `.wp-social-link-<service>`
 * rules, and for a while this theme overrode all of them to `muted` — one accent,
 * five icons, no argument.
 *
 * Sajid reversed that: wherever a network's own mark appears on this site it
 * should be that network's own colour. So core's rules are left alone and only
 * the transition is added here, which theme.json cannot express.
 *
 * The share row under a post is deliberately different — it holds the brand
 * colour back until hover, because four of them under every post pull harder
 * than the post does. See src/share/style.css.
 */
.wp-block-social-links.is-style-logos-only .wp-social-link {
	transition: opacity var(--wp--custom--motion--duration-fast) var(--wp--custom--motion--ease);
}

.wp-block-social-links.is-style-logos-only .wp-social-link:hover,
.wp-block-social-links.is-style-logos-only .wp-social-link:focus-visible {
	opacity: 0.7;
}

/*
 * WHY: core renders a separator as a centred 100px dash unless it carries
 * is-style-wide. Under a section heading that reads as a stray mark rather than
 * a rule. Width is not expressible through register_block_style's style_data,
 * so the hairline variation is squared up here.
 *
 * The :not() carries no meaning beyond specificity: core's rule is
 * .wp-block-separator:not(.is-style-wide):not(.is-style-dots), three classes,
 * which out-specifies a plain two-class selector. Matching that count lets this
 * sheet win on load order instead of resorting to !important.
 */
.wp-block-separator.is-style-ias-hairline:not(.is-style-dots) {
	/*
	 * No max-width override here: the constrained layout sets max-width to
	 * contentSize, and overriding it made the rule span the viewport while every
	 * heading above it stopped at the content column. The rule should align with
	 * the text it divides.
	 */
	width: 100%;
	height: var(--wp--custom--hairline);
	border: 0;
	opacity: 1;
}

/*
 * WHY: the search field's inner input does not inherit the block's border
 * styles from theme.json — core applies them to the wrapper. Without this the
 * field renders with the browser default border against a designed page.
 */
.wp-block-search__input {
	border: var(--wp--custom--hairline) solid var(--wp--preset--color--border);
	border-radius: var(--wp--custom--radius--sm);
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
	padding: var(--wp--preset--spacing--30);
	min-height: var(--wp--custom--touch-target);
}

.wp-block-search__input:focus {
	border-color: var(--wp--preset--color--primary);
}

/* ---------------------------------------------------------------------------
 * Testimonials
 * ------------------------------------------------------------------------ */

/*
 * WHY: a quotation needs to read as a quotation. theme.json has no way to hang
 * a decorative mark off a block, and the accent rule is what ties the card to
 * the palette without adding a second border colour.
 *
 * ⚠️ The selector carries `.wp-block-group.is-style-ias-card` on purpose. The
 * card style sets `border: 1px solid transparent` with two classes and an
 * element, which beat a single `.ias-testimonial` — the accent rule computed to
 * `rgba(0, 0, 0, 0)` and never rendered at all. Do not simplify this selector.
 *
 * Colour is `primary`, not `primary-soft`: soft measures 1.32:1 against the card
 * background, so the rule was invisible even once it applied.
 */
.wp-block-group.is-style-ias-card.ias-testimonial {
	display: flex;
	flex-direction: column;
	border-left: var(--wp--custom--accent-rule) solid var(--wp--preset--color--primary);
	padding-left: var(--wp--preset--spacing--40);
}

/*
 * WHY: the attribution needs air between it and the quote it belongs to,
 * without a margin that collapses into the card padding.
 */
.ias-testimonial .is-style-ias-meta {
	padding-top: var(--wp--preset--spacing--40);
}

/* ---------------------------------------------------------------------------
 * Testimonial slider
 * ------------------------------------------------------------------------ */

/*
 * WHY: a slider, requested by Sajid, built with **no JavaScript**. None of this
 * is expressible in theme.json — scroll-snap, overflow, and the CSS carousel
 * pseudo-elements have no block-editor equivalent.
 *
 * Native overflow scrolling is what makes it work: touch swipe, trackpad, and
 * shift-scroll come free, and browsers make scroll containers keyboard
 * focusable, so arrow keys move it without a script. That is the whole reason
 * to do it this way rather than shipping a carousel library for three quotes.
 *
 * It does not autoplay, deliberately. Text that moves on a timer is text that
 * gets pulled away from a slow reader, and no keystroke brings it back.
 */
.ias-testimonials {
	display: flex;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	overscroll-behavior-x: contain;
	padding-bottom: var( --wp--preset--spacing--30 );
	/*
	 * The block's flex layout centres its children, which left the two short
	 * quotes floating in the middle of the row. Stretching them instead made it
	 * worse: Dan's quote is three times the length of the others, so equal
	 * heights meant 350px of empty card under two of them.
	 *
	 * With one full-width quote per view, short quotes are centred in a stage as
	 * tall as the longest one. A flex row is always as tall as its tallest item,
	 * so the stage is not a choice — only where the short quotes sit inside it
	 * is, and centred reads as deliberate where top-aligned reads as a bug.
	 *
	 * ⚠️ On a phone that same fact makes the slider the wrong pattern outright:
	 * Dan's quote runs to ~955px at 390px wide, so every other slide would carry
	 * 600px of dead space beneath it and push the dots off the screen. Below
	 * 48rem the slider is switched off entirely and the quotes stack — see the
	 * media query below. Phones scroll vertically anyway.
	 */
	align-items: stretch;
}

/*
 * WHY: the scrollbar is the affordance, so it stays visible. A mouse user with
 * no trackpad has no other way to discover that the row scrolls — the dots
 * below jump between cards but do not say "this is draggable". Hiding it is the
 * usual carousel mistake and it is why so many of them need JavaScript arrows.
 */
.ias-testimonials {
	scrollbar-width: thin;
	scrollbar-color: var( --wp--preset--color--primary-soft ) transparent;
}

/*
 * WHY: one testimonial per view, full width. A quote given the whole width lands
 * as a statement rather than as one tile in a row, and the long quote reads in
 * four lines instead of fourteen.
 *
 * The cost of one-at-a-time is that the peeking card is gone, so it no longer
 * advertises itself as scrollable. The dots below and the visible scrollbar are
 * carrying that on their own — do not hide either of them.
 */
.ias-testimonials > .ias-testimonial {
	flex: 0 0 100%;
	scroll-snap-align: start;
	scroll-snap-stop: always;
}

.ias-testimonials > .ias-testimonial {
	justify-content: center;
}

/*
 * WHY: media queries are not expressible in theme.json.
 *
 * Below 48rem there is no slider. The row unwraps into a plain vertical stack:
 * no overflow, no snap, no dots, and every card its own height, so there is no
 * dead space anywhere. All three quotes are readable by scrolling the page,
 * which is what someone on a phone is doing regardless.
 */
@media ( max-width: 47.999rem ) {

	/*
	 * ⚠️ The selector needs `.wp-block-group` on it. Core's flex layout emits
	 * `.wp-block-group.is-layout-flex { display: flex }`, which beat a bare
	 * `.ias-testimonials` — the row stayed flex, `overflow-x: visible` then let
	 * the slides spill past the viewport, and the page scrolled sideways at 320
	 * and 390px. Do not lower this specificity.
	 */
	.wp-block-group.ias-testimonials {
		display: block;
		overflow-x: visible;
		scroll-snap-type: none;
		scroll-marker-group: none;
		padding-bottom: 0;
	}

	.wp-block-group.ias-testimonials > .ias-testimonial {
		margin-bottom: var( --wp--preset--spacing--50 );
	}

	.wp-block-group.ias-testimonials > .ias-testimonial:last-child {
		margin-bottom: 0;
	}
}

/*
 * WHY: CSS carousel controls — real buttons, generated by the browser, keyboard
 * operable and announced without any markup of ours. Where they are not
 * supported the container is still a scroll-snap slider that swipes; nothing is
 * lost but the chrome, which is why none of the layout depends on them.
 */
/*
 * WHY: the dots. `::scroll-marker` gives one per card, with the active state
 * tracked by the browser — no script counting scroll offsets, and each marker is
 * a focusable control that jumps to its card.
 */
@supports selector( ::scroll-marker ) {

	.ias-testimonials {
		scroll-marker-group: after;
	}

	.ias-testimonials::scroll-marker-group {
		display: flex;
		justify-content: center;
		gap: var( --wp--preset--spacing--30 );
		padding-top: var( --wp--preset--spacing--40 );
	}

	.ias-testimonials > .ias-testimonial::scroll-marker {
		content: '';
		width: 0.6rem;
		height: 0.6rem;
		border: var( --wp--custom--hairline ) solid var( --wp--preset--color--primary );
		border-radius: 50%;
		cursor: pointer;
	}

	.ias-testimonials > .ias-testimonial::scroll-marker:target-current {
		background: var( --wp--preset--color--primary );
	}
}

/* ---------------------------------------------------------------------------
 * Entrance motion
 * ------------------------------------------------------------------------ */

/*
 * WHY: theme.json cannot express keyframes, a scroll timeline, or the @supports
 * guard this needs.
 *
 * Scroll-driven CSS animation — no JavaScript, no IntersectionObserver, nothing
 * to ship or to leak. CLAUDE.md's "no JavaScript unless required" rule means
 * this either works in CSS or it does not happen.
 *
 * Everything sits inside @supports on purpose. The usual way this technique
 * breaks is a stylesheet that sets `opacity: 0` up front and relies on script
 * or an unsupported timeline to put it back — one unsupported property and the
 * page is blank. Here the default state is fully visible and the animation is
 * added only where the browser can actually run it.
 */
@keyframes ias-rise {

	from {
		opacity: 0;
		transform: translateY( var( --wp--custom--motion--rise ) );
	}

	to {
		opacity: 1;
		transform: none;
	}
}

@supports ( animation-timeline: view() ) {

	.wp-block-post-content > .wp-block-group,
	.wp-block-post-template > li,
	.ias-timeline__entry {
		animation: ias-rise var( --wp--custom--motion--duration-slow ) var( --wp--custom--motion--ease ) both;
		animation-timeline: view();
		animation-range: entry 0% entry 40%;
	}

	/*
	 * The testimonial cards are deliberately NOT in that list. They now live in
	 * a horizontal scroll container, and `view()` resolves against the nearest
	 * scrollport — which is the slider, not the page. Cards parked off to the
	 * right would sit at `entry 0%` and never fade in, and a card you have to
	 * scroll sideways to reveal would arrive invisible. The section group around
	 * them animates instead, which is the effect that was wanted anyway.
	 */

	/*
	 * The timeline is the one place the design takes a risk, so the entries
	 * resolve one after another rather than as a block. A shorter range on a
	 * per-item timeline reads as a sequence because the items are stacked —
	 * no delay chain, no nth-child arithmetic to maintain when entries change.
	 */
	.ias-timeline__entry {
		animation-range: entry 0% entry 25%;
	}

	/*
	 * The first section is already in view at load, so a view() timeline sits at
	 * the end of its range and shows it plainly. That is the correct behaviour —
	 * it must not be excluded from the rule above, or it would be the one block
	 * that never settles. The hero's own entrance is handled below on a plain
	 * time-based animation instead, because there is no scroll to drive it.
	 */
	.wp-block-post-content > .wp-block-group:first-child {
		animation-range: entry 0% entry 1%;
	}
}

/* ---------------------------------------------------------------------------
 * Hero entrance
 * ------------------------------------------------------------------------ */

/*
 * WHY: keyframes and per-child delays are not theme.json properties, and the
 * hero is above the fold where a scroll timeline has nothing to scrub against.
 *
 * Targeted structurally (`:first-child`) rather than by a class, because Home
 * holds a detached copy of the hero in the database — a class added to
 * patterns/hero.php would never reach the page that actually needs it.
 *
 * Delays are small and the whole sequence is under half a second. A hero that
 * choreographs itself for two seconds is a hero that is read once and then
 * waited through on every subsequent visit.
 */
@media ( prefers-reduced-motion: no-preference ) {

	.wp-block-post-content > .wp-block-group:first-child > * {
		animation: ias-rise var( --wp--custom--motion--duration-slow ) var( --wp--custom--motion--ease ) both;
	}

	.wp-block-post-content > .wp-block-group:first-child > :nth-child(1) {
		animation-delay: 0ms;
	}

	.wp-block-post-content > .wp-block-group:first-child > :nth-child(2) {
		animation-delay: 60ms;
	}

	.wp-block-post-content > .wp-block-group:first-child > :nth-child(3) {
		animation-delay: 120ms;
	}

	.wp-block-post-content > .wp-block-group:first-child > :nth-child(4) {
		animation-delay: 180ms;
	}
}

/* ---------------------------------------------------------------------------
 * Interaction motion
 * ------------------------------------------------------------------------ */

/*
 * WHY: none of these are theme.json properties — transforms on hover, an
 * overflow clip to contain a scaling image, and a pseudo-element nudge.
 *
 * A card image that scales slightly under the cursor tells you the whole card
 * is the target, which a border colour alone does not. The card already clips
 * to its radius; the overflow rule is what stops the scaled image escaping it.
 */
.wp-block-group.is-style-ias-card .wp-block-post-featured-image {
	overflow: hidden;
	border-radius: var( --wp--custom--radius--sm );
}

.wp-block-group.is-style-ias-card .wp-block-post-featured-image img {
	transition: transform var( --wp--custom--motion--duration ) var( --wp--custom--motion--ease );
}

.wp-block-group.is-style-ias-card:hover .wp-block-post-featured-image img {
	transform: scale( 1.03 );
}

/*
 * WHY: the excerpt "more" links end in an arrow that should move in the
 * direction it points. There is no block-level way to animate one glyph of a
 * link's text, so the arrow is shifted as a whole-element transform on the
 * link — the text is short enough that the movement reads as the arrow leading.
 */
.wp-block-post-excerpt__more-link {
	display: inline-block;
	transition: transform var( --wp--custom--motion--duration-fast ) var( --wp--custom--motion--ease ),
		color var( --wp--custom--motion--duration-fast ) var( --wp--custom--motion--ease );
}

.wp-block-post-excerpt__more-link:hover,
.wp-block-post-excerpt__more-link:focus-visible {
	transform: translateX( 0.25em );
}

/*
 * WHY: pagination arrows are the same idea, moving the way they point.
 */
.wp-block-query-pagination-previous,
.wp-block-query-pagination-next {
	display: inline-block;
	transition: transform var( --wp--custom--motion--duration-fast ) var( --wp--custom--motion--ease );
}

.wp-block-query-pagination-previous:hover {
	transform: translateX( -0.25em );
}

.wp-block-query-pagination-next:hover {
	transform: translateX( 0.25em );
}

/*
 * WHY: the footer social icons are the only place on the site where a row of
 * identical targets sits together, so a small lift is what distinguishes the
 * one under the cursor. theme.json has no transform or transition property.
 */
.wp-block-social-link {
	transition: transform var( --wp--custom--motion--duration-fast ) var( --wp--custom--motion--ease );
}

.wp-block-social-link:hover {
	transform: translateY( -2px );
}

/* ---------------------------------------------------------------------------
 * Page transitions
 * ------------------------------------------------------------------------ */

/*
 * WHY: cross-document view transitions are a CSS at-rule with no theme.json
 * equivalent. Same-origin navigations cross-fade instead of flashing white,
 * which is the single largest perceived-speed change available here — and it
 * costs no JavaScript at all.
 *
 * Browsers without support navigate normally; there is nothing to fall back to
 * because nothing is being replaced.
 */
@view-transition {
	navigation: auto;
}

/* ---------------------------------------------------------------------------
 * Footer colophon
 * ------------------------------------------------------------------------ */

/*
 * WHY: the colophon is a Custom HTML block, so it has no block attributes at
 * all — no alignment, and no route to the `ias-meta` block style.
 *
 * That second part is the non-obvious one. `register_block_style()` emits a
 * numbered class per instance (`p.is-style-ias-meta--2`) and WordPress only
 * applies it to real core/paragraph blocks, so putting the class on a raw <p>
 * matches nothing and the line renders at body size in the body font. The same
 * theme.json tokens are applied here directly, so the two footer lines match.
 */
.ias-colophon {
	text-align: center;
	margin: 0;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: var(--wp--custom--tracking--wide);
	color: var(--wp--preset--color--muted);
}

/*
 * WHY: an inline SVG sits on the text baseline by default, which drops the
 * heart below the line of mono capitals it sits between. Nudging it up by a
 * fraction of an em puts its optical centre on the text's centre. No theme.json
 * property expresses vertical-align.
 *
 * The SVG fills with `currentColor`, so the colour is set here rather than in
 * the markup. The heart is red, at Sajid's call: against a page of cyan it
 * reads as deliberate rather than as a stray token, and it is the one place on
 * the site meant to catch the eye on its own.
 *
 * ⚠️ The red lives in theme.json `custom.heart`, NOT in the palette. A palette
 * entry would show up in every colour picker in the editor and invite someone
 * to set body text or a button in it; this red exists for one 14px glyph. It
 * still clears AA against both page backgrounds (4.74:1 on `base`, 4.55:1 on
 * `surface`) even though the heart is decorative — `aria-hidden`, with the word
 * "love" carried by the adjacent screen-reader-text span — so contrast is not
 * strictly required of it. Do not reach for a brighter red: below about #E0243B
 * it stops clearing AA, and it starts to look like an error state next to the
 * muted grey it sits in.
 */
.ias-colophon .ias-heart {
	vertical-align: -0.2em;
	margin-inline: 0.1em;
	color: var(--wp--custom--heart);
}

/* ---------------------------------------------------------------------------
 * Motion preferences
 * ------------------------------------------------------------------------ */

/*
 * WHY: media queries cannot be declared in theme.json. Motion is opt-out for
 * anyone whose OS asks for less of it.
 */
@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;
	}

	.wp-block-group.is-style-ias-card:hover,
	.wp-element-button:active,
	.wp-block-button__link:active {
		transform: none;
	}

	/*
	 * The blanket rule above collapses animation *duration*, but a scroll-driven
	 * animation is scrubbed by scroll position rather than by time, so duration
	 * is not what governs it. Detaching the timeline is what actually stops it,
	 * and the reset guarantees nothing is left mid-fade.
	 */
	.wp-block-post-content > .wp-block-group,
	.wp-block-post-content > .wp-block-group:first-child > *,
	.wp-block-post-template > li,
	.ias-testimonial,
	.ias-timeline__entry {
		animation: none !important;
		animation-timeline: auto !important;
		opacity: 1 !important;
		transform: none !important;
	}

	/*
	 * The hover transforms added for cards, arrows, and social icons. The
	 * blanket rule at the top of this block collapses their duration to
	 * near-zero, which makes them instant rather than absent — an instant jump
	 * is arguably worse than a slow one for someone who asked for less motion.
	 */
	.wp-block-group.is-style-ias-card:hover .wp-block-post-featured-image img,
	.wp-block-post-excerpt__more-link:hover,
	.wp-block-post-excerpt__more-link:focus-visible,
	.wp-block-query-pagination-previous:hover,
	.wp-block-query-pagination-next:hover,
	.wp-block-social-link:hover {
		transform: none;
	}

	/*
	 * View transitions are driven by the browser, not by these declarations, so
	 * the duration rule above does not reach them. This is the documented way to
	 * opt out of the cross-fade.
	 */
	::view-transition-group(*),
	::view-transition-old(*),
	::view-transition-new(*) {
		animation: none !important;
	}
}

/* ---------------------------------------------------------------------------
 * Share row
 * -------------------------------------------------------------------------*/

/*
 * WHY: core's outline button style is built for a call to action — heavy
 * border, contrast colour, bold label. Four of them under a post read as four
 * things the page wants you to do, when they are the quietest thing on it.
 * theme.json cannot express this: the outline style is core's, and the button
 * element styles there apply site-wide, where that weight is correct.
 *
 * The treatment matches the mono metadata used for dates and categories, so the
 * row sits with the post's other furniture rather than competing with it.
 */
.ias-share .wp-block-button__link {
	font-family: var(--wp--preset--font-family--mono);
	font-weight: 400;
	letter-spacing: var(--wp--custom--tracking--wide);
	color: var(--wp--preset--color--muted);
	border-color: var(--wp--preset--color--border);
	border-width: var(--wp--custom--hairline);
	background-color: transparent;
	transition: color var(--wp--custom--motion--duration) var(--wp--custom--motion--ease),
		border-color var(--wp--custom--motion--duration) var(--wp--custom--motion--ease);
}

/*
 * WHY: the accent belongs on intent, not on presence. Hover and focus are the
 * only moments the reader has told us they mean it.
 */
.ias-share .wp-block-button__link:hover,
.ias-share .wp-block-button__link:focus {
	color: var(--wp--preset--color--primary);
	border-color: var(--wp--preset--color--primary);
}
