/**
 * Theme Name: Toner Cable 2026
 * Author URI: https://firsttracksmarketing.com
 * Requires at least: 6.9
 * Tested up to: 6.9
 * Requires PHP: 7.2
 * Requires Plugins: ftm-blocks
 * Version: 1.9.1
 * License: GNU General Public License v2 or later
 * License URI: http://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: firsttracks
 * GitHub Theme URI: https://github.com/FirstTracks-Theme/firsttracks-theme
 */

/*
 * ── Cross-pattern micro-kit ─────────────────────────────────────────────────
 *
 * Two editorial classes the hero family shares. They live here, unscoped,
 * because G21 is answered: `ftm_enqueue_pattern_styles()` globs
 * `patterns/*​/{name}.css` unconditionally, so every pattern stylesheet loads on
 * every request (build-knowledge §1). Duplicating these rules per pattern would
 * ship three identical copies on every page for no isolation benefit.
 *
 * Why style.css and not a pattern folder: this file is enqueued on
 * `enqueue_block_assets` (front end AND editor canvas, enqueue.php:17-49) and
 * every pattern stylesheet already declares handle `ftm-style` as its
 * dependency, so the kit is guaranteed to load first and a pattern can always
 * override it without a specificity fight. A `patterns/shared/` folder would
 * load too, but ordering between sibling pattern handles is alphabetical and
 * incidental.
 *
 * Recorded in DECISIONS.md 2026-07-22. If a third consumer outside the hero
 * family appears, promote `tc-eyebrow` to a design-system typeStyle rather than
 * growing this block.
 */

/*
 * Heading highlight — G2. The comps carry unbound Figma fills (#6EE7B7 /
 * #34D399); the family unified on tertiary-300, which IS #6EE7B7 in the
 * theme's own scale and measures 8.1:1 on primary-700.
 */

.tc-highlight {
	color: var( --wp--custom--color--tertiary--300 );
}

/*
 * Eyebrow — G19. The comps run a mixed-case label-medium 500 with a 20x4 bar,
 * which is NOT the design-system `eyebrow` typeStyle (label-small, 600,
 * uppercase, 0.08em). Kept as a class rather than retuning the typeStyle;
 * candidate theme delta once T40 confirms a third usage.
 *
 * Measured off the frames at 2x, identical in all five hero frames:
 *   bar 20x4 · bar-to-text gap 9 · text 14px/20
 * The gap reads 9.0 rather than the 8 `xsmall` would give; it is written as a
 * literal because 1px on a decorative rule is not worth a token that is wrong.
 */

.tc-eyebrow {
	display: flex;
	gap: 9px;
	align-items: center;
	font-size: var( --wp--preset--font-size--label-medium, 0.875rem );
	font-weight: 500;
	line-height: 1.4286;
}

.tc-eyebrow::before {
	content: "";
	flex: none;
	width: 20px;
	height: 4px;
	background-color: currentcolor;
}

/*
 * ── Decorative globe kit ────────────────────────────────────────────────────
 *
 * ONE asset, ONE set of box mechanics, a per-pattern positioning contract.
 * Consumed by heading-banner (T40), media-stats (T35) and product-grid (T36).
 *
 * WHY IT LIVES HERE, AND WHY IT IS KEYED ON THE PATTERN ROOTS RATHER THAN A
 * `tc-globe` CLASS IN THE MARKUP.
 *
 * The mask URL, the square box, the vertical centring and the `contain` sizing
 * were written out three times, once per pattern, differing only in the numbers
 * — the F22 recurrence (DECISIONS 2026-08-18: "`--tc-icon-*` is now defined a
 * SEVENTH time… escalated from a note to an explicit maintainer ask"). This is
 * the same shape and it is answered the same way the eyebrow and logo-tile kits
 * above are answered: pattern stylesheets all declare handle `ftm-style` as a
 * dependency, so this file is guaranteed to load first and a pattern can always
 * override without a specificity fight (build-knowledge §1).
 *
 * The selector list carries the three pattern roots and NOT a class added to
 * their markup, deliberately. `/demo/` inlines a SNAPSHOT of each pattern's
 * evaluated output rather than referencing it — `tools/create-demo-page.php`
 * says so in its own header, and gary-review-fixes.md item 9 is the record of
 * what happens when a class is added to a pattern and that page is not re-run:
 * the section silently loses every rule scoped to it. A markup change here
 * would have removed the globe from `/demo/` — the page shown to the client —
 * until three separate content scripts were re-run. Keying on classes the
 * markup already carries makes the change reach every existing page, snapshot
 * or reference, with no content migration at all.
 *
 * `.tc-globe` leads the list so a future consumer can opt in by class.
 *
 * THE CONSUMER OWNS THE CONTAINER. This kit styles only the pseudo-element. The
 * band itself must already be `position: relative`, so the globe's containing
 * block is the band rather than the page, AND `overflow: hidden`, which is what
 * clips the bleed at the band's own edge — without it the globe would extend the
 * document instead of running off the section. All three consumers already set
 * both, each with its own comment saying why; a new `.tc-globe` consumer must
 * set them too.
 *
 * THE CONTRACT. Every value is a custom property set by the consuming pattern
 * on its own root; the pattern files keep the frame citations, because that is
 * where the frames are documented.
 *
 *   --tc-globe-on       `""` draws the globe; unset leaves it undrawn.
 *                       This is the breakpoint gate: `content: none` generates
 *                       no pseudo-element at all, so media-stats sets it
 *                       inside its lg media query and stays globe-less below
 *                       it (J24). heading-banner sets it on its base rule —
 *                       every width since G37 (2026-09-25).
 *   --tc-globe-size     Edge of the square the mask fills. May be a length or a
 *                       percentage; percentages resolve against the band's
 *                       WIDTH, which is what makes DECISION A's ratio rule
 *                       expressible. It sets the INLINE size only — the block
 *                       size is derived from it by `aspect-ratio`, so the box
 *                       is square whatever the band's height is. See the rule.
 *   --tc-globe-bleed    How far the square's anchored edge sits PAST the band
 *                       edge. Positive bleeds off-canvas. The band's own
 *                       `overflow: hidden` does the clipping, so the artwork
 *                       runs off the edge instead of stopping mid-band.
 *   --tc-globe-offset-y Vertical anchor, default 50% (vertically centred).
 *   --tc-globe-offset-block-end
 *                       Bottom inset, default `auto`. Set it (and set
 *                       --tc-globe-offset-y to `auto`) to anchor from the
 *                       bottom edge instead of the top.
 *   --tc-globe-shift-y  The translateY, default -50% — which is what centres
 *                       the square on --tc-globe-offset-y. `0` for a globe
 *                       placed by its edges (heading-banner since I25).
 *   --tc-globe-paint    `background-color` under the mask — per-pattern, never
 *                       normalised; each globe is coloured against its own band.
 *   --tc-globe-opacity  Layer opacity, default 1.
 *
 * SIDE. CSS cannot switch a property NAME from a variable, so the side is
 * expressed as which inset the pattern leaves at `auto`. The default is
 * inline-end (right in LTR); a left-anchored consumer sets
 * `--tc-globe-inset-start: calc( var( --tc-globe-bleed ) * -1 )` together with
 * `--tc-globe-inset-end: auto`. product-grid is start-anchored (its frames
 * bleed left, 2026-09-25); heading-banner and media-stats are end-anchored.
 *
 * A FAILED MASK URL PAINTS A SOLID SQUARE (environment-facts §4), so every
 * consumer must be verified as artwork, not by presence alone.
 */

.tc-globe::before,
.tc-heading-banner::before,
.tc-media-stats::before,
.tc-product-grid::before {
	content: var( --tc-globe-on, none );
	position: absolute;
	z-index: 0;
	inset-block-start: var( --tc-globe-offset-y, 50% );
	inset-block-end: var( --tc-globe-offset-block-end, auto );
	inset-inline-start: var( --tc-globe-inset-start, auto );
	inset-inline-end: var( --tc-globe-inset-end, calc( var( --tc-globe-bleed, 0px ) * -1 ) );

	/*
	 * SQUARE FROM THE WIDTH ALONE. `--tc-globe-size` may be a percentage, and a
	 * percentage `block-size` on an absolutely positioned box resolves against
	 * the CONTAINING BLOCK'S HEIGHT — the band — not its width. Setting both
	 * sides from the one token therefore produced a NON-SQUARE box wherever the
	 * token was fluid: 555.86 x 232.77 on heading-banner at 1440. `mask-size:
	 * contain` then fitted the square artwork to the SHORT side and centred it,
	 * so the bleed moved an empty box and the artwork landed INSIDE the band at
	 * every width on all three consumers — it never bled at all. Measured on
	 * dev before the fix, artwork right edge relative to the band edge:
	 * heading-banner −12.8 / −39.8 / −56.7 / −113.8 and media-stats +69.6 /
	 * +25.3 / +7.0 / −67.1 at 1024 / 1280 / 1440 / 1920, against bleeds that
	 * should have put all of them positive and equal to the bleed.
	 *
	 * `aspect-ratio` with an `auto` block-size derives the height from the
	 * resolved width and never consults the band's height. `inset-block-end`
	 * stays `auto`, so the box is not over-constrained and the ratio applies.
	 *
	 * The box is now TALLER than the band on every consumer, which is correct
	 * and is what the 1920 frame draws: a 741px circle in a 234px band, clipped
	 * top and bottom by the band's own `overflow: hidden`.
	 */
	inline-size: var( --tc-globe-size, 0 );
	block-size: auto;
	aspect-ratio: 1 / 1;
	transform: translateY( var( --tc-globe-shift-y, -50% ) );
	opacity: var( --tc-globe-opacity, 1 );
	pointer-events: none;
	background-color: var( --tc-globe-paint, currentcolor );

	/* stylelint-disable-next-line property-no-vendor-prefix -- mask needs the prefix pair; scoped exception per the header/media precedent */
	-webkit-mask: url( "assets/images/globe-wireframe-full.svg" ) no-repeat center / contain;
	mask: url( "assets/images/globe-wireframe-full.svg" ) no-repeat center / contain;
}

/*
 * ── Hero globe geometry ─────────────────────────────────────────────────────
 *
 * The homepage hero's globe placement, per breakpoint, as tokens — so a second
 * hero reuses the numbers instead of copying them. Consumed by:
 *   - hero-overlay   (the homepage) — a real <span>, right/bottom/top insets
 *   - heading-banner (Services, Solutions) — the kit's ::before, since I25
 *
 * The values and their frame citations are hero-overlay's (hero-overlay.css,
 * "Layer 4 — globe"); they moved here unchanged on 2026-09-30. Fixed pixel
 * sizes per breakpoint, anchored off the bottom-right corner below lg and off
 * the top-right from lg. `top` / `bottom` are `auto` where unanchored.
 *
 * Paint and opacity are NOT here: each consumer still colours its globe
 * against its own band (the kit's rule).
 */

html {
	--tc-hero-globe-size: 197.91px;
	--tc-hero-globe-right: -30.68px;
	--tc-hero-globe-top: auto;
	--tc-hero-globe-bottom: -75.89px;
}

@media (min-width: 48rem) { /* breakpoint/md */

	html {
		--tc-hero-globe-size: 519.52px;
		--tc-hero-globe-right: -80.54px;
		--tc-hero-globe-bottom: -199.21px;
	}
}

@media (min-width: 64rem) { /* breakpoint/lg */

	html {
		--tc-hero-globe-size: 1026.44px;
		--tc-hero-globe-right: -202.72px;
		--tc-hero-globe-top: -188.5px;
		--tc-hero-globe-bottom: auto;
	}
}

/*
 * ── Logo tile kit ───────────────────────────────────────────────────────────
 *
 * Shared surface for logo tiles. Consumed by:
 *   - brand-grid (TC2-T38) — dynamic, term-driven, linked
 *   - logo strip (TC2-T40) — static, manually entered, unlinked
 *
 * Geometry only. The anchor, hover affordance and grid tracks belong to the
 * consuming pattern; this owns the tile box and the logo's fit inside it. It
 * lives here rather than in brand-grid.css for the same reason the two classes
 * above do — pattern CSS loads globally and unconditionally (build-knowledge
 * §1), so a second copy in T40's stylesheet would ship both on every page.
 *
 * Frame-derived from the brand-grid homepage grid (43782:19980), and re-measured
 * off the three brand-grid frames: 220x98 / 218.67x97.4 / 140x62.4 tiles at 8/32
 * padding with a 4px radius on a neutral-50 panel, logo contained and centred.
 * The aspect ratio holds at all three breakpoints (2.2449 / 2.2543 / 2.2436), so
 * it needs no override; the horizontal 32 was confirmed by three width-limited
 * logos measuring exactly 156 / 154 / 76 inside their tiles.
 *
 * The brand-grid frames are the ONLY source for the vertical 8. This comment
 * previously also cited 43782:16448 — TC2-T40's logo strip — which does not
 * support it: that frame draws 16px vertical padding, and two different
 * horizontal insets (24 on three tiles, 32 on the other three), so it carries no
 * single padding rule to derive from. T40 therefore overrides `padding-block` on
 * its own consumer rather than bending this kit; see heading-banner.css and the
 * corrections log in build-knowledge.md.
 *
 * `:is()` takes the specificity of its most specific argument, so both
 * selectors land at (0,2,0). That is deliberate: it clears category-grid's
 * (0,1,0) `.tc-term-card__link` treatment on specificity rather than on load
 * order — style.css is every pattern stylesheet's declared dependency and
 * therefore always loads FIRST, so order would lose. The rest of that
 * collision is undone in brand-grid.css, where it is scoped and explained.
 */

:is(.tc-logo-tile, .tc-brand-grid__grid .tc-term-card__link) {
	display: flex;
	align-items: center;
	justify-content: center;
	aspect-ratio: 110 / 49; /* 220 x 98 */

	/* 8 / 32, constant at every breakpoint — the brand-grid frames do not scale
	   it. Inner box is therefore 156 x 82 at desktop. */
	padding: 0.5rem 2rem;

	/* Literal, not a token: no radius token resolves to 4px. The scale is
	   sm 6 / md 16 / pill 1.5em (verified in the merged theme.json), and
	   category-grid's card ships the same literal for the same reason. */
	border-radius: 4px;
	background-color: var( --wp--custom--color--neutral--50, #f8f9fa );
}

:is(.tc-logo-tile, .tc-brand-grid__grid .tc-term-card__link) img {
	width: auto;
	height: auto;
	max-width: 100%;
	max-height: 100%;
	object-fit: contain;
}

/*
 * The hand-off's `.tc-logo-tile__fallback` is NOT here: nothing emits it.
 * term-cards renders a logo-less term through its existing
 * `.tc-term-card__title`, so the J38 fallback is restyled in brand-grid.css
 * where that class already exists, rather than inventing a second one.
 */

/*
 * ── Section rhythm — theme override of a plugin default ─────────────────────
 *
 * Restores the design system's fluid inter-section rhythm, which has never
 * applied to any shipped pattern. Every `.page-section` on the site computed a
 * flat 24px at 320, 768 and 1920 alike.
 *
 * NOT A VARIABLE BUG — the variable resolves correctly. Two rules tie at
 * (0,1,0) and load order decides:
 *
 *   .page-section { margin-block-start: var(--ftps-space-section-rhythm) }
 *       core-blocks.css:147          — head index 71
 *   :root :where(.is-layout-constrained) > * { margin-block-start:
 *       var(--wp--preset--spacing--large) }
 *       global-styles-inline-css     — head index 97
 *
 * `:where()` contributes nothing and `:root` contributes (0,1,0), so both are
 * (0,1,0); WP's global styles are emitted later and win. The tell that this is
 * specificity and not the variable: core-blocks' sibling rule
 * `.page-section:first-child` is (0,2,0) and DOES win — the leading section
 * correctly computes 0.
 *
 * WHY (0,2,0)+ AND NOT A LATER STYLESHEET. `global-styles-inline-css` is
 * emitted at head index 97, AFTER every theme stylesheet (`ftm-style` is 80,
 * the pattern sheets are 81-96). A theme rule at (0,1,0) would lose exactly the
 * same way. Specificity is the only lever that works from here; `:root` +
 * class + `:not()` lands at (0,3,0) and mirrors WP's own selector shape.
 *
 * WHY THIS FILE. The losing rule is the PLUGIN's, so core-blocks.css is not
 * ours to edit — this is a theme override of a plugin default. It is site-wide
 * (18 patterns, three templates, /demo/, /pattern-library/), so it belongs in
 * no `patterns/` stylesheet; style.css is the theme's only site-wide sheet and
 * already carries cross-pattern rules for the same reason. It also follows the
 * logo-tile kit's precedent above: clear a (0,1,0) collision on SPECIFICITY,
 * not on load order, because order is not winnable here.
 *
 * THE VALUE — TWO OF THEM SINCE 2026-09-14 (Gary, G04). Until then this rule
 * consumed `--ftps-space-section-rhythm` unchanged (`spacing.xxxlarge`,
 * `clamp(3rem, 0.857rem + 10.714vw, 6rem)`, 48px @320 → 96px @768+), and the
 * 16px it left under the Toner boards' 112 was escalated rather than settled
 * here (docs/reports/section-rhythm.md §3d). Gary settled it: 112 between
 * page patterns, 96 for the ONE pattern sitting directly below a hero. Both
 * are what the full-page boards draw — Homepage Desktop/Tablet: 96 at the
 * hero boundary, 112 at every boundary after it; Solutions Desktop: 112
 * between sections and ~116 below the heading banner.
 *
 * ⚠ THE HEADING BANNER COUNTS AS A HERO FOR THIS RULE — SINCE 2026-09-17, AND
 * FOR THIS RULE ONLY. Until then the Solutions board's ~116 was read as "the
 * banner sits on the 112 side" and the banner was deliberately NOT matched.
 * The designer ruled otherwise on 2026-09-15: the pattern directly below the
 * heading banner takes the 96. The selector below matches it by the group it
 * wraps (`:has(> .tc-heading-banner)`), NOT by `.page-section-cta` — three
 * other patterns are `layout: cta` (contact-form, newsletter-signup,
 * page-header) and none of them is a hero. Nothing else about the banner
 * changes: it stays `layout: cta`, its own top margin, scheme and markup are
 * untouched. Reaches /services/ and /demo/ (where the banner is itself the
 * hero-adjacent section, and the pattern under it now takes 96 too);
 * /pattern-library/ has a note between the banner and the next section, so
 * `+` does not match there. DECISIONS.md 2026-09-17 §1.
 *
 *   --tc-space-section-rhythm     48 @320 → 112 @768+   between patterns
 *   --ftps-space-section-rhythm   48 @320 →  96 @768+   hero → next pattern
 *
 * The theme token is the DS's `huge` slope (14.286vw — it reaches its ceiling
 * at 768, exactly where `xxxlarge` does) with the floor held at the corpus's
 * universal 48: the Homepage Smartphone board draws 48 between patterns at
 * 320, which is what renders today. `xxxlarge` itself is NOT overridden —
 * 17 pattern sheets reason about it as a 48→96 clamp and would all move.
 *
 * The hero rule keeps the DS token as-is, so the hero boundary renders exactly
 * what it rendered before this change; only the other gaps grew.
 * `.page-section-hero` is the class `ftm/page-section` emits for
 * `layout: hero` — the four hero patterns (basic, split, overlay,
 * fullbleed-gradient) and nothing else. `+` means DIRECTLY below: a note or
 * heading in between (as on /pattern-library/) breaks the adjacency and the
 * pattern takes the 112, which is right — it is no longer directly below the
 * hero.
 *
 * ⚠ NOT SETTLED: the Homepage Smartphone board draws 40, not 48, at the hero
 * boundary at 320. Gary's ruling names 96 and 112 only; the 40 was not in it
 * and is not adopted — the DS floor stays. DECISIONS.md 2026-09-15.
 *
 * TWO EXCLUSIONS, both deliberate:
 *   - `:first-child` — the leading section keeps a 0 top margin. Left to
 *     core-blocks' own (0,2,0) `.page-section:first-child` rather than
 *     restated, so there is one owner of that reset.
 *   - `.page-section-cart-notice` — cart-progress/style.css strips section
 *     chrome from the WooCommerce threshold notice on purpose ("strip the page
 *     chrome ... the framework applies to every .page-section"). Its rule is
 *     (0,1,0) and only holds today on load order, so a (0,3,0) rule here would
 *     silently undo it. Excluded rather than out-specified.
 *
 * `:not()` takes the specificity of its most specific argument, so the whole
 * selector is (0,3,0) regardless of how many exclusions the list grows.
 *
 * Recorded in DECISIONS.md 2026-07-31 (the cascade) and 2026-09-15 (the
 * values, G04).
 */

:root {
	--tc-space-section-rhythm: clamp(3rem, 0.143rem + 14.286vw, 7rem);
}

:root .page-section:not(.page-section-cart-notice, :first-child) {
	margin-block-start: var( --tc-space-section-rhythm );
}

/*
 * (0,5,0) — outranks the rule above on specificity, not on order. Same
 * exclusion list minus `:first-child`, which a `+` sibling can never be.
 *
 * `:is()` rather than a comma list, on purpose: `:is()` is a FORGIVING
 * selector list, so in a browser without `:has()` the banner argument is
 * dropped and the hero half still applies, where an invalid member of a plain
 * comma list drops the whole rule. `:has()` is already a hard dependency of
 * this build (see the leading-gap rule below), so this is belt-and-braces.
 * Specificity is the most specific argument's: `.page-section-cta:has(>
 * .tc-heading-banner)` is (0,2,0), so the whole selector is (0,5,0).
 */
:root
	:is(
		.page-section-hero,
		.page-section-cta:has( > .tc-heading-banner )
	)
	+ .page-section:not( .page-section-cart-notice ) {
	margin-block-start: var( --ftps-space-section-rhythm );
}

/*
 * ── The two ENDS of that rhythm — header -> first section, last section ->
 *    footer ────────────────────────────────────────────────────────────────
 *
 * The rule above governs the gaps BETWEEN sections. It cannot reach the two
 * gaps at the ends of the run, because those are not between sections at all:
 * they are between `.wp-site-blocks`' three children, and WordPress spaces
 * those with its own blockGap —
 *
 *   :where(.wp-site-blocks) > * { margin-block-start:
 *       var(--wp--preset--spacing--large) }
 *
 * — which resolves to a flat 24px on `<main>` and on `<footer>` alike. That is
 * the same flat 24 the section-rhythm block above was written to get rid of,
 * arriving one level up the tree where that fix cannot see it, and it is wrong
 * in OPPOSITE DIRECTIONS at the two ends. Both were reported by a design
 * review; they are one cause.
 *
 * TOP — 24 too many. Measured on /demo/ at 1440: header bottom 206, hero top
 * 230. The system already has an answer here and it is ZERO: core-blocks ships
 * `.page-section:first-child { margin-block-start: 0 }` at (0,2,0) precisely so
 * a leading full-bleed band butts the header, and the exclusion note above
 * leaves that reset to it as the single owner. `<main>`'s own margin defeats it
 * from outside — the section correctly computes 0 and then the whole of `main`
 * is pushed down 24 regardless, opening a white strip across a full-bleed navy
 * hero. Restoring 0 here honours the reset rather than competing with it.
 *
 * BOTTOM — 24 far too few. Every page ends the same way: the last section's box
 * has `padding-bottom: 0` (checked on /demo/, /pattern-library/, /user-guides/,
 * /find-a-rep/ and /contact/ — all five), so 24px is the entire separation
 * between the last row of content and the footer band. On /demo/ that is a row
 * of brand tiles stopping 24px short of the navy. Sections are 96px apart at
 * this width; there is no reason the footer is four times closer than the
 * section above it, and `--ftps-space-section-rhythm` is exactly the value
 * already answering that question everywhere else.
 *
 * NO NEW NUMBER IS INVENTED. The footer takes the theme's section-rhythm
 * token, 48px @320 -> 112px @768 and above (G04, 2026-09-14) — the same one
 * the between-sections rule uses, because the full-page boards draw the last
 * section -> footer gap at 112 on Homepage Desktop, Homepage Tablet and
 * Solutions Desktop alike, no different from any other boundary. Before G04
 * it took the DS token (48 -> 96) for the same reason. The header end takes
 * 0, which is core-blocks'.
 *
 * SPECIFICITY IS NOT A FIGHT HERE, unlike the block above. WP's rule is
 * `:where(.wp-site-blocks) > *`: `:where()` contributes nothing and `*`
 * contributes nothing, so it lands at (0,0,0) and any class selector beats it.
 * Both rules below are (0,1,0) and need no `:root` prefix.
 *
 * Scoped to the direct children of `.wp-site-blocks` so this cannot reach a
 * nested `<main>` or `<footer>` inside pattern content.
 *
 * ── ⚠ THE BOTTOM RULE'S PREMISE HAS ONE KNOWN EXPIRY — 2026-09-01 ───────────
 *
 * The "all five" measurement above — that every page's last section has
 * `padding-bottom: 0`, so the token supplies the WHOLE separation — was true
 * when taken and is no longer true of `/find-a-rep/`, one of the five. Its last
 * section is now the Operations band, which carries its own 48/80/112 of bottom
 * padding AND paints a background, so the token ADDED to that padding and drew
 * 96px of white between two coloured bands. Measured, and fixed at the pattern:
 * `patterns/rep-directory/rep-directory.css`, "WHEN THIS BAND IS THE PAGE'S
 * LAST SECTION, IT BUTTS THE FOOTER".
 *
 * **Nothing here changes.** The token is right for a page that ends in plain
 * content, which is still every other page. What is worth carrying forward is
 * the general shape: **a trailing full-bleed band wants this margin at 0**, for
 * the same reason a LEADING one wants `main`'s at 0 immediately below. If a
 * second pattern ever ends a page with a painted band, it needs the same rule
 * and it belongs in that pattern, not here — the site frame should not have to
 * enumerate them.
 */

.wp-site-blocks > main {
	margin-block-start: 0;
}

.wp-site-blocks > footer {
	margin-block-start: var( --tc-space-section-rhythm );
}

/*
 * ── ...and the page that does NOT open with a band ──────────────────────────
 *
 * `main { margin-block-start: 0 }` above is right for a page whose first
 * section is a full-bleed band — /demo/'s navy hero, /services/'s pale heading
 * banner — and WRONG for a page that opens with plain content. /contact-us/
 * regressed to a measured 0: the "Our Locations" eyebrow's box top sat at 206
 * and the header's bottom edge sat at 206, so the first line of the page ran
 * straight into the nav rule. /pattern-library/ and every legacy page do the
 * same thing.
 *
 * THE VALUE IS NOT A NEW ONE — IT IS THIS REPO'S, DECIDED ONCE ALREADY.
 * `patterns/blog-archive/blog-archive.css` carries the identical problem and
 * the identical answer, with its own derivation (D1/D4): "the 80 below the
 * nav ... Both blog boards draw it (first content at y 285 against a 205 nav)",
 * dropping to 48 below `breakpoint/md` because no mobile board exists to say
 * otherwise and 48 is the corpus's own mobile section rhythm (the floor of
 * `spacing--xxxlarge`).
 *
 * `docs/design/contact-us/desktop.png` lands on the SAME TWO NUMBERS from a
 * different board: nav bottom row 205, first ink row 289, and the eyebrow's
 * 14px glyph sits 4px inside its own 20px line box — so its box top is 285 and
 * the gap is 285 - 205 = 80. Two boards, independently, 80.
 *
 * WHY `padding` ON `.entry-content`, AND NOT `margin` ON `main`:
 *
 *   - A margin on `main` would collapse straight back out through it (`main`
 *     has no padding or border and is not a flow root), and it would move
 *     `main`'s own box — so the "header butts main" measurement above would
 *     stop meaning what it says. Padding here leaves that 0 intact and moves
 *     only the content.
 *   - `.entry-content` is the POST CONTENT — the page a person authored. The
 *     blog and shop archives assemble their leading section from the TEMPLATE
 *     instead and have no `.entry-content` at all, so they are untouched by
 *     this rule and keep the spacing they already own. That is deliberate and
 *     it is the reason the rule is scoped here rather than to `main`: measured
 *     at 1440, `.tc-blog-archive` already pads itself 80 and /shop/ already
 *     clears the nav, and a blanket rule would have doubled both.
 *
 * THE EXCLUSION IS THE DESIGN SYSTEM'S OWN DEFINITION OF "BAND". A section
 * paints exactly when it carries a scheme class or `has-background` —
 * `.page-section[class*=ftm-scheme-] { background-color: var(--ftm-scheme-bg) }`
 * in page-section's stylesheet, and `.tc-heading-banner[class*='ftm-scheme-']`
 * for the banner. Two levels are tested because the band is one level down on
 * /services/, where a transparent `.page-section-cta` wraps a painted
 * `.tc-heading-banner`.
 *
 * `:has()` IS ALREADY A HARD DEPENDENCY OF THIS BUILD — page-section's own
 * stylesheet uses it for its layout (`.wp-block-columns:has(> .ftps-col-40)`),
 * so this adds no new support floor. It is Baseline widely available
 * (Chrome 105, Safari 15.4, Firefox 121). In a browser without it the whole
 * selector is invalid and the rule is dropped, which degrades to exactly
 * today's behaviour rather than to something broken.
 *
 * `:not()` takes the specificity of its most specific argument, so this lands
 * at (0,4,1) and competes with nothing.
 *
 * ⚠ THE RAMP IS A CUSTOM PROPERTY AND THE SELECTOR APPEARS ONCE, which is a
 * deliberate departure from the blog-archive rule this borrows its numbers
 * from — that one restates its (short) selector inside the media query. This
 * selector is four lines of `:has()` and restating it would mean two copies to
 * keep in step, where editing one and forgetting the other fails silently at
 * exactly one breakpoint. The two rules that set the value carry no `:has()` at
 * all, so they are trivially readable, and the custom property is inert on any
 * `.entry-content` the padded rule does not match.
 */

.wp-site-blocks > main > .entry-content,
.tc-page-document__inner {
	--tc-leading-gap: 3rem; /* 48 — derived, matches blog-archive D1/D4 */
}

@media (min-width: 48rem) { /* breakpoint/md */

	.wp-site-blocks > main > .entry-content,
	.tc-page-document__inner {
		--tc-leading-gap: 5rem; /* 80 — the frame's own value */
	}
}

.wp-site-blocks
	> main
	> .entry-content:not(
		:has( > :first-child:is( [class*='ftm-scheme-'], .has-background ) ),
		:has(
				> :first-child
					> :first-child:is( [class*='ftm-scheme-'], .has-background )
			)
	) {
	padding-block-start: var( --tc-leading-gap );
}

/*
 * ⚠ A SCREEN-READER-ONLY HEADING MUST NOT OPEN A BLOCK GAP — review-batch-7.
 *
 * `includes/page-title-fallback.php` prepends an `<h1 class="screen-reader-text">`
 * to the surfaces whose frames draw no page heading (`/contact-us/`, `/shop/`,
 * the two verification fixture pages). The heading is `position: absolute` and
 * clipped to 1x1, so it paints nothing and occupies nothing — but WordPress's
 * block gap is written as `:root :where(.is-layout-flow) > *` with a
 * `:first-child` exemption, and merely EXISTING as a preceding sibling demotes
 * the real content out of `:first-child`.
 *
 * MEASURED, not predicted: /contact-us/ moved DOWN 24px at every width the
 * moment the fallback shipped — `.entry-content` picked up
 * `margin-block-start: var(--wp--preset--spacing--large)` where it previously
 * had none. `e2e/templates-verify.spec.ts`'s leading-gap ceiling is what caught
 * it (48 expected, 72 measured at 320).
 *
 * ⚠ (0,1,1), AND IT HAS TO BE. WordPress's rule is (0,1,0) — `:root` scores,
 * `:where()` does not, `*` does not — and `global-styles` prints AFTER this
 * file, so a tie would lose. The `h1` in the selector is doing specificity work
 * as well as scoping: the theme's other `.screen-reader-text` uses are a `label`
 * and a `span` inside forms, and neither is a flow sibling of anything whose
 * spacing matters.
 */
h1.screen-reader-text + * {
	margin-block-start: 0;
}

/*
 * ── Page with title (`templates/page-document.html`) ────────────────────────
 *
 * Review 2026-09-29: the legal pages, the credit application and the Video
 * Library had no VISIBLE H1. This template draws `core/post-title` as the H1.
 *
 * SCALE is the interior banner title's (heading-banner.css
 * `.tc-heading-banner__title`, the "Simplify Your Content Delivery
 * Infrastructure" line on /services/): 26 @320 -> 51 at breakpoint/md, 700,
 * 60/51, -2%. Restated rather than taken from `has-heading-large-font-size`
 * for the reason heading-banner.css gives — the shipped fluid preset does not
 * reach 51 until far past 768. No band, no eyebrow, left-aligned on the frame.
 *
 * SPACING: the header -> title gap is the site's leading gap (48 / 80), the
 * `--tc-leading-gap` ramp declared above — the same number every other
 * interior page sits at. The same value closes the page above the footer.
 *
 * (0,2,0) and (0,3,0): core's flow/constrained gap is `:where()`-wrapped
 * (0,1,0) at best and `global-styles` prints after this file (§5a).
 */
.tc-page-document .tc-page-document__inner {
	padding-block: var(--tc-leading-gap);
}

.tc-page-document .tc-page-document__title {
	margin-block: 0;
	font-size: clamp(1.625rem, 0.5089rem + 5.5804vw, 3.1875rem);
	font-weight: 700;
	line-height: 1.1765; /* 60/51 */
	letter-spacing: -0.02em;
}

.tc-page-document .tc-page-document__title + .entry-content {
	margin-block-start: var(--wp--preset--spacing--xlarge, 2.5rem); /* 40 */
}

/*
 * ── Video Library grid (`/user-videos/`, `tools/create-user-videos-page.php`) ─
 *
 * Gary, 2026-10-01: 80 between rows, 16 from each video to its title, at
 * every width.
 *
 * ROWS: the block can hold only ONE gap. A split row/column `blockGap` makes
 * core (WP 7.0.2) paste both values into its column formula and the grid
 * collapses to one column (DECISIONS 2026-09-30, the seed script's header). So
 * the block keeps its single `xlarge` and THIS rule decides what renders: it
 * beats core's per-instance `.wp-container-core-group-is-layout-*` gap
 * ((0,1,0), printed after this file) at (0,2,0). `column-gap` is restated as
 * the same `xlarge` the column formula subtracts, so the two cannot drift.
 *
 * TITLE: the 32 was the theme's heading `margin-top: 2rem` collapsing with the
 * embed figure's `1em` bottom margin (16.1 / 16.8 / 18 at 390 / 768 / 1440).
 * Both have to move for 16 to be 16 at every width.
 */
.wp-block-group.tc-video-library {
	row-gap: 5rem; /* 80 */
	column-gap: var(--wp--preset--spacing--xlarge, 2.5rem); /* 40, the formula's gap */
}

.tc-video-library .tc-video-library__item > .wp-block-embed {
	margin-block-end: 0;
}

.tc-video-library .tc-video-library__item > .wp-block-heading {
	margin-block-start: var(--wp--preset--spacing--normal, 1rem); /* 16 */
}

/*
 * ── Post card kit ───────────────────────────────────────────────────────────
 *
 * The one surface the two blog templates share. Consumed by:
 *   - blog-archive — 3-up at 1400 (frame 43782:14845)
 *   - blog-post    — 2-up related grid at 1000 (frame 43782:14510)
 *
 * Markup is `patterns/hidden/post-card.php`; read the reasoning there for why
 * it is a hidden pattern rather than a template part.
 *
 * WHY THIS FILE. Three reasons, and the first is the binding one:
 *
 *  1. A hidden pattern CANNOT carry a stylesheet. The glob is
 *     `glob(patterns/*, GLOB_ONLYDIR)` loading `{name}/{name}.css`, so anything
 *     in `patterns/hidden/` would need to be `patterns/hidden/hidden.css` —
 *     one sheet for four unrelated patterns.
 *  2. Two consumers, both of them pattern stylesheets. Pattern CSS is
 *     glob-loaded unconditionally (§1), so duplicating these rules would ship
 *     both copies on every page for no isolation benefit — the same finding
 *     that put `.tc-eyebrow` and `.tc-logo-tile` here.
 *  3. Ordering between sibling pattern handles is alphabetical and incidental.
 *     `ftm-pattern-blog-archive` loads BEFORE `ftm-pattern-blog-post`, so a kit
 *     living in the first could not be overridden by the second on load order.
 *     style.css is every pattern sheet's declared dependency and always loads
 *     first, so both consumers can override it — and one of them has to (D2).
 *
 * SPECIFICITY. Every rule is (0,2,0) or higher, deliberately and not out of
 * habit. WordPress's own layout rules — `:root :where(.is-layout-flow) > *`,
 * which sets the 24px block gap — are (0,1,0) and are emitted at head index 97,
 * AFTER every theme stylesheet. A (0,1,0) rule here would lose on load order
 * exactly the way the section-rhythm rule above does. This is the same lesson,
 * applied before it bites rather than after.
 *
 * Frame numbers, from get_design_context on 43782:14845 and 43782:14503:
 *   card gap 24 · media 440x289 r4 · meta gap 8 · pill p8 r0/0/0/8 secondary-200
 *   title Title/Large 27/32 700 -2% capitalize, black
 */

.tc-post-card.wp-block-group {
	display: flex;
	flex-direction: column;
	gap: var( --wp--preset--spacing--large, 1.5rem ); /* 24 */
}

.tc-post-card__meta.wp-block-group {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var( --wp--preset--spacing--xsmall, 0.5rem ); /* 8 */
}

/* Beats `:root :where(.is-layout-flow) > *`'s block gap at (0,2,0). Without it
   the flex `gap` above and a 24px margin would both apply and compound. */

.tc-post-card.wp-block-group > *,
.tc-post-card__meta.wp-block-group > * {
	margin-block: 0;
}

/* ── Media ── */

.tc-post-card .tc-post-card__media {
	/*
	 * 440/289 = 0.65682. The related card draws 480x315 (0.65625) and the single
	 * post's own featured image 1000x656 (0.656) — within 0.06px on a 1000-wide
	 * box, so ONE ratio serves all three rather than three near-identical ones.
	 */
	aspect-ratio: 440 / 289;

	/*
	 * Literal, not a token: no radius token resolves to 4px. The scale is
	 * sm 6 / md 16 / pill 1.5em, verified in the merged theme.json. Same literal
	 * and same reason as `.tc-logo-tile` and category-grid's card.
	 */
	border-radius: 4px;
	overflow: hidden;
}

.tc-post-card .tc-post-card__media-link {
	display: block;
	width: 100%;
	height: 100%;
}

.tc-post-card .tc-post-card__img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * The no-thumbnail panel. This is not an edge case here: 11 of the 15 published
 * posts have no featured image, and `core/post-featured-image` renders NOTHING
 * at all in that state — which is why the media is a theme block at all. See
 * blocks/post-card-media/render.php.
 *
 * D2 — THE TWO FRAMES DISAGREE ON THIS FILL. The archive board draws
 * secondary-50 (#F0F9FF) on all seven of its placeholder cards; the single-post
 * board draws neutral-100 (#E9ECEF) on both of its. Almost certainly a slip.
 * The kit ships the archive's value because it is the larger sample, and
 * blog-post.css scopes the other — which is precisely the shape the
 * `.tc-logo-tile` precedent predicts: extraction is still right, and the
 * minority consumer still needs a scoped override.
 */

.tc-post-card .tc-post-card__media--empty {
	display: flex;
	align-items: center;
	justify-content: center;
	background-color: var( --wp--custom--color--secondary--50, #f0f9ff );
}

/*
 * ⚠ THE CENTRING ABOVE NEVER REACHED THE MARK, AND THAT ONLY BECAME VISIBLE
 * WHEN THE ASSET SHIPPED. Measured on /category/news/ before this rule, at
 * 1440 / 768 / 390: the mark sat at `left: 0, top: 0` in every panel, with
 * 186.2 / 139.7 of slack on the other two edges at 1440.
 *
 * The panel is the flex container, but its only child is
 * `.tc-post-card__media-link` — an `<a>` at `width: 100%; height: 100%` (the
 * rule above, which the LINKED state needs). A flex container centres its
 * ITEMS, and this item already fills it, so there is nothing left to centre;
 * the mark is then an ordinary block at the anchor's top-left corner.
 *
 * So the anchor has to centre too. `43782:14828` draws the panel as
 * `flex items-center justify-center` with the mark `object-contain` inside
 * `px-[96px] py-[72px]`, which is what `.tc-post-card__placeholder-img`'s
 * percentages already express — the geometry was right and only its origin
 * was wrong.
 *
 * (0,3,0) against the link rule's (0,2,0), so this wins on specificity in
 * either source order (§5a). Scoped to `--empty` deliberately: in the image
 * state the `<img>` is `width/height: 100%` and centring it would be a no-op
 * that still had to be reasoned about by the next reader.
 *
 * `isLink: false` consumers (the single post's hero) have no anchor at all,
 * so the panel's own centring reaches their mark directly and this rule
 * simply does not match.
 */
.tc-post-card .tc-post-card__media--empty .tc-post-card__media-link {
	display: flex;
	align-items: center;
	justify-content: center;
}

.tc-post-card .tc-post-card__placeholder-img {
	/*
	 * Percentages, not the frames' flat 96/72 padding. The frames draw that
	 * constant at BOTH card widths (248x145 inside 440x289, 288x171 inside
	 * 480x315), which cannot hold below about a 300px card — and there is no
	 * mobile board to say what should happen there. Scaling reproduces the
	 * archive card, the larger consumer, exactly: 248/440 and 145/289.
	 *
	 * ⚠ "Nothing renders here yet — the theme ships no placeholder asset, which
	 * is designer item D3" was true until 2026-09-01, when `0136db2a` shipped
	 * `assets/images/post-card-placeholder.png` and D3 closed with a derived
	 * asset. 10 of the 11 cards on /category/news/ draw it. The percentages
	 * below have been paying since; what had never been looked at is WHERE the
	 * mark landed, which is the rule above.
	 */
	width: 56.36%;
	height: 50.17%;
	object-fit: contain;
}

/* ── Pill ── */

.tc-post-card .tc-post-card__pill {
	display: inline-flex;
	align-items: center;
	justify-content: center;

	/*
	 * 4.5 + 16 + 4.5 = 25, the boards' height (G34, 2026-09-17). The frame
	 * declares 8 and draws 25 because Figma trims the label's line box to its
	 * cap height; the designer wants the browser to match, so the 7 comes out of
	 * the vertical padding. Same recipe, same day, on the blog-post restatement,
	 * both product badges and the category-grid count — product-grid.css has
	 * the full account, including why `text-box-trim` was rejected.
	 */
	padding-block: 4.5px;
	padding-inline: var( --wp--preset--spacing--xsmall, 0.5rem ); /* 8 */
	border-radius: 0 0 0 8px; /* frame: bottom-left only */
	background-color: var( --wp--custom--color--secondary--200, #bae6fd );
	color: var( --wp--custom--color--base--black, #0a205b );

	/*
	 * Literals. NO `.ftm-type-*` role fits: `caption` is label-small at 400,
	 * `eyebrow` is label-small at 600 but uppercase with 0.08em tracking, and
	 * the frame binds Inter BOLD 12/16 with 0.2% tracking. Identical values to
	 * `.tc-term-card__count` in category-grid.css, which reached the same place
	 * from the same frames.
	 */
	font-size: var( --wp--preset--font-size--label-small, 0.75rem ); /* 12, flat */
	font-weight: 700;
	line-height: 1.3333; /* 16 */
	letter-spacing: 0.002em; /* frame: 0.024px on 12px */
}

.tc-post-card .tc-post-card__pill a {
	color: inherit;
	text-decoration: none;
}

/*
 * C1 interim — one pill, whatever the post's category count. `core/post-terms`
 * emits every term plus a `, ` separator into the one element. Hidden rather
 * than trimmed server-side so the decision stays reversible in one place.
 */

.tc-post-card .tc-post-card__pill a:not(:first-of-type),
.tc-post-card .tc-post-card__pill .wp-block-post-terms__separator {
	display: none;
}

/* ── Title ── */

.tc-post-card .tc-post-card__title {
	/*
	 * `.ftm-type-title` supplies family, size, line-height and weight and gets
	 * two things wrong for these frames, so only those two are restated:
	 *
	 *  - letter-spacing: the role emits 0; every heading on these boards is
	 *    bound at -2% (frame: -0.54px on 27px). Eleven shipped pattern
	 *    stylesheets already restate exactly this.
	 *  - text-transform: no role sets it at all, and the frames capitalise —
	 *    the board renders "Update On Tariffs And Pricing" where the layer is
	 *    "Update on Tariffs and Pricing".
	 */
	letter-spacing: -0.02em;
	text-transform: capitalize;
	color: var( --wp--custom--color--base--black, #0a205b );
}

/*
 * The linked title renders primary-600 by default — measured rgb(18, 59, 158)
 * on /category/news/ before this build. The frames draw it in the same black as
 * an unlinked one.
 */

.tc-post-card .tc-post-card__title a {
	color: inherit;
	text-decoration: none;
}

.tc-post-card .tc-post-card__title a:hover,
.tc-post-card .tc-post-card__title a:focus-visible {
	text-decoration: underline;
}

/* J32's shape — no hover treatment beyond focus visibility on the card media. */

.tc-post-card .tc-post-card__media-link:focus-visible,
.tc-post-card .tc-post-card__title a:focus-visible {
	outline: 2px solid var( --wp--custom--color--secondary--600, #057db9 );
	outline-offset: 2px;
}

/*
 * ── Core button defaults: the house button (2026-10-01) ─────────────────────
 *
 * A core/button authored in page content (the Credit Application's Download)
 * drew primary-600, 18px, 16/32 padding and a 4px corner, while every pattern
 * button measures secondary-600, 15/24/500, 12/16 and 2px. theme.json's
 * `core/button` now holds the house values and is the source of truth; the
 * Global Styles panel and the editor read it.
 *
 * This block restates only what the design system's `elements.css` ALSO sets,
 * because those properties are a (0,1,0) tie with theme.json and the winner
 * depends on print order: on a page carrying a core block style variation,
 * `global-styles` prints before `elements.css` and the DS's primary fill,
 * hairline border and 16px radius come back (build-knowledge, the
 * footer-cta-parity report). style.css always prints after `elements.css`, so
 * the same specificity wins here on every page. Hover cannot live in
 * theme.json at all: its `:hover` emits at (0,1,0) and the DS's darken-the-
 * accent hover is (0,2,0), which would turn a secondary button navy.
 *
 * Hover is secondary-800, the site's filled-button hover (mini cart, empty
 * cart, footer CTA, product cards) — owner's call on 2026-10-01; the pattern
 * CTAs show no fill change on hover only because their own rules pin it.
 *
 * `:where()` keeps this at the DS's specificity, so every pattern rule
 * ((0,2,0) and up, printed later) still wins and pattern buttons do not move.
 * `main` keeps the footer CTAs out (they are core Buttons too) and
 * `.editor-styles-wrapper` is its stand-in in the editor canvas, which has no
 * `main`;
 * `.wp-block-buttons > .wp-block-button >` keeps WooCommerce's product
 * buttons out, as in the tap-target block below.
 */
:where(:is(main, .editor-styles-wrapper) .wp-block-buttons > .wp-block-button:not(.is-style-outline)) > .wp-block-button__link {
	border: 0;
	border-radius: 2px;
	background-color: var(--wp--custom--color--secondary--600, #057db9);
	color: var(--wp--custom--color--base--white, #fff);
}

:where(:is(main, .editor-styles-wrapper) .wp-block-buttons > .wp-block-button:not(.is-style-outline)) > .wp-block-button__link:hover,
:where(:is(main, .editor-styles-wrapper) .wp-block-buttons > .wp-block-button:not(.is-style-outline)) > .wp-block-button__link:active {
	background-color: var(--wp--custom--color--secondary--800, #075985);
	color: var(--wp--custom--color--base--white, #fff);
}

/*
 * Outline: the media patterns' outline on a light band — 1px secondary-600
 * border and label, 11/16 padding so the box stays 48. At (0,3,0) to meet the
 * DS's outline rule, which paints the primary accent. Hover fills
 * secondary-600 with a white label, the DS outline's own behaviour in the
 * house colour.
 */
:where(:is(main, .editor-styles-wrapper) .wp-block-buttons) > .wp-block-button.is-style-outline > .wp-block-button__link {
	padding-block: calc(var(--wp--preset--spacing--small, 0.75rem) - 1px);
	border: 1px solid var(--wp--custom--color--secondary--600, #057db9);
	border-radius: 2px;
	background-color: transparent;
	color: var(--wp--custom--color--secondary--600, #057db9);
}

:where(:is(main, .editor-styles-wrapper) .wp-block-buttons) > .wp-block-button.is-style-outline > .wp-block-button__link:hover {
	background-color: var(--wp--custom--color--secondary--600, #057db9);
	color: var(--wp--custom--color--base--white, #fff);
}

/*
 * Tracking, both styles. Not in theme.json: its block selector
 * (`.wp-block-button .wp-block-button__link`) also reaches the footer CTAs and
 * WooCommerce's product buttons, which pin everything else but not tracking,
 * and widened them 2-3px (measured 2026-10-01). And at (0,2,0), one step up
 * from the rules above, because theme.json's `elements.button` emits
 * `letter-spacing: inherit` at (0,1,0) in `global-styles`, which prints after
 * this file on most pages. Patterns still win: theirs print later.
 */
:where(:is(main, .editor-styles-wrapper) .wp-block-buttons > .wp-block-button) > .wp-block-button__link.wp-element-button {
	letter-spacing: 0.2px;
}

/*
 * ── Phone tap targets: core buttons (QA batch F, 2026-09-28) ────────────────
 *
 * The round-three sweep found core buttons drawn 40 tall on phones with
 * nothing to reach 44: "Back to homepage" on the 404, and the hero buttons
 * ("Speak with Our Team" on /engineering/; hero-overlay and hero-basic draw the
 * same 40, per mobile frame 43391:28219). A transparent ::before, centred,
 * takes each to at least 44×44 and draws nothing. A button already 44 or more
 * is untouched (`max()` resolves to its own box).
 *
 * `.wp-block-buttons > .wp-block-button >` limits this to the core Buttons
 * block. WooCommerce's product buttons carry `.wp-block-button__link` too, and
 * two of them already draw with ::before (the card kit's hit box and the PDP
 * cross-sell "+"), so they must not match. No core button in this theme uses
 * ::before; the heroes' arrow is ::after.
 */
@media (max-width: 47.9375rem) {
	.wp-block-buttons > .wp-block-button > .wp-block-button__link {
		position: relative;
	}

	.wp-block-buttons > .wp-block-button > .wp-block-button__link::before {
		content: "";
		position: absolute;
		inset-block: min(0px, calc(50% - 22px));
		inset-inline: min(0px, calc(50% - 22px));
	}
}

/*
 * ── Phone tap target: the added-to-cart notice's "View cart" (QA batch F, N6) ─
 *
 * 64×21 on the PDP after a non-AJAX add and on /cart/ (the round-three review:
 * 968 of the 1,936 points of its square). Global, not in woo-templates.css,
 * because that file is only enqueued on the cart, checkout and account, and
 * the PDP's notice is the same banner. ::before, because Woo's classic sheet
 * owns `.wc-forward::after` for its arrow. 16 of banner padding and a line of
 * text sit round it: nothing to overlap.
 */
@media (max-width: 47.9375rem) {
	.wc-block-components-notice-banner__content > a.wc-forward {
		position: relative;
	}

	.wc-block-components-notice-banner__content > a.wc-forward::before {
		content: "";
		position: absolute;
		inset-block: min(0px, calc(50% - 22px));
		inset-inline: min(0px, calc(50% - 22px));
	}
}

/*
 * ── Phone tap target: the Klaviyo sign-up popup's close (2026-09-29) ─────────
 *
 * VENDOR MARKUP. klaviyo.js draws this at runtime; nothing here renders it,
 * and Klaviyo does not promise to keep the class. Measured on tonerdev in
 * Chrome, 2026-09-28, sign-up form UBNEsJ ("Get 10% Off") at 390:
 * `button.klaviyo-close-form` (aria-label "Close dialog") 27.9×27.9, inside a
 * `div[role=dialog]` at z-index 90000, nothing of ours over it. The only
 * Klaviyo control taken out of the C9 third-party exception (DECISIONS.md,
 * 2026-09-29). If the form is rebuilt or Klaviyo renames the class, this
 * silently stops applying: re-measure on tonerdev.
 *
 * THE FORM IS SCALED. Klaviyo shrinks the panel to fit the phone (0.796 at
 * 390), and the pseudo-element is inside that transform, so a 44 box would
 * land at 35. The box is written in the button's own unscaled pixels: 68,
 * which is 54 on screen at 0.796 and still 44 at 0.647 — where the scale
 * falls at 320 if Klaviyo's fit is linear in width (not measured; 390 is the
 * only width on record).
 *
 * POSITION IS KLAVIYO'S. The button is placed by Klaviyo's own styles; the
 * `:where()` rule has no specificity, so it only supplies `relative` if
 * Klaviyo leaves the button unpositioned, and never overrides where Klaviyo
 * put it. The ::before is transparent and centred on the X: same technique
 * as the phone buttons above.
 */
@media (max-width: 47.9375rem) {
	:where(button.klaviyo-close-form) {
		position: relative;
	}

	button.klaviyo-close-form::before {
		content: "";
		position: absolute;
		inset-block: min(0px, calc(50% - 34px));
		inset-inline: min(0px, calc(50% - 34px));
	}
}
