/*
Theme Name: Talamantez Therapy
Author: Talamantez Therapy
Description: Custom block theme for talamanteztherapy.com — 5 pages, blog, header, and footer rebuilt in the native block editor, replacing Elementor.
Version: 0.1.34
Requires at least: 6.7
Tested up to: 7.0
Requires PHP: 8.3
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: talamantez-therapy
Tags: block-theme, full-site-editing, blog, one-column
*/

/*
 * Design tokens live in theme.json. This file exists for the required theme
 * header and for any CSS that cannot be expressed in theme.json.
 */

/*
 * ============================================================================
 * U20-T typography register CLOSE (2026-07-12)
 * ============================================================================
 * Owner decision, round-2 lens A
 * (scratch/artifacts/u20-type2-lens-a/{rationale.md,specimen.html}), judged
 * in scratch/artifacts/u20-type2-judge/scorecard.md:
 *
 *   T1 (base typeface) = Literata + Karla, a two-face reading-first pairing,
 *     both SIL OFL 1.1, self-hosted, $0 forever. Role split (see
 *     theme.json typography.fontFamilies slugs "body"/"heading" = Literata,
 *     "ui" = Karla):
 *       - Literata (regular 400 + italic 400) — h1/h2/h3 headings, all
 *         long-form body copy (blog posts, page content, FAQ answers when
 *         that pattern ships).
 *       - Karla (one variable file, pinned to weight 400 regular + weight
 *         600 semibold via two theme.json fontFace entries) — kickers/
 *         eyebrows (.is-style-kicker), buttons, nav (core/navigation block
 *         style), category-label chips (.wp-block-post-terms a), contact-
 *         form labels (.grunion-field-label).
 *   T2 (emphasis device) = SKIP the second/script face. Literata ships true
 *     italics; <em>/emphasis renders in Literata Italic (already loaded for
 *     headings/body — zero extra webfont weight). The synthesis spine's
 *     Caveat script device is DROPPED at implementation per the round-2
 *     judge's finding that it violates round 2's own "no new script faces"
 *     rule.
 *
 *   STALE-COMMENT CORRECTION (U14 K2 lock, 2026-07-16): the two clauses this
 *   paragraph used to carry — a "handwritten Therapy" script wordmark, and a
 *   six-point spark glyph as a recurring "education-wing device" — are
 *   superseded by the K2 LOCK RECORD (docs/design/2026-07-16-u14-porch-
 *   build-spec-DRAFT.md, dispositions O3 + "Wordmark"): the wordmark ships
 *   as a typographic Karla/Literata lockup (no script face, no image logo
 *   pending K3), and the six-point star is a plain shared family mark
 *   confined to the masthead/footer ONLY — never an inline recurring glyph
 *   in body/hero emphasis. Left here as the historical record of what this
 *   pass originally shipped; do not read the retired clauses as current.
 *
 * Retired: Adobe Fonts / Typekit kit `vuj5jfg` (Brandon Grotesque,
 * designer-owned, Adobe licensing forbids self-hosting, ~1.5s
 * render-blocking third-party request — perf ledger R20/U18). See
 * functions.php for the removed enqueue/editor-style calls.
 *
 * Self-hosted payload (theme/assets/fonts/, Latin subset only — see
 * assets/fonts/LICENSE-fonts.txt for exact source URLs + fetch date):
 *   literata-regular.woff2   ~20.4 KB
 *   literata-italic.woff2    ~21.1 KB
 *   karla-variable.woff2     ~24.3 KB (one file serves BOTH Karla weights —
 *                             Google's own CSS2 response returns the same
 *                             URL for the 400 and 600 requests; the two
 *                             theme.json fontFace entries pin the `wght`
 *                             axis per weight from that single source, so
 *                             only one file is downloaded)
 *   ------------------------------------------------------------------------
 *   Total                    ~65.9 KB, THREE requests — under lens-a's
 *                             ~71KB/four-file estimate.
 *
 * Deviation from the task brief's generic default: no separate Literata (or
 * Karla) BOLD weight was added. Lens-a's own weights/styles line and payload
 * budget (rationale.md "Licensing" + "Loading plan") specify only Regular
 * 400 + Italic 400 for Literata and Regular 400 + SemiBold 600 for Karla —
 * no bold cut for either family. Any <strong> inside long-form blog content
 * will render as browser-synthesized (faux) bold on Literata until/unless a
 * future pass adds a true bold weight; flagged for the gate, not resolved
 * here, since adding a 5th file would exceed the specced/judged payload.
 *
 * fontSizes are UNCHANGED by this pass (lens-a's scale matches the existing
 * fluid small…xx-large tokens exactly — see rationale.md "theme.json fluid
 * scale, unchanged").
 */

/*
 * U20 partial close (2026-07-11): theme.json palette extension — color/
 * register tokens only, current (provisional) typography untouched; see
 * docs/plans/2026-07-10-001-feat-enhancements-phase-plan.md Review Log,
 * "Owner answers wave 5" and the U20 synthesis
 * (scratch/artifacts/u20-synthesis/synthesis.md §a). JSON carries no
 * comments, so every contrast pair the new/changed tokens create is logged
 * here instead. All ratios computed with the WCAG 2.x relative-luminance
 * formula, independently re-derived for this edit (not copied uncomputed
 * from the synthesis doc, though they match it):
 *
 *   - teal-deep (#1F5A5E) text on paper (#FAF6EE): 7.26:1 — PASS (≥4.5:1
 *     body-text bar). Grief-wing text accent / step numerals / focus ring.
 *   - contrast/navy (#03174B) text on paper (#FAF6EE): 15.86:1 — PASS
 *     (≥4.5:1). Also the ratio for the reverse pairing (paper text on navy,
 *     see next row) — contrast ratio is symmetric between two fixed colors.
 *   - paper (#FAF6EE) text on contrast/navy (#03174B): 15.86:1 — PASS
 *     (≥4.5:1). Same pair as above, reversed fg/bg.
 *   - teal (#B6DEE0) as UI/large text on contrast/navy (#03174B): 11.82:1 —
 *     PASS (≥3:1 UI bar; also clears the 4.5:1 body-text bar). teal is a
 *     wash/fill color and must never be set as text on a LIGHT background
 *     (teal-on-white is ~1.45:1, fails AA) — this navy-background pairing is
 *     the sole authorized exception, per the synthesis's three named call
 *     sites (CTA footnote copy, no-gold-state emphasis spark, no-gold-state
 *     chooser arrow — none shipped in this theme yet; this comment documents
 *     the token's authorized ceiling ahead of that unit's build).
 *
 * No locked pair fails its bar; nothing shipped with a warning exception.
 *
 * `contrast` (navy) migration: value changed from #2A3353 to #03174B in
 * this same edit (owner-confirmed re-anchor, wave-4/5 lock; synthesis
 * §Migration notes item 1). This is a sitewide value change — every existing
 * surface referencing var(--wp--preset--color--contrast) (header, footer
 * nav accents, buttons, heading text, blog pagination .current, the
 * dark-section-link rule below) now renders the new navy. See the dated
 * addendum on the dark-section-link rule below for the one comment in this
 * file whose own recorded ratio depended on the old value.
 *
 * Deliberately NOT added this pass (still gated, see the plan's wave-5 §6
 * open item): gold tokens (dropped, wave-3 lock) and category-label color
 * tokens (`label-*`, held pending the gold-deep chip-vs-large-accent
 * clarification). No typography settings touched (wave-5 §1: typography
 * deferred to its own session; this extension ships with current
 * provisional fonts).
 *
 * --- wave 7 — label chips (2026-07-11) -----------------------------------
 * Owner answers wave 7 (plan Review Log, "2026-07-11 — Owner answers wave
 * 7", item 1) closed the gold-chip question this file flagged above as
 * deferred: gold is now fully out of the system, and the category-label
 * hues are final. `terracotta-deep` (#9C5030) is a new palette token added
 * in this pass (bake-off winner recorded in
 * scratch/artifacts/u20-synthesis/synthesis.md, "Warm-accent bake-off");
 * `teal-deep` and `contrast`/navy already existed (see above) and are only
 * being reused here, not re-added.
 *
 * Five cluster → hue assignments (icons unchanged: leaf/flag/spark/paw/
 * compass):
 *   - Child Grief    → teal-deep       (leaf)
 *   - Milestones     → teal-deep       (flag; told apart from Child Grief
 *                                        by icon + text, same hue reused)
 *   - Holiday Grief  → contrast/navy   (six-point spark)
 *   - Pet Loss       → contrast/navy   (paw; told apart from Holiday Grief
 *                                        by icon + text, same hue reused)
 *   - Practical Next Steps → terracotta-deep (compass; the one cluster
 *                                        with its own hue)
 *
 * Chip-hue-on-paper contrast (small text, 4.5:1 bar):
 *   - teal-deep (#1F5A5E) on paper (#FAF6EE): 7.26:1 — PASS (recorded
 *     above, referenced here — same pair, same ratio).
 *   - contrast/navy (#03174B) on paper (#FAF6EE): 15.86:1 — PASS (recorded
 *     above, referenced here — same pair, same ratio).
 *   - terracotta-deep (#9C5030) on paper (#FAF6EE): 5.41:1 — PASS (≥4.5:1
 *     body-text bar). Computed independently for this edit with the same
 *     WCAG 2.x relative-luminance formula used above; matches the
 *     synthesis doc's own figure. No darkening needed — shipped as-is.
 *
 * All three chip hues clear the 4.5:1 bar on paper; no adjustment required.
 * Attribute-selector CSS for the `label-*` chips themselves (the
 * `a[href*="/category/<slug>/"] .label{...}` rule U1 describes) is U1 build
 * work, not part of this token/annotation pass — deliberately not added
 * here.
 */

/*
 * Live-site parity: header is permanently navy with the white logo variant,
 * footer is permanently pale-aqua with the navy logo variant, at ALL
 * viewport widths — there is no breakpoint inversion (corrected 2026-07-07
 * during U5 verify; see reference/rebuild-spec.md § Header / § Footer).
 * Colors are set via block attributes (backgroundColor/textColor on the
 * header/footer Group and Navigation blocks in parts/header.html and
 * parts/footer.html) rather than CSS, so no override rules are needed here.
 */

/*
 * core/site-logo (header only — the footer logo is a plain core/image with
 * its own inline width) cannot size an SVG via its "width" block attribute:
 * the logo SVG has a viewBox but no width/height, so WordPress can't read
 * intrinsic dimensions and emits neither width/height attributes on the img
 * nor an inline width on the wrapper (verified on staging 2026-07-07 — the
 * img rendered 0×0, or container-width once given height:auto). Pin the
 * rendered size here instead; 200px matches the reference capture
 * (~180–220px at 1280, similar on mobile).
 *
 * SLIMMED (2026-07-30, header-slimming fix): 200px -> 120px, one of several
 * cuts (padding, this scale, row gaps) that bring the sticky header down
 * from 223px to ~139px at 1280/987/768 — see the sticky-header SECTION
 * below for the full before/after. Logo remains legible at 120px (measured
 * ~31px tall); wp:site-logo's own "width" attribute in parts/header.html is
 * kept in sync (120) even though this rule is what actually renders it.
 *
 * ENLARGED, desktop/tablet only (2026-07-30, header-unification commission):
 * 120px -> 192px at >=768px (rendered ~48.2px tall, up from ~31px, mid-low
 * of the owner's requested 48-60px range). Chosen because it keeps the
 * two-row fallback height closest to the ~90px cap: row 1's height is
 * governed by whichever of the logo or the 44px-min-height CTA buttons is
 * taller, and 48.2px only adds ~4px over the buttons' own floor, versus
 * the ~10px a 60px logo would add. The source is the ORIGINAL vector
 * (attachment 67, `2021/05/talamantez-therapy-logo-white.svg`, confirmed
 * live via `wp option get site_logo` + `wp post get 67`), already the
 * light/white variant the navy header needs (`.st0{fill:#FFFFFF}`); no
 * raster upgrade was needed or produced, since scaling an SVG's CSS width
 * costs nothing in crispness, only this rule's number changes.
 *
 * MOBILE (<768px) is deliberately left untouched at 120px. This rule used
 * to be unscoped (applied at every width), so the enlargement is carved
 * out into its own >=768px override immediately below rather than edited
 * here, preserving the existing base rule as the mobile default
 * byte-for-byte.
 */
.site-header .wp-block-site-logo img {
	width: 120px;
	max-width: 100%;
	height: auto;
}

@media (min-width: 768px) {
	.site-header .wp-block-site-logo img {
		width: 192px;
	}
}

/*
 * SECTION: header row-gap tightening (header-slimming fix, 2026-07-30).
 * Flex containers in parts/header.html default to WP's blockGap (0.5em /
 * ~24px in the block-level nesting used here) between wrapped rows — more
 * air than a header needs stacked vertically. Tightened to single digits so
 * the wrapped rows read as one compact unit instead of stacked cards.
 *
 * `.header-cta-col`'s own row-gap (previously 2px, spacing the button row
 * against the subtext line below it) was removed here (Amendment 13
 * resolution, 2026-07-30) along with the subtext paragraph itself
 * (`.header-cta-subtext` — see the REMOVED note further down) — the group
 * now wraps a single child (`.header-cta-wrap`), so a row-gap has nothing
 * left to space.
 */
.header-brand-row {
	row-gap: 4px;
}

/*
 * Header nav uppercase (U11 gate fix, 2026-07-08): production's nav is
 * uppercase+letterspaced in the HEADER only — the footer nav renders Title
 * Case ("Speaking Information / Resources / Podcast / Blog / Etsy Shop /
 * Contact", reference/pages/blog-1280.png). theme.json previously set
 * textTransform:"uppercase"/letterSpacing on core/navigation globally,
 * which incorrectly also uppercased the footer nav; that global rule has
 * been removed from theme.json (styles.blocks.core/navigation) and is
 * re-applied here scoped to .site-header. The mobile overlay dialog
 * (#modal-N, .wp-block-navigation__responsive-container) renders as a DOM
 * child of the header's <nav> element (confirmed live: it is not portaled
 * to document.body), so this descendant selector covers it too without a
 * separate rule.
 */
.site-header .wp-block-navigation-item__content {
	text-transform: uppercase;
	letter-spacing: 0.08em;
}

/*
 * Dark-section links (U6 gate fix, 2026-07-07): links inside
 * contrast-background (navy) sections render navy-on-navy and disappear.
 * Verified live on staging before this fix: the Educational Services "this
 * google form" link computed to rgb(42, 51, 83) text on an
 * rgb(42, 51, 83) background. Production rendered these links gold; restore
 * that. Gold (#BAAC77) on navy (#2A3353) is 5.45:1, passing WCAG AA.
 * Light-background links keep the theme-default navy — a deliberate a11y
 * call recorded for U11 (gold on white is 2.27:1, gold on the pale-aqua
 * tint is 2.03:1 — both fail AA).
 *
 * U20 partial close (2026-07-11): the `contrast` palette slug this rule's
 * background depends on (has-contrast-background-color) was re-anchored
 * from #2A3353 to #03174B (owner wave-4/5 lock, U20 spine decision). Gold
 * (#BAAC77) on the new navy (#03174B) is 7.54:1 — recomputed via the WCAG
 * relative-luminance formula, still comfortably AA (an improvement over the
 * prior 5.45:1). The #2A3353 figure above is kept as a historical record of
 * the U11 fix's original reasoning, not the current live ratio.
 *
 * U11 gate fix (2026-07-08): this rule is a descendant selector, so it also
 * captured the header nav links (the site-header Group carries
 * has-contrast-background-color) and any button whose own aqua/navy
 * background sits inside a navy section (the Educational Services "Book a
 * Consultation" button, has-contrast-color) — both would otherwise get
 * painted gold too, one making the nav unreadable (white was intended, its
 * own textColor:"base"), the other giving a 1.56:1 button. Scope the rule
 * to plain content links only: nav links keep their own color (inherited
 * from the nav's has-base-color/has-text-color, protected by core's own
 * ".wp-block-navigation-item__content.wp-block-navigation-item__content
 * {color:inherit}" doubled-class rule) and button links keep their own
 * has-contrast-color/has-base-color text color.
 *
 * U12 (2026-07-08): !important removed — it existed only to out-fight
 * Elementor's leaked global kit CSS (".elementor-kit-40 a", equal
 * specificity, loading after this stylesheet), which is now deleted from
 * staging. The color declaration itself stays; nothing else on the page
 * targets these links at equal-or-higher specificity.
 *
 * U14 K2 lock O1 (2026-07-16): gold retired in usage. Navy-ground links now
 * render `teal` (#B6DEE0) instead of gold-deep — 11.82:1 on navy #03174B,
 * already computed and authorized for exactly this navy-text use in the
 * U20 palette-extension comment above ("teal as UI/large text on contrast/
 * navy"). teal-deep is NOT used here — teal-deep on navy is 2.18:1, fails
 * 3:1 (K2 lock adjudication correction; the prep draft's blanket remap was
 * wrong on this point).
 */
.has-contrast-background-color a:not(.wp-block-navigation-item__content):not(.wp-element-button) {
	color: var(--wp--preset--color--teal);
}

/*
 * Blog grid (Query Loop, U6 gate fix, 2026-07-07): when a Query block is
 * set to grid view, core applies the SAME grid-template-columns to both the
 * Query wrapper <div> and its single child, the Post Template <ul> — but
 * with only one grid item, the wrapper's own 3-column grid places that
 * child in column 1 and leaves columns 2–3 empty, squeezing the whole post
 * list (and, inside it, the Post Template's own 3-column grid) into 1/3 of
 * the intended width. Confirmed live on staging before this fix: each post
 * rendered at roughly 1/9 of the container width at 375/768/1280. Force the
 * child to span the full wrapper width, then match production's responsive
 * behavior (reference/pages/front-375.png, front-768.png): two columns at
 * 768px, one column below 600px. Uses !important because core's per-block
 * grid-template-columns rule (.wp-container-core-*-is-layout-<hash>) has
 * equal selector specificity and no media query, so it would otherwise win
 * ties by source order at every width.
 */
.wp-block-query.is-layout-grid > .wp-block-post-template {
	grid-column: 1 / -1;
}

@media (max-width: 781px) {
	.wp-block-post-template.is-layout-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
	}
}

@media (max-width: 600px) {
	.wp-block-post-template.is-layout-grid {
		grid-template-columns: 1fr !important;
	}
}

/*
 * "Kicker" heading style (U6 gate fix, 2026-07-07): small, gold, uppercase,
 * letterspaced H3 label used ~11x across front-page.html (Therapy/
 * Speaking/Pricing, Dyslexia Intervention/Individual Reading Support/
 * Reading Comprehension Groups, the two bio names, Office/Phone/Email).
 * Registered in functions.php as a core/heading block style ("Kicker") so
 * future pages apply className "is-style-kicker" instead of repeating the
 * inline typography JSON blob. Color/font-size are set here too (not just
 * via textColor/fontSize attributes) so the style is self-sufficient for
 * pages that apply it without also setting those attributes.
 *
 * U11 Option 2 (2026-07-08): the fallback color here was deep gold
 * (#776E4C) for kickers on light backgrounds (white/tint/paper).
 *
 * U14 K2 lock O1 (2026-07-16): gold retired in usage — light-ground
 * fallback moves to teal-deep (#1F5A5E), 7.26:1 on paper / 6.99:1 on tint /
 * 7.83:1 on white, all PASS. Kickers on NAVY sections now render `teal`
 * (see the rule below) instead of gold — teal-deep on navy is 2.18:1 and
 * FAILS the 3:1 UI bar, so navy-ground kickers must NOT reuse the
 * light-ground teal-deep value (K2 lock adjudication correction).
 */
.is-style-kicker {
	color: var(--wp--preset--color--teal-deep);
	font-family: var(--wp--preset--font-family--ui);
	font-weight: 600;
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.has-contrast-background-color .is-style-kicker {
	color: var(--wp--preset--color--teal);
}

/*
 * Blog index hero card (U8, 2026-07-07; scoped to sticky at U8 verify):
 * production's blog index (reference/pages/blog-1280.png) shows one post as
 * a full grid-width hero card (title below, gold Read More), then a regular
 * 3-column grid of identical smaller cards. The hero treatment belongs to
 * the STICKY post only ("A Place for Thoughts: The Beginning", ID 21),
 * which WordPress floats to the top of page 1 of the posts page and marks
 * with the `sticky` post class — a plain :first-child span (the first-cut
 * rule) wrongly heroed an ordinary post on every paginated page
 * (live-confirmed on /blog/page/2/) and on every archive. With li.sticky
 * required, /blog/ page 1 matches production's hero-first layout while
 * deeper pages and archives render a uniform grid. Scoped to .blog-index —
 * the className set on the Query Loop in templates/home.html and
 * templates/archive.html — so the front page's blog preview grid (SECTION 7
 * of front-page.html, which has no blog-index class and excludes sticky) is
 * unaffected. At the U6 mobile fallback widths the grid already collapses
 * to 2 then 1 column (see the .wp-block-post-template.is-layout-grid media
 * queries above), so grid-column: 1 / -1 is a no-op there and needs no
 * extra media-query guard. No !important: unlike the U6 dark-link and
 * blog-grid-width fixes, this selector doesn't compete with any Elementor
 * kit rule — nothing in the deployed kit CSS targets .wp-block-post-template
 * list items.
 */
.blog-index .wp-block-post-template > li.sticky:first-child {
	grid-column: 1 / -1;
}

/*
 * Post-title case (U8 verify fix, 2026-07-07): production reference is
 * title case for h2.wp-block-post-title (blog index, archives, and the
 * front-page blog preview) — this rule keeps it that way. Does NOT touch
 * the read-more link — its uppercase is intentional, set inline via
 * typography attributes on the block.
 *
 * U12 (2026-07-08): !important removed — it existed only to out-fight
 * Elementor's leaked ".elementor-kit-40 h2{text-transform:uppercase}" kit
 * rule, which is now deleted from staging. Kept the plain declaration
 * (rather than deleting the rule outright) since no other stylesheet here
 * sets text-transform on post titles, but an explicit "none" documents the
 * parity intent and costs nothing.
 */
.wp-block-post-title {
	text-transform: none;
}

/*
 * Content H2 leak counter (U11, 2026-07-08): the same leaked
 * ".elementor-kit-40 h2{color:var(--e-global-color-accent) [gold];
 * text-transform:uppercase}" rule (specificity 0,1,1) that forced blog
 * card titles into ALL CAPS (see the post-title fix above) also renders
 * every content H2 gold and/or uppercase — confirmed live before this fix:
 * "Therapy Services"/"Contact" (no textColor attribute) computed
 * rgb(186, 172, 119) uppercase, and even "Educational Services"
 * (has-base-color, already correctly white via its own !important)
 * still computed uppercase, because has-base-color only sets color, never
 * text-transform. Split into two rules so each applies to exactly the H2s
 * it needs to:
 *   - text-transform applies to EVERY content H2 (has-text-color or not) —
 *     removing uppercase never conflicts with has-base-color/has-X-color,
 *     which don't touch text-transform at all.
 *   - color only applies to H2s with NO existing color utility class
 *     (:not(.has-text-color)), so it never fights has-base-color's own
 *     !important for "Educational Services".
 * Both exclude is-style-kicker so page-resources.html's H2-level kickers
 * (which must stay uppercase gold/gold-deep) are untouched, and neither
 * touches h2.wp-block-post-title (a different block, already handled
 * above). (page-speaking-information.html's kickers moved to teal-deep at
 * U5, 2026-07-12 — same is-style-kicker exclusion still applies, just via
 * an explicit textColor attribute now instead of the gold-deep fallback.)
 * Each selector (h2 element + .wp-block-heading class + one or two
 * :not() classes = specificity 0,2,1 / 0,3,1) already out-specifies the
 * kit's 0,1,1 rule outright, so no !important is needed here (unlike the
 * post-title fix, which was a bare single-class selector too weak on its
 * own).
 */
h2.wp-block-heading:not(.is-style-kicker) {
	text-transform: none;
}

h2.wp-block-heading:not(.has-text-color):not(.is-style-kicker) {
	color: var(--wp--preset--color--contrast);
}

/*
 * Inline content links (U11 Option 2, 2026-07-08): body-copy links inside
 * paragraphs render an accent color on light backgrounds instead of the
 * theme-default navy. Scoped to "p a" so it structurally can never touch:
 * post-title links (core/post-title's anchor is a direct child of the
 * h2/h3, never wrapped in a <p> — handled by the explicit override below
 * instead), nav links (rendered inside <li>, never inside a <p>), button
 * links (never inside a <p>), and read-more/pagination links (also never
 * inside a <p>). Dark-section paragraph links (e.g. "this google form")
 * still match this selector too, but the .has-contrast-background-color
 * rule above has higher specificity (0,3,1 vs this rule's 0,1,2) and wins,
 * keeping those on the navy-ground value as required.
 *
 * U12 (2026-07-08): !important removed — it existed only to out-fight
 * Elementor's leaked ".elementor-kit-40 a{color:var(--e-global-color-
 * primary)}" rule (0,1,1 specificity), which is now deleted from staging.
 * The redundant :not(.wp-element-button) is left in place — it still does
 * no harm and keeps this selector's specificity a notch above a bare "p a"
 * for any future stylesheet, though it's no longer load-order-critical.
 *
 * U14 K2 lock O1 (2026-07-16): gold retired in usage. Color moves gold-deep
 * -> teal-deep (7.26:1 on paper, 6.99:1 on tint, 7.83:1 on white — all
 * PASS, same figures already computed in the U20 palette-extension comment
 * above). The lock also makes the underline explicit ("kill color-alone" —
 * considerations §12 Tier 1) rather than relying solely on a color cue.
 */
p a:not(.wp-element-button) {
	color: var(--wp--preset--color--teal-deep);
	text-decoration: underline;
}

/*
 * Post-title links stay navy (U11 Option 2): production's blog/archive
 * card titles are navy, not gold-deep — the anchor inside
 * h2.wp-block-post-title/h3.wp-block-post-title previously got its color
 * from the same generic elements.link style now repointed at gold-deep.
 * Restore navy explicitly.
 */
.wp-block-post-title a {
	color: var(--wp--preset--color--contrast);
}

/*
 * Category label chips (U1, 2026-07-12): colored label rendered above the
 * card title on /blog/ and each category archive (Redwood colored-label-
 * above-title pattern, U20 register decision — see the wave-7 comment block
 * above, ~line 65, for the hue assignments this rule implements). Categories
 * are WP-CLI-created with locked slugs (docs/content/2026-07-11-u1-category-
 * assignment.sh; term IDs 3968-3972); core's post-terms block emits no
 * per-term class, so the hue mapping is attribute-selector CSS keyed on the
 * category permalink, per the plan's U1 approach note. Scoped to
 * .blog-index (the Query Loop className shared by templates/home.html and
 * templates/archive.html) so the front-page blog preview (SECTION 7 of
 * front-page.html, no blog-index class) is unaffected.
 *
 * Chip is paper (#FAF6EE) background with hue text — reuses the three
 * chip-hue-on-paper ratios already computed and PASSING in the wave-7
 * comment above (teal-deep 7.26:1, contrast/navy 15.86:1, terracotta-deep
 * 5.41:1; all ≥4.5:1 small-text bar — not re-derived here, same fixed pair
 * of colors).
 *
 * Off-topic/general-bucket posts (20 of 65, per the U1 content audit) stay
 * in the default "Uncategorized" term — the assignment script deliberately
 * never touches them. The Uncategorized link is hidden outright (no chip,
 * no color) rather than styled, and the now-empty wrapper is collapsed via
 * :has() so it takes no vertical space, leaving those cards genuinely
 * unlabeled (R7/U1 edge case). Per-cluster hue assignment: Child Grief +
 * Milestones -> teal-deep; Holiday Grief + Pet Loss -> contrast/navy;
 * Practical Next Steps (practical-next-steps slug) -> terracotta-deep.
 *
 * Slug rename (U1 follow-up, 2026-07-12): the Practical Next Steps slug was
 * renamed task-frameworks -> practical-next-steps on staging (`wp term
 * update category 3970 --slug=practical-next-steps`) immediately after the
 * U1 acceptance gate, while the term was fresh and nothing external linked
 * to it. Both attribute selectors below (chip hue + fallback tint) were
 * updated in the same commit — they key on the category permalink, so a
 * slug change without this swap would silently drop the terracotta chip.
 */
.blog-index .wp-block-post-terms {
	margin: 0 0 var(--wp--preset--spacing--10, 0.5rem) 0;
}
.blog-index .wp-block-post-terms:has(a[href*="/category/uncategorized/"]) {
	display: none;
}
.blog-index .wp-block-post-terms a[href*="/category/uncategorized/"] {
	display: none;
}
.blog-index .wp-block-post-terms a {
	display: inline-block;
	padding: 0.2em 0.7em;
	border-radius: 999px;
	background-color: var(--wp--preset--color--paper);
	text-decoration: none;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	font-family: var(--wp--preset--font-family--ui);
	font-weight: 600;
}
.blog-index .wp-block-post-terms a[href*="/category/child-grief/"],
.blog-index .wp-block-post-terms a[href*="/category/milestones/"] {
	color: var(--wp--preset--color--teal-deep);
}
.blog-index .wp-block-post-terms a[href*="/category/holiday-grief/"],
.blog-index .wp-block-post-terms a[href*="/category/pet-loss/"] {
	color: var(--wp--preset--color--contrast);
}
.blog-index .wp-block-post-terms a[href*="/category/practical-next-steps/"] {
	color: var(--wp--preset--color--terracotta-deep);
}

/*
 * Missing-featured-image fallback (U1, 2026-07-12): several legacy posts
 * (audit-confirmed) have no featured image; core's post-featured-image
 * block renders zero markup when a post has no thumbnail (Query Loop
 * context) — no wrapping element at all — which would otherwise leave a
 * blank gap above the title where a card should be. There's no declarative
 * FSE way to give that block fallback markup, so this uses a :has()-guarded
 * ::before pseudo-element on the post-template list item to draw a
 * category-tinted placeholder at the same 3/2 aspect ratio as a real
 * featured image, never a blank card (U1 edge-case test).
 *
 * Tints are a fixed ~18% blend of each cluster's label hue into paper
 * (#FAF6EE), computed by hand (no color-mix() dependency, for broadest
 * staging-browser support): teal-deep -> #D3DAD4, contrast/navy -> #CECED1,
 * terracotta-deep -> #E9D8CC. These are decorative fills only — no text sits
 * on them, so the 4.5:1 text-contrast bar does not apply and is deliberately
 * not computed for these three values. Posts with no category chip
 * (off-topic/Uncategorized) get a plain paper fallback, no hue.
 */
.blog-index .wp-block-post-template > li:not(:has(.wp-block-post-featured-image))::before {
	content: "";
	display: block;
	width: 100%;
	aspect-ratio: 3 / 2;
	border-radius: 4px;
	margin-bottom: var(--wp--preset--spacing--20, 0.75rem);
	background-color: var(--wp--preset--color--paper);
}
.blog-index .wp-block-post-template > li:has(a[href*="/category/child-grief/"]):not(:has(.wp-block-post-featured-image))::before,
.blog-index .wp-block-post-template > li:has(a[href*="/category/milestones/"]):not(:has(.wp-block-post-featured-image))::before {
	background-color: #D3DAD4;
}
.blog-index .wp-block-post-template > li:has(a[href*="/category/holiday-grief/"]):not(:has(.wp-block-post-featured-image))::before,
.blog-index .wp-block-post-template > li:has(a[href*="/category/pet-loss/"]):not(:has(.wp-block-post-featured-image))::before {
	background-color: #CECED1;
}
.blog-index .wp-block-post-template > li:has(a[href*="/category/practical-next-steps/"]):not(:has(.wp-block-post-featured-image))::before {
	background-color: #E9D8CC;
}

/*
 * Blog pagination (owner-decided parity revert, 2026-07-08): reverted from
 * the U11 Option 2 deep-gold treatment back to production's actual look —
 * pixel-sampling reference/pages/blog-1280.png confirmed "‹ PREV / numbers /
 * NEXT ›" render aqua (#B7DDDF), uppercase, with only the current page
 * navy. This was a deliberate, owner-approved WCAG AA contrast exception
 * (~1.3:1 aqua-on-white) under the parity-first doctrine, "slated for
 * reconsideration at the enhancements phase."
 *
 * U12 (2026-07-08): !important removed from both the base aqua color and
 * the .current navy color below — both existed only to out-fight
 * Elementor's leaked ".elementor-kit-40 a" kit rule, which is now deleted
 * from staging. Colors are unchanged; text-decoration:none at rest and
 * text-decoration:underline on hover/focus (neither ever needed
 * !important) are untouched so the hover affordance still wins over the
 * base rule.
 *
 * U19 Option A CLOSE (2026-07-17, owner-approved, remaining-pages design
 * audit): base color recolored aqua -> teal-deep (#1F5A5E), 7.83:1 on white,
 * AA PASS — closes the site's last axe/AA contrast exception. Existing
 * locked token, zero new colors (K2 lock). Bonus coherence: teal-deep is
 * already the exact color of the "Read More" links directly above the
 * pagination on the same index (home.html), so the whole blog-index control
 * set is now one color instead of two. Uppercase/letter-spacing/size/at-rest-
 * underline behavior and `.current` navy are unchanged.
 */
.wp-block-query-pagination .page-numbers,
.wp-block-query-pagination .wp-block-query-pagination-next,
.wp-block-query-pagination .wp-block-query-pagination-previous {
	color: var(--wp--preset--color--teal-deep);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	font-size: var(--wp--preset--font-size--small);
	/* At-rest underline suppressed for parity (production shows none); the
	 * only source of it was the UA stylesheet default for <a>, so a plain
	 * declaration is sufficient to win. */
	text-decoration: none;
}

.wp-block-query-pagination .page-numbers:hover,
.wp-block-query-pagination .page-numbers:focus,
.wp-block-query-pagination .wp-block-query-pagination-next:hover,
.wp-block-query-pagination .wp-block-query-pagination-next:focus,
.wp-block-query-pagination .wp-block-query-pagination-previous:hover,
.wp-block-query-pagination .wp-block-query-pagination-previous:focus {
	text-decoration: underline;
}

.wp-block-query-pagination .page-numbers.current {
	color: var(--wp--preset--color--contrast);
}

/*
 * Post-meta headings (U11 Option 2): "Share this:" (Jetpack sharing,
 * h2.sd-title), the comments-count heading (core/comments-title,
 * .wp-block-comments-title), and "Leave a Reply" (core/post-comments-form,
 * h3.comment-reply-title) are recolored on this white single-post section
 * to match Option 2.
 *
 * U12 (2026-07-08): !important removed — it existed only to out-fight
 * Elementor's leaked ".elementor-kit-40 h2/h3" gold (#BAAC77) kit rule,
 * which is now deleted from staging. Color declaration unchanged.
 *
 * K4 fix A4 (2026-07-17, K2 lock O1 "gold retired from usage"): this was
 * the one residual live gold-deep usage flagged by the K4 ship-gate prep
 * memo (docs/design/2026-07-17-k4-ship-gate-PREP.md A4). Remapped per the
 * O1 lock's mechanics — light ground → teal-deep (same target as kickers
 * and links elsewhere in this file). teal-deep on white/base = 7.83:1,
 * PASS (≥4.5:1), reusing the already-computed U20/K2 ratio, not re-derived.
 */
.sd-title,
.wp-block-comments-title,
.comment-reply-title {
	color: var(--wp--preset--color--teal-deep);
}

/*
 * Jetpack contact form (#Contact, U9, 2026-07-07): Jetpack Forms 16.x ships
 * layout-only CSS for this block (jetpack-forms-layout.css only handles flex
 * widths / the multistep style variant) — confirmed on staging by rendering
 * the exact block markup below via a throwaway test page: fields come back
 * as plain, unstyled native controls: `<div class="grunion-field-wrap">
 * <label class="grunion-field-label">Name</label><input class="name
 * grunion-field" /></div>`. Labels/inputs/spacing below are this theme's
 * own styling, not an override of anything Jetpack ships. The submit button
 * is the jetpack/button block, which reuses core's .wp-block-button__link
 * class, so it already inherits the theme.json global button style
 * (uppercase, letterspacing, padding, border-radius:0); aqua-on-navy comes
 * from the has-aqua-background-color/has-contrast-color block attributes
 * set in front-page.html, same as every other button on the page — no
 * button CSS needed here. Confirmed (fetched the live combined kit
 * stylesheet) that .elementor-kit-40 only styles a/h1–h6 and never
 * touches inputs, textarea, button, or label, so — unlike the U6 dark-link
 * and U8 post-title fixes above — no !important is needed for any of this.
 */
#Contact .wp-block-jetpack-contact-form .grunion-field-wrap {
	margin-bottom: var(--wp--preset--spacing--30);
}

/*
 * U11 Option 2 (2026-07-08): labels were originally styled gold to match
 * the kicker treatment; the approved Option 2 palette moves all
 * light-background labels to navy instead (contact-form labels are
 * explicitly called out as navy, not gold-deep). The "(required)" span
 * (.grunion-label-required) has no color of its own, so it inherits this
 * value automatically.
 */
#Contact .wp-block-jetpack-contact-form .grunion-field-label {
	display: block;
	margin-bottom: var(--wp--preset--spacing--20);
	color: var(--wp--preset--color--contrast) !important;
	font-family: var(--wp--preset--font-family--ui);
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

#Contact .wp-block-jetpack-contact-form .grunion-field {
	box-sizing: border-box;
	width: 100%;
	border: 1px solid var(--wp--preset--color--contrast);
	padding: var(--wp--preset--spacing--20);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--medium);
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
}

/*
 * The Message field renders with a `rows="20"` attribute (a Jetpack Forms
 * default for the textarea field type) — left as-is per Jetpack defaults,
 * but that intrinsic sizing would otherwise make the field enormous.
 * Constrain with an explicit height, which overrides `rows` in the box
 * model, and match production's shorter message box (reference/pages/
 * front-1280.png, Contact section).
 */
#Contact .wp-block-jetpack-contact-form .grunion-field.textarea {
	height: 8em;
	resize: vertical;
}

/*
 * Jetpack stock success-heading suppression (2026-07-23, owner form-copy
 * decision): Jetpack Forms 16.x renders an un-configurable
 * `<h4 id="contact-form-success-header-{hash}">Thank you for your
 * response. ✨</h4>` inside every `.contact-form-submission` success
 * wrapper, regardless of the block's own `customThankyouMessage` attribute —
 * confirmed via served staging markup on all three forms (porch, front-page,
 * education contact). Hidden here so the customThankyouMessage div (same
 * wrapper, sibling markup) is the sole visible confirmation copy. Trade-off:
 * the wrapper's `aria-labelledby` points at this h4's id, so hiding it drops
 * the wrapper's accessible name (hidden aria-labelledby targets are excluded
 * from accname computation) — accepted, since the customThankyouMessage
 * text remains in the accessible tree as readable content either way.
 */
.contact-form-submission h4[id^="contact-form-success-header-"] {
	display: none;
}

/*
 * U2 crisis-line block (2026-07-12, R13): sitewide footer instance
 * (theme/parts/footer.html) + front-page #Contact near-form instance
 * (theme/templates/front-page.html) share this one reusable className
 * rather than two ad-hoc rule sets. Placement follows plan §U2
 * (`docs/plans/2026-07-10-001-feat-enhancements-phase-plan.md`, ~line 163:
 * "navy-on-tint block, AA contrast") and the Everroot near-contact model
 * (`docs/research/2026-07-10-design-addendum-mobile-report.md` §7 ADOPT row;
 * `reference/design-research/everroot/book-form-mobile-connect.png`).
 *
 * Contrast: contrast/navy (#03174B) text on tint (#EAF4F5) = 15.27:1 — PASS
 * (≥4.5:1 body-text bar; ≥3:1 UI bar for the tel:/sms: links). Computed with
 * the same WCAG 2.x relative-luminance formula used in the U20 header
 * comment above (independently re-derived for this pair, not copied).
 *
 * Link color, fixed 2026-07-12 (U2-gate advisory, adopted): the sitewide
 * `p a:not(.wp-element-button){color:var(--wp--preset--color--gold-deep)}`
 * rule (~line 356; specificity 0,1,2 — 1 class from :not(), 2 elements)
 * used to beat a bare `.crisis-line a{color:inherit}` (specificity 0,1,1),
 * so crisis links rendered gold-deep #776E4C (4.55:1 on tint) instead of
 * inheriting the intended navy #03174B (15.27:1 on tint, computed above).
 * The link-color rule below is now scoped `.crisis-line p a:not(...)`
 * (specificity 0,2,2 — 2 classes, 2 elements) so it unambiguously outranks
 * the global rule rather than relying on source-order tie-breaking, and
 * crisis links correctly inherit navy at the 15.27:1 ratio. The global
 * gold-deep rule itself is untouched — this is a local override only, and
 * the pre-existing sitewide condition it corrects for other paragraph links
 * remains someone else's owned decision.
 *
 * `.crisis-line--near-contact` is a spacing-only modifier for the wider
 * #Contact section paragraph; the footer instance stays at the compact
 * default. Both instances are still NEEDS-COPY-SIGNOFF (see the dated HTML
 * comments directly above each block in their template files) — this CSS
 * governs presentation only and ships regardless of which copy variant the
 * owners eventually pick, since all drafted variants are comparable length.
 *
 * tel:988 / sms:988 links get an explicit ≥44px-tall tap target (line-height
 * + min-height, not just default text-link sizing) per the plan's mobile a11y
 * test scenario — core ships no touch-sizing for inline text links, and the
 * design-addendum research (§2.3) found sub-44px height is the dominant tap-
 * target failure across every competitor site scanned.
 *
 * tel:911 added to the same tap-target selector (2026-07-12, footer 911
 * add): the near-contact instance (theme/templates/front-page.html) already
 * shipped a tel:911 link under the original C1 911 build but this selector
 * was never widened to cover it, so that link was rendering at default
 * inline-text size with no ≥44px guarantee. Fixed here rather than left as
 * a second gap, since both the footer and near-contact instances now carry
 * tel:911 and should get identical tap-target treatment to tel:988/sms:988.
 */
.crisis-line {
	max-width: 480px;
	margin-left: auto;
	margin-right: auto;
	border-radius: 6px;
	text-align: center;
}
.crisis-line--near-contact {
	max-width: 620px;
	margin-top: 0;
	margin-bottom: var(--wp--preset--spacing--40, 2rem);
}
.crisis-line p {
	margin: 0;
}
.crisis-line p a:not(.wp-element-button) {
	color: inherit;
	font-weight: 600;
	text-decoration: underline;
}
.crisis-line a[href^="tel:988"],
.crisis-line a[href^="sms:988"],
.crisis-line a[href^="tel:911"] {
	display: inline-block;
	min-width: 44px;
	min-height: 44px;
	line-height: 44px;
	padding: 0 0.5em;
	vertical-align: middle;
}
.crisis-line a:hover,
.crisis-line a:focus {
	text-decoration-thickness: 2px;
}

/*
 * ============================================================================
 * U7 — numbered "what happens when you reach out" steps module
 * ============================================================================
 * Anxiety-reduction pattern (design-cues §7): each step renders as a
 * distinct card with a circular numeral, not a plain <ol>/<li> list.
 * teal-deep is used for the numeral — it's already documented above (U20
 * contrast ledger, "Grief-wing text accent / step numerals / focus ring")
 * as the intended color for exactly this purpose, so no new contrast check
 * was needed: teal-deep on paper is 7.26:1 (PASS, ≥4.5:1) and the numeral
 * also sits on base/white inside each card, an even lighter ground.
 * Markup: a .step-module wrapper (theme/templates/page-what-to-expect.html
 * SECTION 1) containing plain wp:group blocks classed .step-item, each with
 * a .step-number paragraph and a .step-text paragraph — no JS, no custom
 * block, degrades to a readable stack of labeled paragraphs with CSS off.
 */
.step-module {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--30, 1.5rem);
}
.step-item {
	display: flex;
	align-items: flex-start;
	gap: var(--wp--preset--spacing--30, 1.5rem);
	padding: var(--wp--preset--spacing--30, 1.5rem);
	background-color: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--tint);
	border-radius: 4px;
}
.step-number {
	flex: 0 0 auto;
	width: 2.75rem;
	height: 2.75rem;
	margin: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	border: 2px solid var(--wp--preset--color--teal-deep);
	color: var(--wp--preset--color--teal-deep);
	font-family: var(--wp--preset--font-family--ui);
	font-weight: 600;
	font-size: var(--wp--preset--font-size--medium);
}
.step-text {
	margin: 0;
	align-self: center;
}

/*
 * ============================================================================
 * U8 — education landing page (/dyslexia-tutoring/), 2026-07-12
 * ============================================================================
 * Education-wing register per the U20 outcome (docs/design/u20-council-
 * brief.md §6 decisions 1/3): navy/contrast focal color, no gold, no
 * italic-emphasis device (Literata's true italic is the grief-wing device
 * only — see the U20-T comment near the top of this file; education stays
 * plain-weight/uppercase-label instead).
 *
 * Program-card grid (u8-education-landing.md §3): reuses the .step-item
 * card chrome (padding/border/radius) established at U7 under a new class
 * name, so a future edit to one component doesn't silently reflow the
 * other. Card border is decorative only (tint #EAF4F5), same no-ratio-
 * needed precedent as .step-item's border above. Card heading/body text is
 * the sitewide default navy-on-white pairing (theme.json styles.color:
 * contrast text on base background) — already the global default, not a
 * new color use, so no new ratio is computed here.
 */
.program-card-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: var(--wp--preset--spacing--30, 1.5rem);
}
/* WP's flow-layout block-gap still applies margin-block-start to children
 * 2+ even though this is visually a CSS grid — zero it out so card tops
 * stay flush (B2, opus gate fix list). */
.program-card-grid > * {
	margin-block-start: 0;
}
@media (max-width: 600px) {
	.program-card-grid {
		grid-template-columns: 1fr;
	}
}
.program-card {
	padding: var(--wp--preset--spacing--30, 1.5rem);
	background-color: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--tint);
	border-radius: 4px;
}
.program-card h3 {
	margin-top: 0;
}

/*
 * DRAFT PROPOSAL badge (program cards 5-6, §3: Friends on the Block /
 * Guided Phonics Instruction — differentiation copy pending owner
 * discussion, not final). Same chip chrome as the U1 blog category-label
 * chips (paper bg + navy text): contrast/navy (#03174B) on paper
 * (#FAF6EE) = 15.86:1 — PASS (≥4.5:1 small-text bar; figure already
 * computed in the U20 palette-extension comment above, reused not
 * re-derived, same fixed color pair). No italic (education register skips
 * the italic-emphasis device per U20 decision 1) — uppercase weight +
 * letter-spacing carry the "flagged, not final" signal instead.
 */
.program-card__badge {
	display: inline-block;
	margin-bottom: var(--wp--preset--spacing--20, 0.5rem);
	padding: 0.2em 0.7em;
	border-radius: 999px;
	background-color: var(--wp--preset--color--paper);
	color: var(--wp--preset--color--contrast);
	text-transform: uppercase;
	letter-spacing: 0.06em;
	font-family: var(--wp--preset--font-family--ui);
	font-weight: 600;
	font-size: var(--wp--preset--font-size--small);
}

/*
 * Navy step-numeral variant ("how it works," §4): the U7 .step-number
 * numeral above is hardcoded teal-deep, the grief-wing text accent (see
 * the U7 comment block above) — reusing it unmodified on this
 * education-wing page would bleed a grief-register color onto education
 * content, contrary to the U20 per-wing register split (navy focal for
 * education, teal-deep reserved for grief). This modifier repaints the
 * numeral navy/contrast instead, scoped with .is-navy so U7's own
 * instance (what-to-expect, a wing-neutral page) is untouched. contrast/
 * navy on white ≈16:1 (U20 council brief §4 known-good anchor,
 * docs/design/u20-council-brief.md line ~157) — PASS, clears both the
 * ≥4.5:1 text bar and the ≥3:1 UI-component bar (the ring) with room to
 * spare.
 */
.step-module.is-navy .step-number {
	border-color: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--contrast);
}

/*
 * U10 — not-legal-advice callout (texas-school-testing page): draft §2
 * requires the disclaimer placed "near the top, not buried in a footnote."
 * Reuses .program-card's existing base-bg/tint-border/radius chrome verbatim
 * (no new color pair to verify, no new visual device introduced) — an even
 * all-around border, not a colored side-accent stripe.
 */
/*
 * C2 (2026-07-30 estate-banding review, Amendment 12): border tint -> teal.
 * Tint-on-white read as nearly invisible on the hub's FAQ cards and the
 * signs-page callout (same failure mode already fixed once for
 * .no-diagnosis-box above); teal (#B6DEE0) gives the box a real edge while
 * staying a background-only accent, matching the approved hub mock's FAQ
 * cards, which used teal borders.
 */
.callout-box {
	padding: var(--wp--preset--spacing--30, 1.5rem);
	background-color: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--teal);
	border-radius: 4px;
}

/*
 * SECTION: "what we don't do" box (2026-07-30 design-review fix, item 3e).
 * page-dyslexia-tutoring.html's no-diagnosis callout (className callout-box
 * no-diagnosis-box) had drifted to .callout-box's all-around tint border,
 * which on this box's tint-ground section reads as nearly invisible (tint
 * border on tint background). Restores the mock's navy left rule instead:
 * a real, load-bearing accent instead of a border that quietly disappears.
 * Two classes on the element (.callout-box.no-diagnosis-box) outrank the
 * plain .callout-box rule above, so this needs no !important; only this
 * specific box is affected — the shared .callout-box class (also reused by
 * the FAQ details blocks, item 3c) is untouched.
 */
.no-diagnosis-box {
	border: none;
	border-left: 5px solid var(--wp--preset--color--contrast);
	border-radius: 0;
}

/*
 * ============================================================================
 * U14 — two-door porch homepage (theme/templates/page-porch.html), 2026-07-16
 * ============================================================================
 * Ported from the K2-locked build spec (docs/design/2026-07-16-u14-porch-
 * build-spec-DRAFT.md) and its LOCK RECORD. Structural sections (hero
 * halves, fold-chooser, door cards, pinned CTA) are hand-authored HTML
 * wrappers around real wp:heading/wp:paragraph/wp:button blocks — same
 * pattern already established by .step-module (U7) and .program-card (U8):
 * a custom class carries layout/color, core blocks carry the text content.
 * No backgroundColor block attribute is used on these wrappers, so the
 * dual-encoding matched-pair rule (comment attr <-> class) does not apply
 * to them — colors are plain CSS custom-property references instead, same
 * technique as every other custom class in this file.
 *
 * Contrast ratios below are the K2-locked figures, reused from the U20
 * palette-extension comment (top of this file) and the K2 LOCK RECORD
 * itself — not re-derived, same fixed color pairs.
 */

/*
 * Full-bleed breakout for the porch's two hand-authored <section> wrappers
 * (.porch-hero, .porch-doors — see below): these are plain HTML, not
 * wp:group blocks, so they carry no block JSON attributes for WP to compute
 * a dynamic "is-layout-constrained"/alignfull breakout class from at render
 * time. Rather than depend on that (unverifiable without a live render),
 * force the breakout directly — same result, no dependency on core's
 * per-block layout-class injection.
 */
.porch-hero,
.porch-doors {
	box-sizing: border-box;
	width: 100vw;
	position: relative;
	left: 50%;
	right: 50%;
	margin-left: -50vw;
	margin-right: -50vw;
}

/* Hero: two co-equal entrances, dual-<h1> exception (build-spec §4). */
.porch-hero {
	display: flex;
	flex-direction: column;
}
.porch-doors {
	padding: var(--wp--preset--spacing--60, 4rem) var(--wp--preset--spacing--40, 1.5rem);
}
@media (min-width: 768px) {
	.porch-hero {
		flex-direction: row;
		align-items: stretch;
	}
}
.hero-half {
	flex: 1 1 0;
	padding: var(--wp--preset--spacing--60, 4rem) var(--wp--preset--spacing--40, 1.5rem);
	display: flex;
	flex-direction: column;
	justify-content: center;
}
.hero-half.grief {
	background-color: var(--wp--preset--color--teal);
	/* text color intentionally unset: theme.json default text (contrast/navy)
	   already reads correctly on teal — navy-on-teal-wash, no override needed */
}
.hero-half.education {
	background-color: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
}
.hero-eyebrow {
	text-transform: uppercase;
	letter-spacing: 0.12em;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	font-family: var(--wp--preset--font-family--ui);
	margin: 0 0 var(--wp--preset--spacing--20, 0.5rem);
}
/* grief eyebrow: teal-deep on teal-wash = 5.41:1 PASS */
.hero-half.grief .hero-eyebrow {
	color: var(--wp--preset--color--teal-deep);
}
/* education eyebrow: teal on navy = 11.82:1 PASS (no-gold authorized use) */
.hero-half.education .hero-eyebrow {
	color: var(--wp--preset--color--teal);
}
.hero-half h1 {
	margin-bottom: var(--wp--preset--spacing--20, 0.5rem);
}

/*
 * Above-fold condensed chooser (mobile only, ≤767px) — build spec §1 S1b.
 * Plain anchor links (R17 no-JS), grief first (R1). Resolves the fork
 * before any scroll, per addendum §3 item 1.
 */
.fold-chooser {
	display: none;
}
@media (max-width: 767px) {
	.fold-chooser {
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: var(--wp--preset--spacing--20, 0.5rem);
		padding: var(--wp--preset--spacing--20, 0.5rem) var(--wp--preset--spacing--30, 1rem);
	}
	.fold-card {
		display: flex;
		flex-direction: column;
		gap: 0.15rem;
		padding: 0.55rem 0.65rem;
		text-decoration: none;
		min-height: 44px;
		justify-content: center;
		border: 1px solid transparent;
	}
	/* grief card: navy text on tint, teal-deep border = 15.27:1 PASS */
	.fold-card.grief {
		background-color: var(--wp--preset--color--tint);
		color: var(--wp--preset--color--contrast);
		border-color: var(--wp--preset--color--teal-deep);
	}
	/* education card: base text on navy = 17.10:1 PASS */
	.fold-card.education {
		background-color: var(--wp--preset--color--contrast);
		color: var(--wp--preset--color--base);
	}
	.fold-card-title {
		font-weight: 600;
		font-size: 0.8rem;
		font-family: var(--wp--preset--font-family--ui);
	}
	.fold-card-sub {
		font-size: 0.68rem;
		line-height: 1.3;
	}
}

/*
 * Two-door chooser (desktop; #Services anchor host) — build spec §1 S2.
 * Equal-width co-equal doors (synthesis L329 written invariant). Plain
 * links, no-JS (R17).
 */
.door-grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--wp--preset--spacing--30, 1.5rem);
	max-width: 72rem;
	margin: var(--wp--preset--spacing--40, 1.5rem) auto 0;
}
@media (min-width: 768px) {
	.door-grid {
		grid-template-columns: 1fr 1fr;
		gap: var(--wp--preset--spacing--40, 1.5rem);
	}
}
.door-card {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--20, 0.5rem);
	padding: var(--wp--preset--spacing--40, 1.5rem);
	text-decoration: none;
	border: 2px solid transparent;
	min-height: 44px;
}
/* grief door: navy text on tint, teal-deep border = 15.27:1 PASS */
.door-card.grief {
	background-color: var(--wp--preset--color--tint);
	color: var(--wp--preset--color--contrast);
	border-color: var(--wp--preset--color--teal-deep);
}
/* education door: base text on navy = 17.10:1 PASS */
.door-card.education {
	background-color: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
}
.door-card h3 {
	margin: 0;
}
.door-card p {
	margin: 0;
}
.door-arrow {
	font-weight: 700;
	font-size: 1.2em;
}
.door-card.grief .door-arrow {
	color: var(--wp--preset--color--teal-deep);
}
/* education arrow: teal on navy = 11.82:1 PASS (no-gold authorized use) */
.door-card.education .door-arrow {
	color: var(--wp--preset--color--teal);
	font-size: 1.3em;
}

/*
 * Pinned mobile booking CTA (persistent, after <main>) — build spec §1.
 * Sticky bottom, mobile-only, no transition (reduced-motion safe by
 * construction — nothing to gate). Sits after <main> in flow so it never
 * overlaps content (R17).
 */
.pinned-cta {
	position: sticky;
	bottom: 0;
	left: 0;
	right: 0;
	background-color: var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--base);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--20, 0.5rem);
	padding: var(--wp--preset--spacing--20, 0.5rem) var(--wp--preset--spacing--30, 1rem);
	z-index: 80;
	/* top border: teal, not gold (K2 lock O1) — 11.82:1 on navy, decorative
	   1px hairline, no text-contrast bar applies but reuses the same
	   authorized navy-ground accent value as everywhere else on the porch */
	border-top: 2px solid var(--wp--preset--color--teal);
}
/* white-on-navy override: a default navy button would vanish against this
   bar's own navy background — contrast/navy text on base/white = 17.10:1 at
   rest. theme.json's sitewide button :hover now sets BOTH background
   (teal-deep) and text (base) explicitly (U14 K2 lock fix, 2026-07-16 — see
   theme.json styles.elements.button:hover), so hover here needs no local
   override: base text on teal-deep = 7.83:1 PASS, same as every other
   button on the site. */
.pinned-cta .wp-element-button {
	font-size: var(--wp--preset--font-size--small);
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
	padding: 0.7em 1.1em;
	min-height: 44px;
	display: inline-flex;
	align-items: center;
	text-decoration: none;
}
@media (min-width: 768px) {
	.pinned-cta {
		display: none;
	}
}

/*
 * Dark-context focus ring (GRAFT FROM A, build-spec §4 "Interactive-element
 * invariants"): default focus ring is teal-deep (7.83:1 white / 7.26:1
 * paper / 6.99:1 tint — all far past the 3:1 UI bar). On navy hero/door/
 * pinned-CTA surfaces, teal-deep itself is a poor ring color against navy,
 * so this scopes a light ring (base/white, 17.10:1 on navy) to exactly the
 * navy-ground interactive elements the porch introduces.
 */
.hero-half.education :focus-visible,
.door-card.education:focus-visible,
.fold-card.education:focus-visible,
.pinned-cta :focus-visible {
	outline: 3px solid var(--wp--preset--color--base);
	outline-offset: 2px;
}

/*
 * O2 — reading-preference control (K2 lock disposition 2, D-b). Shipped in
 * the shared theme/parts/footer.html so every page carries it. Literata is
 * the no-JS default; toggling adds .reading-sans to <html>
 * (reading-preference.js), which overrides the BODY font-family custom
 * property only — headings use a separate --heading custom property and
 * are intentionally untouched, keeping the T2 "no second emphasis face"
 * decision intact. The footer's ground is `aqua` (light), so the button
 * uses the site's light-ground focus-ring color (teal-deep), matching the
 * porch's documented on-light/on-dark focus-ring split.
 */
html.reading-sans {
	--wp--preset--font-family--body: var(--wp--preset--font-family--ui);
}
.reading-preference {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--wp--preset--spacing--20, 0.5rem);
	font-family: var(--wp--preset--font-family--ui);
	font-size: var(--wp--preset--font-size--small);
}
.reading-preference__label {
	color: var(--wp--preset--color--contrast);
}
.reading-preference__btn {
	font-family: inherit;
	font-size: inherit;
	line-height: 1;
	background: transparent;
	border: 1px solid var(--wp--preset--color--contrast);
	color: var(--wp--preset--color--contrast);
	border-radius: 999px;
	padding: 0.4em 0.9em;
	cursor: pointer;
	/* A6-3 (2026-07-17, remaining-pages design audit): bumped 32px -> 44px,
	   the house tap-target bar (design-addendum research cited above:
	   "sub-44px height is the dominant tap-target failure"). Padding
	   already accommodated this; only min-height changed. */
	min-height: 44px;
}
.reading-preference__btn:hover {
	background-color: var(--wp--preset--color--base);
}
.reading-preference__btn:focus-visible {
	outline: 2px solid var(--wp--preset--color--teal-deep);
	outline-offset: 2px;
}

/*
 * ADV-1 (K4) — site-wide horizontal-overflow guard, catch-up spec §7
 * (docs/design/2026-07-17-sitewide-catchup-SPEC.md). The porch's two
 * hand-authored full-bleed sections (.porch-hero, .porch-doors, above) use
 * 100vw + negative margins instead of core alignfull; core alignfull itself
 * can also exhibit a ~½-scrollbar horizontal overflow on classic
 * (space-consuming) scrollbars. Rather than touch the frozen porch
 * (untouchable in the catch-up) or retrofit every alignfull section, one
 * global ancestor rule immunizes the whole site.
 *
 * overflow-x: clip (NOT hidden) does not establish a scroll container and
 * does not force overflow-y to `auto` the way `hidden` would (clip is
 * exempt from that computed-value coercion) — so `position: sticky`
 * ancestors keep working. This is required by the porch's pinned mobile
 * booking CTA (.pinned-cta, position: sticky). If this rule is ever
 * suspected of breaking sticky behavior, move it off html/body as a last
 * resort — never switch to `hidden`.
 *
 * Standardization note: all patterns promoted in this catch-up (§5) and any
 * future new sections use core alignfull, never hand-rolled 100vw. The
 * porch's 100vw idiom is the sole exception, is frozen, and is now
 * protected by this rule.
 */
html,
body {
	overflow-x: clip;
}

/*
 * ============================================================================
 * A6 — shared header/footer tap-target sizing (2026-07-17, remaining-pages
 * design audit, owner-approved)
 * ============================================================================
 * WCAG 2.5.8 AA floor is 24x24px (with an inline-text exception); the
 * porch's own bar and the design-addendum research cited above ("sub-44px
 * height is the dominant tap-target failure") set 44px as the house
 * standard. These are shared parts, so one fix each covers every page.
 * Icon/glyph sizes are unchanged in every rule below — hit areas only.
 */

/* A6-1: mobile hamburger button (parts/header.html wp:navigation
   overlayMenu:"mobile") measured 24x24px — the primary mobile nav trigger.
 *
 * FIX (header-overhaul commission, 2026-07-30): this rule used to also set
 * `display: inline-flex` unconditionally. Root-cause diagnosis (Playwright,
 * live staging /porch-v2/): Gutenberg's own block-library CSS
 * (wp-content/plugins/gutenberg/build/styles/block-library/navigation/
 * style.min.css) ships `.wp-block-navigation__responsive-container-open
 * { display: flex; }` unconditionally, THEN hides it above the mobile
 * breakpoint with `@media (min-width: 600px) { ...:not(.always-shown)
 * { display: none; } }` — both selectors are two-class specificity (0,2,0).
 * This theme stylesheet loads AFTER the block-library stylesheet, so this
 * rule's own unconditional `display: inline-flex` won every tie and
 * permanently forced the hamburger button visible at EVERY width, including
 * desktop/tablet where the full nav row already renders beside it (owner
 * report: "looks oddly placed" — it floated in the header's empty upper
 * band, unrelated to the nav row beneath it) and, when clicked, opened the
 * full-viewport mobile overlay modal redundantly over the already-visible
 * desktop nav (owner report: "opens oddly" — confirmed live: clicking it at
 * 1280px opens the same full-screen navy dialog the mobile overlay uses).
 * Deleting the display override here restores Gutenberg's own responsive
 * switch (hamburger only <600px; full inline nav >=600px, matching the
 * `overlayMenu:"mobile"` block attribute, which was already correct and not
 * the cause). Sizing properties are unaffected — they're inert while the
 * button is display:none, so nothing here regresses the A6 tap-target fix. */
.site-header .wp-block-navigation__responsive-container-open {
	min-width: 44px;
	min-height: 44px;
	align-items: center;
	justify-content: center;
}

/*
 * A6-5 (header-overhaul commission item 1, 2026-07-30): header nav items
 * inside the mobile overlay/narrow inline row measured only ~22-36px tall
 * live (Playwright) — under the 44px bar the commission explicitly asks for
 * in the mobile overlay. The old A6-4 comment above assumed the header nav
 * didn't need this treatment because it's "a large overlay on mobile," but
 * that assumed the overlay's own rows were already touch-sized; they
 * weren't measured at the time and turned out not to be. Scoped to <768px
 * (the same range the hamburger/overlay is the primary interaction mode,
 * per the A6-1 fix above) so the desktop/tablet mouse-driven inline nav row
 * — already verified clean at 1280/987/768 — is untouched. Same technique
 * as A6-4's footer fix.
 */
@media (max-width: 767px) {
	.site-header .wp-block-navigation-item__content {
		display: inline-flex;
		align-items: center;
		min-height: 44px;
		padding-block: var(--wp--preset--spacing--20, 0.5rem);
	}
}

/* A6-2: footer social icons (parts/footer.html wp:social-links
   is-style-logos-only) measured 24x24px. */
.site-footer .wp-block-social-link a {
	min-width: 44px;
	min-height: 44px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

/* A6-4: footer secondary-nav links (parts/footer.html wp:navigation) measured
   ~22px tall. These wrap in a flex row and are present at all widths (unlike
   the header nav, which is desktop-mouse-only >=768 and a large overlay on
   mobile), so the inline-text exception is debatable — standalone nav items
   get the same 44px bar. Modestly increases footer nav row height —
   acceptable, matches intent. */
.site-footer .wp-block-navigation .wp-block-navigation-item__content {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	padding-block: var(--wp--preset--spacing--20);
}

.footer-legal__link {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
}

/*
 * ============================================================================
 * Crisis-line 988/911 tap-target — padding + negative-margin technique
 * (2026-07-17, owner complaint fix: "the spacing feels weird where the words
 * call and text are")
 * ============================================================================
 * Root cause (measured live): the previous `display:inline-block;
 * min-width:44px; min-height:44px; line-height:44px; padding:0 0.5em` rule
 * turned the short words "call"/"text" into 58px-wide, 44px-tall boxes,
 * which broke the sentence's natural word spacing around them — the boxed
 * words no longer sat in the sentence the way plain inline text does.
 *
 * Fix: replace the box-sizing approach with the padding + negative-margin
 * hit-area technique. The link stays inline text (no min-width/min-height/
 * fixed line-height — vertical margin is a no-op on inline elements in every
 * browser, so it never affects the line box; only the horizontal margin
 * does real work here, clawing back part of the horizontal padding so
 * neighboring words are not pushed far apart), so it keeps the sentence's
 * natural flow.
 *
 * Values tuned against LIVE measurement, not just arithmetic (2026-07-17):
 * the footer instance renders its paragraph at fontSize:"small" (14px,
 * `patterns/crisis-line.php`), the porch's near-contact instance at the
 * default body size — the SAME em-based padding therefore yields two
 * different pixel results, and the footer's smaller context is the binding
 * constraint. Iterated live via Playwright `addStyleTag` before finalizing:
 * 0.7em/0.55em (the first draft) measured only 39.6x40px on the footer's
 * "call"/"text" — under the 44px bar on both axes. 0.9em vertical / 0.7em
 * horizontal padding (with -0.9em/-0.5em margin, same ~0.2em net horizontal
 * give as the original draft) measured 45.2px tall x 44.6-47.1px wide on the
 * footer instance (smallest case) and 52.8px x 51-76px on the near-contact
 * instance (larger font, comfortably over) — both instances now clear
 * >=44x44px for every crisis word ("call"/"text"/"call 911").
 *
 * Contrast is unchanged by this fix (see the U2 crisis-line block comment
 * above): contrast/navy (#03174B) text/links on tint (#EAF4F5) = 15.27:1 —
 * PASS (>=4.5:1 body-text bar; >=3:1 UI bar for the tel:/sms: links).
 *
 * Serves both instances of `.crisis-line` — the sitewide footer instance
 * (parts/footer.html) and the porch's own near-contact instance
 * (templates/page-porch.html #Contact).
 *
 * Follow-up (2026-07-17): the reading-preference toggle (html.reading-sans,
 * see the O2 comment further down) swaps the footer's small-size paragraph
 * onto the UI font (Karla) instead of the default body serif (Literata).
 * Same font-size, different font metrics — measured live the Karla/small
 * combination as the new binding constraint at 43.3x42.2px, under the 44px
 * bar on both axes even though the serif/small case above cleared it.
 * Padding bumped 0.9em/0.7em -> 1.05em/0.8em (margin bumped in lockstep to
 * -1.05em/-0.6em, holding the same 0.2em net horizontal give per side so the
 * sentence spacing is unchanged) clears >=44x44 in both the serif and sans
 * reading modes.
 */
.crisis-line a[href^="tel:988"],
.crisis-line a[href^="sms:988"],
.crisis-line a[href^="tel:911"] {
	display: inline;
	min-width: 0;
	min-height: 0;
	line-height: inherit;
	padding: 1.05em 0.8em;
	margin: -1.05em -0.6em;
	vertical-align: baseline;
}

/*
 * ============================================================================
 * Resources — Children's Books two-column list (R-2, 2026-07-17)
 * ============================================================================
 * Core's own columns-block mobile stacking breaks at max-width:781px, one
 * pixel short of the 768px breakpoint this build is specced and verified
 * against ("two columns >=768, collapsing at 375"). The block carries
 * `isStackedOnMobile:false` (renders `is-not-stacked-on-mobile`) to opt out
 * of core's 781px auto-stack entirely, and this rule replaces it at the
 * exact 768px line instead.
 *
 * `isStackedOnMobile:false` also renders under core's newer flex layout
 * engine (`is-layout-flex`), which sets `flex-wrap: nowrap` on the columns
 * wrapper via its own generated `wp-container-core-columns-is-layout-*`
 * rule (live-verified: no wrapping happened at 375 with flex-basis:100%
 * alone — both columns just squeezed to equal width side by side instead of
 * stacking). `flex-wrap: wrap` on the wrapper itself is required alongside
 * the per-column `flex-basis: 100%` for the stack to actually occur.
 *
 * Specificity note (live-verified, second iteration): a single-class
 * `.resources-books-columns{flex-wrap:wrap !important}` was still beaten —
 * core's generated container rule is ALSO `!important` at equal (single-
 * class) specificity, so the tie went to whichever loads later in the
 * cascade (core's inline global-styles block, injected after this
 * stylesheet). Using the compound selector below (the same real
 * `wp-block-columns` class already on the element, stacked with our own)
 * raises specificity above the tie so this rule wins outright regardless of
 * load order.
 */
@media (max-width: 767px) {
	.wp-block-columns.resources-books-columns {
		flex-wrap: wrap !important;
	}
	.resources-books-columns > .wp-block-column {
		flex-basis: 100% !important;
	}
}

/*
 * ============================================================================
 * Porch fix A — grief hero eyebrow renders centered, not left-aligned
 * (2026-07-17, owner-directed)
 * ============================================================================
 * Cause (measured live): WordPress's constrained-layout class injection
 * lands `is-layout-constrained wp-container-core-group-is-layout-*
 * wp-block-group-is-layout-constrained` on the `.hero-half.grief` div (the
 * first inner tag of the page's main wp:group), so its children get
 * `margin-inline: auto !important` from core's constrained-layout styles.
 * The short eyebrow ("Grief & Loss Support") shrink-wraps to its content
 * width (~189px) and centers under that auto margin; the education side's
 * longer lines fill the available width so the same rule has no visible
 * effect there — only the grief eyebrow looks centered.
 *
 * Fix: a scoped reset that wins over the injected `!important` auto
 * margins on both hero halves (not just grief, so a future WP core change
 * that also lands the injection on education's children doesn't silently
 * regress it). page-porch.html itself is untouched — CSS-only.
 *
 * Do not "clean this up" by removing the rule if the injected classes ever
 * disappear from a future core version — re-check live markup first; the
 * override is inert (matches nothing to fight) if the injection is gone,
 * but removing it prematurely reintroduces the centering bug the moment the
 * injection returns.
 */
.porch-hero .hero-half > * {
	margin-left: 0 !important;
	margin-right: 0 !important;
}

/*
 * ============================================================================
 * porch-v2 staging preview (theme/templates/page-porch-v2.html), 2026-07-27
 * ============================================================================
 * Isolated staging-only preview build at /porch-v2/, authorized by Michael.
 * The accepted porch (page-porch.html) and front-page.html are byte-untouched
 * by this addition — every rule below is additive and scoped to new classes.
 * Source of truth: scratch/artifacts/porch-v2-mockup/porch-v2.html (round 3,
 * owner-reviewed) + docs/design/2026-07-24-porch-v2-mockup-plan.md. Same
 * theme.json tokens as the accepted porch (paper/base/tint/teal/teal-deep/
 * contrast="navy"/aqua) — zero new tokens, zero gold, Literata+Karla only.
 * No essential motion anywhere below, so no prefers-reduced-motion query is
 * needed — nothing to gate (same reasoning as the mockup's own comment).
 *
 * Two sections deliberately reuse EXISTING accepted-porch classes verbatim
 * (zero new CSS needed for them, not repeated below): the mobile
 * fold-chooser (.fold-chooser/.fold-card, unchanged design, just moved
 * earlier in this template's source order) and the two-door band
 * (#Services .porch-doors/.door-grid/.door-card, unchanged design, only the
 * heading text differs). The blog preview and contact/crisis sections reuse
 * the accepted porch's real wp:query loop and wp:jetpack/contact-form block
 * markup directly in the template — no CSS impact either way.
 *
 * Contrast ratios below are carried from the mockup's own verified
 * contrast-audit comment block at the top of porch-v2.html (same figures,
 * same relative-luminance formula, matching the already-shipped
 * page-porch.html documentation):
 *   contrast (navy) on teal    = 11.82:1 PASS (hero H1/subhead/body text on
 *                                              the full-bleed teal hero
 *                                              ground)
 *   teal-deep       on teal    =  5.41:1 PASS (hero eyebrow, hero-h1 em word,
 *                                              hero-service-links hover,
 *                                              quiet-CTA — closest margin on
 *                                              the page, still clears AA)
 *   base            on contrast = 17.10:1 PASS (services-bar text, chip
 *                                              hover state, focus ring)
 *   contrast        on tint    = 15.27:1 PASS (support-band ground)
 *   teal-deep       on paper   =  7.26:1 PASS (kickers, chip-muted label)
 *   contrast        on base    = 17.10:1 PASS (chip text at rest)
 * All pairs clear the 4.5:1 AA text minimum (3:1 UI-component minimum for
 * the focus ring) with margin.
 */

/* Full-bleed breakout for the new hand-authored full-width bands — same
   technique as the accepted porch's .porch-hero/.porch-doors rule above,
   kept as a fully separate additive rule so that existing rule is never
   touched. */
.hero-v2,
.services-bar-v2,
.bio-band,
.support-band,
.household-band {
	box-sizing: border-box;
	width: 100vw;
	position: relative;
	left: 50%;
	right: 50%;
	margin-left: -50vw;
	margin-right: -50vw;
}

/* ================= HERO (teal ground, copy left / photo right) ================= */
.hero-v2 {
	background-color: var(--wp--preset--color--teal);
	padding: var(--wp--preset--spacing--50, 2.5rem) var(--wp--preset--spacing--40, 1.5rem);
}
.hero-inner {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--50, 2.5rem);
}
@media (min-width: 768px) {
	.hero-v2 {
		padding: var(--wp--preset--spacing--60, 4rem) var(--wp--preset--spacing--50, 2.5rem);
	}
	.hero-inner {
		flex-direction: row;
		align-items: center;
		gap: var(--wp--preset--spacing--60, 4rem);
		max-width: 1200px;
		margin: 0 auto;
	}
}
.hero-copy { flex: 1 1 55%; }
.hero-photo-wrap { flex: 1 1 40%; display: flex; justify-content: center; }
/*
 * Owner-reported defect fix (2026-08-02): WordPress's injected constrained-
 * layout CSS lands `is-layout-constrained` on the page's outer `main`
 * wp:group, whose `margin-inline: auto !important` rule cascades onto every
 * descendant that has its own max-width set — here `.hero-subhead` (40ch)
 * and `.hero-service-links` (44ch) — auto-centering them ~130px right of
 * the H1/eyebrow/CTA row, which stay flush-left because they have no
 * max-width to center against. Same fix class as the accepted porch's
 * `.porch-hero .hero-half > *` reset above: a scoped `margin-left/right: 0
 * !important` beats the injected auto margins. page-porch-v2.html shares
 * this exact `.hero-v2 .hero-copy` markup (same class names), so this rule
 * fixes both templates without touching either template file.
 */
.hero-v2 .hero-copy > * {
	margin-left: 0 !important;
	margin-right: 0 !important;
}
.eyebrow {
	text-transform: uppercase;
	letter-spacing: 0.12em;
	font-family: var(--wp--preset--font-family--ui);
	font-weight: 700;
	font-size: 0.78rem;
	color: var(--wp--preset--color--teal-deep);
	margin: 0 0 1.1rem;
}
.hero-h1 {
	font-size: clamp(2rem, 4.6vw, 2.85rem);
	line-height: 1.15;
	margin: 0 0 1rem;
}
.hero-h1 em { font-style: italic; color: var(--wp--preset--color--teal-deep); }
.hero-subhead {
	font-size: 1.08rem;
	max-width: 40ch;
	margin: 0 0 1.75rem;
}
.hero-ctas {
	display: flex;
	align-items: center;
	gap: 1.25rem;
	flex-wrap: wrap;
	margin: 0 0 2rem;
}
.hero-ctas .wp-block-buttons { margin: 0; }
/* mockup's pill-shaped CTAs (border-radius 999px) differ from theme.json's
   sitewide square button default (border-radius 0) — scoped override, color/
   hover behavior still inherits the theme.json button defaults unchanged
   (navy/base at rest, teal-deep/base on hover — same pair already verified
   sitewide, not re-derived here). */
.btn-cta-v2 .wp-block-button__link {
	border-radius: 999px;
	padding: 0.9em 1.7em;
	min-height: 44px;
	white-space: nowrap;
}
/*
 * Ship-gate fix B1 (2026-08-02): the 2026-08-02 owner pick below turned
 * "See What to Expect" into an outline pill and had `.bio-cta` (the bio-card
 * "Free 15-minute phone consultation" links) reuse the same unscoped
 * .btn-quiet-v2 class. The pill's `white-space: nowrap` at ~287.8px wide
 * doesn't fit `.bio-body`'s 194-254px column at 900-1152px viewports —
 * overflowed the card and collided with the adjacent card at 960/1080
 * (body.scrollWidth 1117 @1080px). Fix: restore the original underlined-
 * text-link `.btn-quiet-v2` as the base rule (verbatim pre-pill styles),
 * and move the pill-only declarations to `.hero-ctas .btn-quiet-v2` so only
 * the hero's "See What to Expect" gets the pill treatment. `.bio-cta` keeps
 * its `margin-top: auto` bottom-anchoring (approved) and regains its old
 * `padding-left: 0` half-padded-text-link override, restored further below.
 */
.btn-quiet-v2 {
	display: inline-flex;
	align-items: center;
	color: var(--wp--preset--color--teal-deep);
	font-family: var(--wp--preset--font-family--ui);
	font-weight: 600;
	font-size: 0.85rem;
	text-decoration: underline;
	text-underline-offset: 0.25em;
	padding: 0.9em 0.3em;
	min-height: 44px;
}
.btn-quiet-v2:hover, .btn-quiet-v2:focus-visible { color: var(--wp--preset--color--contrast); }
/*
 * Owner pick (2026-08-02), hero-scoped: "See What to Expect" is an outline
 * pill — same border-radius (999px) and vertical padding rhythm as
 * .btn-cta-v2 so the two read as siblings, but stays visually lighter
 * (transparent fill, thin border) so it never competes with the solid navy
 * Book pill. Colors carried from the K2/O1 contrast ledger already
 * documented in this file: teal-deep on teal (hero ground) = 5.41:1 PASS at
 * rest; the hover/focus fill uses the same base-on-teal-deep pair (7.83:1
 * PASS) already used sitewide for .btn-header-cta/.chip hover states —
 * never teal-deep text on navy (hard fail, forbidden by the K2 lock).
 * A2 fix (2026-08-02 ship-gate): box-sizing:border-box added so the pill's
 * rendered height matches sibling .btn-cta-v2 (which inherits border-box
 * from core's .wp-block-button__link) instead of ballooning ~25px taller
 * under content-box sizing.
 */
.hero-ctas .btn-quiet-v2 {
	justify-content: center;
	box-sizing: border-box;
	background-color: transparent;
	border: 2px solid var(--wp--preset--color--teal-deep);
	border-radius: 999px;
	text-decoration: none;
	/* vertical padding trimmed from .btn-cta-v2's 0.9em to 0.8em to offset
	   this pill's 2px border (which .btn-cta-v2 doesn't have) — live-measured
	   (Playwright, 2026-08-02) to land within ~2px of .btn-cta-v2's rendered
	   height at 1440px. */
	padding: 0.8em 1.7em;
	white-space: nowrap;
}
.hero-ctas .btn-quiet-v2:hover, .hero-ctas .btn-quiet-v2:focus-visible {
	background-color: var(--wp--preset--color--teal-deep);
	color: var(--wp--preset--color--base);
}
.hero-service-links {
	display: flex;
	flex-direction: column;
	gap: 0.65rem;
	border-top: 1px solid var(--wp--preset--color--teal-deep);
	padding-top: 1.25rem;
	max-width: 44ch;
}
.hero-service-links h2 {
	margin: 0;
	font-size: 1.05rem;
	font-weight: 400;
}
.hero-service-links a {
	color: var(--wp--preset--color--contrast);
	text-decoration: none;
	border-bottom: 1px solid transparent;
}
.hero-service-links a:hover, .hero-service-links a:focus-visible {
	color: var(--wp--preset--color--teal-deep);
	border-color: var(--wp--preset--color--teal-deep);
}
/* SECTION: pinned-CTA overlap bugfix (narrow phone heights) */
@media (max-width: 480px) {
	.hero-v2 { padding-top: 0.5rem; }
	.eyebrow { margin-bottom: 0.4rem; }
	.hero-h1 { margin-bottom: 0.35rem; }
	.hero-subhead { margin-bottom: 0.4rem; }
	.hero-ctas { margin-bottom: 0.5rem; }
}
/* Owner pick (2026-08-02): tighter cream padding so the photo fills more of
   the frame ("modest fill"). Shared by the hero photo and the bio-card
   photos (`.photo-frame.bio-photo`) — both read better with less visible
   cream border, so the change is intentionally not hero-scoped. */
.photo-frame {
	background-color: var(--wp--preset--color--paper);
	padding: 0.35rem;
	border-radius: 26px;
	border: 1px solid var(--wp--preset--color--teal);
	box-shadow: 0 18px 40px rgba(3, 23, 75, 0.10);
	max-width: 320px;
}
.photo-frame img { border-radius: 20px; width: 100%; height: auto; display: block; }
/* hero context: deepen the frame border for definition against the teal
   ground (bio-band reuse of .photo-frame keeps the lighter default border
   above, since it sits on a paper ground instead). */
.hero-photo-wrap .photo-frame { border-color: var(--wp--preset--color--teal-deep); }
.bio-photo { max-width: 160px; margin: 0 0 0.4rem; }

/* ================= SERVICES BAR (navy strip under the hero) ================= */
.services-bar-v2 {
	background-color: var(--wp--preset--color--contrast);
	padding: 0.85rem 1.5rem;
}
.services-bar-list {
	max-width: 1200px;
	margin: 0 auto;
	text-align: center;
	font-family: var(--wp--preset--font-family--ui);
	font-size: 0.88rem;
	font-weight: 600;
	color: var(--wp--preset--color--base);
}
/* Ship-gate fix B2 (2026-07-28): these links sit inside a <p>
   (page-porch-v2.html SECTION 3), so the sitewide "p a:not(.wp-element-
   button)" rule (0,1,2 specificity, ~line 444) was matching and painting
   them teal-deep — 2.18:1 on this bar's navy background, same failing
   pair the K2 O1 navy-links comment above already documents as a hard
   fail (teal-deep on navy is never used for text, only the light `teal`
   token or `base`). Add the same ":not(.wp-element-button)" guard used by
   that navy-links rule (K2 O1, ~line 265) to raise this selector to
   (0,2,1), which beats (0,1,2) on class-count alone — restores base/white
   at 17.10:1 on navy, matching every other navy-ground link on the site. */
.services-bar-list a:not(.wp-element-button) { color: var(--wp--preset--color--base); text-decoration: none; border-bottom: 1px solid transparent; }
.services-bar-list a:hover, .services-bar-list a:focus-visible { border-color: var(--wp--preset--color--base); }

/* ================= BIO BAND (glanceable bio cards) ================= */
.bio-band {
	background-color: var(--wp--preset--color--paper);
	padding: var(--wp--preset--spacing--50, 2.5rem) 1.5rem;
}
.bio-grid {
	/* mirrors .door-grid's own max-width/gap so each owner sits directly
	   beneath their door column above */
	max-width: 72rem;
	margin: 0 auto;
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--wp--preset--spacing--50, 2.5rem);
}
.bio-card {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 0.55rem;
	text-align: left;
	border-top: 3px solid transparent;
	padding-top: 1.1rem;
}
.bio-body { display: flex; flex-direction: column; align-items: flex-start; gap: 0.55rem; flex: 1; min-width: 0; }
/* B1 fix (2026-07-30 K4 re-gate): the 2-column breakpoint used to fire at
   768px, but the row-layout card's min-content floor (photo 160px + 1.4rem
   gap + 2rem side padding + text) is ~372px per card, wider than the
   768-899px track's fair share, so the card clipped 23px off the right edge
   at 768 and .bio-body collapsed to ~101px. Raised to 900px so 768-899
   stays single-column (readable), and grid-template-columns now uses
   minmax(0, 1fr) so the grid item's automatic min-content floor is
   removed as a second, independent guard against the same clip. */
@media (min-width: 900px) {
	.bio-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--wp--preset--spacing--40, 1.5rem); }
	/* match .door-card's 2rem inner padding so photo/text edges align with
	   the door headings above; photo left, text beside it */
	.bio-card { padding-left: 2rem; padding-right: 2rem; flex-direction: row; gap: 1.4rem; align-items: flex-start; min-width: 0; }
	.bio-photo { flex: 0 0 160px; }
	.bio-body { align-self: stretch; }
}
/* K4 re-gate advisory A5, adopted (2026-07-30): .bio-body still read tight
   (~143px) from 900px up to the desktop 1024px+ range where it settles at
   ~205px. Shrinking the photo column in this narrower band only lets
   .bio-body pick up the freed track space without touching the >=1080px
   desktop rule above (or the sub-900px single-column layout below), so
   1280px stays byte-identical to before this fix. */
@media (min-width: 900px) and (max-width: 1080px) {
	.bio-photo { flex: 0 0 120px; }
}
/* Owner pick (2026-08-02): bigger photo column at desktop widths. The base
   `.bio-photo { max-width: 160px; }` rule above still applies at every
   narrower width (mobile stack and the 900-1080px band), so max-width is
   raised here too, not just flex-basis — otherwise the frame would stay
   capped at the old 160px ceiling even though its flex-basis grew. */
@media (min-width: 1080px) {
	.bio-photo { flex: 0 0 210px; max-width: 210px; }
}
/* O1 (2026-07-30 estate-banding review, Amendment 12, owner-approved): the
   grief rule deepens to teal-deep for equal visual weight with the edu
   card's navy rule; wing semantics (grief vs. edu color) are unchanged. */
.bio-card.bio-grief { border-top-color: var(--wp--preset--color--teal-deep); }
.bio-card.bio-edu { border-top-color: var(--wp--preset--color--contrast); }
.bio-card p, .bio-card .is-style-kicker { margin: 0; }
.bio-descriptor {
	font-family: var(--wp--preset--font-family--ui);
	font-size: 0.85rem;
	color: var(--wp--preset--color--teal-deep);
	margin: -0.35rem 0 0;
}
/* Ship-gate fix B1 (2026-07-28): opacity:0.75 blended teal-deep to an
   effective #568182 on paper (#FAF6EE) = 4.01:1, failing the 4.5:1 small-
   text bar — and opacity dims the child <a> too, since it's a rendering
   property, not a color override. Fixed with a solid color instead: plain
   teal-deep (already used one line up on .bio-descriptor, and already
   PASSING at 7.26:1 on paper per the palette-extension comment near the
   top of this file) on the whole line, anchor included via the sitewide
   "p a:not(.wp-element-button)" rule — no blending, no opacity. */
.bio-credential { font-size: 0.85rem; line-height: 1.4; color: var(--wp--preset--color--teal-deep); }
.bio-line { font-size: 1.05rem; margin: 0; }
/*
 * Owner pick (2026-08-02): chips move from bottom-anchored
 * (`margin: auto 0 0.35rem`) to sitting directly under the bio text, on
 * both cards. Only `.bio-cta` (the "Free 15-minute phone consultation"
 * link) stays bottom-anchored across the card pair, so it now carries the
 * `margin-top: auto` that used to live here. `.bio-chips` just needs its
 * old top spacing kept as a plain (non-auto) value so it still reads as a
 * discrete group under the last line of bio copy.
 */
.bio-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0; padding-top: 0.35rem; }
/* B1 fix (2026-08-02 ship-gate): restores the pre-pill half-padded-text-link
   override (`.btn-quiet-v2` is back to an underlined link by default, see
   above) alongside the approved bottom-anchoring. */
.bio-cta { margin-top: auto; padding-left: 0; }
/*
 * Michael's three chips ("Dyslexia · Reading · Spelling & Writing") were
 * live-measured (Playwright, 2026-08-02) against `.bio-body`'s available
 * width at the desktop widths this build targets; they wrapped to 2+1
 * instead of one row once the bio-photo column grew to 210px (see the
 * `.bio-photo` ≥1080px rule below), which ate into `.bio-body`'s share of
 * the card. Tightened only at ≥1080px (font-size untouched, stays at the
 * 0.8rem accessibility floor) so the sub-1080 2+1 wrap (explicitly
 * acceptable per spec) is unaffected.
 */
@media (min-width: 1080px) {
	.bio-chips .chip {
		padding: 0.5em 0.75em;
	}
}

/* ================= SUPPORT BAND ("Other Ways We Can Help" chips) ================= */
.support-band {
	background-color: var(--wp--preset--color--tint);
	padding: var(--wp--preset--spacing--40, 1.5rem);
}
.support-band .is-style-kicker { margin-bottom: 1.5rem; }
.chip-grid {
	max-width: 900px;
	margin: 0 auto;
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.75rem;
}
.chip {
	font-family: var(--wp--preset--font-family--ui);
	font-size: 0.85rem;
	font-weight: 600;
	padding: 0.6em 1.1em;
	border-radius: 999px;
	text-decoration: none;
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
	border: 1px solid var(--wp--preset--color--teal-deep);
}
.chip:hover, .chip:focus-visible { background-color: var(--wp--preset--color--teal-deep); color: var(--wp--preset--color--base); }

/* Dark-context focus ring, additive: same technique as the accepted porch's
   equivalent rule further up this file (base/white ring on navy-ground
   interactive elements), scoped to the new v2 navy-ground surface only —
   the v2 template's other navy-ground elements (.door-card.education,
   .fold-card.education, .pinned-cta) already inherit the existing rule
   unchanged, since those classes are reused verbatim from the accepted
   porch. */
.services-bar-v2 a:focus-visible {
	outline: 3px solid var(--wp--preset--color--base);
	outline-offset: 2px;
}

/* ================= DOORS INTRO + PRESS LINE (v2 additions) =================
 * Both reuse the already-verified teal-deep-on-paper pair (7.26:1 PASS,
 * documented above) rather than deriving a new ratio. */
.doors-intro {
	max-width: 56ch;
	margin: 0.5rem auto 0;
	color: var(--wp--preset--color--teal-deep);
	font-size: 0.95rem;
}
.press-line {
	max-width: 72rem;
	margin: 2rem auto 0;
	color: var(--wp--preset--color--teal-deep);
	font-size: 0.85rem;
	font-style: italic;
}

/* SECTION: household band */
.household-band {
	background-color: var(--wp--preset--color--base);
	padding: var(--wp--preset--spacing--50, 2.5rem) 1.5rem;
}
.household-inner {
	max-width: 640px;
	margin: 0 auto;
	text-align: center;
}
.household-inner p {
	margin: 1rem 0 0;
	color: var(--wp--preset--color--contrast);
}

/*
 * Unbox product page (/unbox/): 17-box card grid (12 grief + 5 feelings)
 * reuses the base/tint card chrome already established for program-style
 * cards elsewhere (padding/border/radius) under a new class name, plus a
 * signature block for the grief-wing Star + Walk With Grief line (K3 doc
 * §8/§18; master geometry star-clean-balanced.svg, fill="currentColor" —
 * recolored via `color` below, no new contrast pair to verify since it
 * reuses the sitewide teal-deep-on-base pairing already in use for kickers
 * and inline links).
 */
.unbox-grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: var(--wp--preset--spacing--30, 1.5rem);
}
@media (max-width: 782px) {
	.unbox-grid {
		grid-template-columns: repeat(2, 1fr);
	}
}
@media (max-width: 480px) {
	.unbox-grid {
		grid-template-columns: 1fr;
	}
}
/*
 * C3 (2026-07-30 estate-banding review, Amendment 12): border tint -> teal.
 * Required so the white cards stay legible once C4 swaps the Unbox Your
 * Feelings ground from tint to base (white card + tint border on a white
 * ground is a near-invisible edge); teal reads on both base and tint grounds.
 */
.unbox-card {
	padding: var(--wp--preset--spacing--30, 1.5rem);
	background-color: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--teal);
	border-radius: 4px;
}
.unbox-card h3 {
	margin-top: 0;
	margin-bottom: 0.4em;
}
.unbox-card h3 a {
	color: var(--wp--preset--color--contrast);
	text-decoration: none;
	border-bottom: 1px solid var(--wp--preset--color--teal-deep);
}
.unbox-card h3 a:hover,
.unbox-card h3 a:focus-visible {
	color: var(--wp--preset--color--teal-deep);
}
.unbox-card p {
	margin: 0 0 0.5em;
}
.unbox-card p:last-child {
	margin-bottom: 0;
}

.unbox-signature {
	text-align: center;
	margin-top: var(--wp--preset--spacing--40, 2rem);
	padding-top: var(--wp--preset--spacing--30, 1.5rem);
	border-top: 1px solid var(--wp--preset--color--teal);
}
.unbox-signature__star {
	display: inline-block;
	width: 40px;
	color: var(--wp--preset--color--teal-deep);
}
.unbox-signature__star svg {
	display: block;
	width: 100%;
	height: auto;
}
.unbox-signature__kicker {
	margin: var(--wp--preset--spacing--20, 0.75rem) 0 0.5em;
	font-family: var(--wp--preset--font-family--ui);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--wp--preset--color--teal-deep);
}
.unbox-signature__line {
	margin: 0;
	font-style: italic;
	font-size: var(--wp--preset--font-size--medium);
	color: var(--wp--preset--color--contrast);
}

/* ================= HEADER "Book a Consultation" BUTTON (sitewide) =================
 * Batch 4 ask 20 + Amendment 2/3 item 2 — both owners YES, header CTA on
 * every page. Destination /#Contact matches the existing sitewide nav
 * "Contact" link above (parts/header.html) and is the same section the
 * porch-v2 pinned mobile CTA and hero CTA point to (#Contact) — made
 * absolute so it resolves correctly from any page, not just porch-v2.
 *
 * The header's own ground is navy (contrast); the sitewide default button
 * (navy bg / base text, theme.json) would vanish against it, so this reuses
 * the white-bg/navy-text-at-rest pair already established for navy-ground
 * buttons elsewhere on the site (.pinned-cta .wp-element-button above) —
 * base on contrast = 17.10:1 PASS, same pair, no new ratio to derive.
 *
 * Ship-gate fix A1 (2026-07-28): the comment used to claim hover "inherits
 * the sitewide theme.json button:hover rule unchanged." That was false —
 * `.btn-header-cta .wp-block-button__link` (0,2,0) beats the compiled
 * theme.json hover selector, so rest and hover rendered identically (white
 * bg / navy text both states, no hover feedback at all). Fixed below with
 * an explicit local :hover rule, same pattern as .chip:hover and
 * .btn-quiet-v2:hover elsewhere in this file: teal-deep bg / base text,
 * base on teal-deep = 7.83:1 PASS, matching the K2 O1 sitewide hover pair.
 *
 * Deliberately smaller/quieter than the hero's pill CTA (small font, tight
 * padding, square corners) so it reads as a persistent nav utility action
 * rather than a second competing primary next to a page's own hero CTA
 * (no-second-primary check, Amendment 2 build caution).
 */
.header-nav-group {
	column-gap: var(--wp--preset--spacing--40, 1.5rem);
	/* row-gap tightened 0.5rem -> 2px, header-slimming fix (2026-07-30) —
	   see the SECTION comment above .header-brand-row for the full context. */
	row-gap: 2px;
	/* the Navigation block's own box reserves extra height above its visible
	   link row (room for the Services submenu panel), so centering this
	   group vertically (the block editor's default) leaves the button
	   floating above the actual nav text instead of level with it — align
	   to the shared bottom edge instead, where the nav links actually sit. */
	align-items: flex-end;
}
.header-cta-wrap { margin: 0; display: flex; flex-wrap: wrap; gap: var(--wp--preset--spacing--20, 0.5rem); }
.btn-header-cta .wp-block-button__link {
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
	font-size: var(--wp--preset--font-size--small);
	padding: 0.55em 1.1em;
	min-height: 44px;
	display: inline-flex;
	align-items: center;
	white-space: nowrap;
}
/* Explicit local hover (ship-gate fix A1, 2026-07-28) — see the comment
   block above for why the sitewide theme.json button:hover rule doesn't
   reach this button. base text on teal-deep = 7.83:1 PASS. */
.btn-header-cta .wp-block-button__link:hover {
	background-color: var(--wp--preset--color--teal-deep);
	color: var(--wp--preset--color--base);
}
/* Same navy-ground light focus ring already used sitewide for dark-context
   interactive elements (see the .pinned-cta / door-card.education rule
   above) — the default teal-deep ring is a poor match against the navy
   header, so this button gets the same base/white override. */
.btn-header-cta .wp-block-button__link:focus-visible {
	outline: 3px solid var(--wp--preset--color--base);
	outline-offset: 2px;
}

/*
 * SECTION: header "Call" secondary CTA (Amendment 11 commission item 3,
 * 2026-07-30). Bryna's business number (tel:+19725594943). Book stays
 * PRIMARY (white fill, above); Call is SECONDARY — an outline/ghost
 * treatment so it never competes with Book for visual weight. Rest state:
 * transparent fill, base/white border + base/white text — base on contrast
 * = 17.10:1 PASS (already-computed sitewide pair, reused). Hover: FILLS
 * with teal-deep instead of putting teal-deep on the text, per the K2 lock
 * (teal-deep text on navy is a hard fail at 2.18:1 — the lock forbids
 * teal-deep as TEXT on navy, not as a background); base text on teal-deep
 * fill = 7.83:1 PASS, same hover pair as .btn-header-cta/.chip/.btn-quiet-v2
 * elsewhere in this file, so hover feedback is consistent sitewide.
 */
.btn-header-call .wp-block-button__link {
	background-color: transparent;
	color: var(--wp--preset--color--base);
	border: 2px solid var(--wp--preset--color--base);
	font-size: var(--wp--preset--font-size--small);
	padding: calc(0.55em - 2px) calc(1.1em - 2px);
	min-height: 44px;
	display: inline-flex;
	align-items: center;
	white-space: nowrap;
}
.btn-header-call .wp-block-button__link:hover,
.btn-header-call .wp-block-button__link:focus-visible {
	background-color: var(--wp--preset--color--teal-deep);
	border-color: var(--wp--preset--color--teal-deep);
	color: var(--wp--preset--color--base);
}
.btn-header-call .wp-block-button__link:focus-visible {
	outline: 3px solid var(--wp--preset--color--base);
	outline-offset: 2px;
}

/*
 * REMOVED (Amendment 13 resolution, option (a), 2026-07-30): the header CTA
 * subtext line ("Prefer to talk? Call to schedule.") and its
 * `.header-cta-subtext` rule are gone. Joint call (Michael, who originally
 * commissioned the line, + Bryna): with the Call button's own label already
 * self-explanatory, the line was redundant and read as clutter once the CTA
 * pair sat flush right — dropping it both declutters and slims the header
 * further (see the right-alignment SECTION above for the re-measured
 * height and the scroll-margin-top re-sync below it). The wp:paragraph
 * block is deleted from parts/header.html; no other selector referenced
 * `.header-cta-subtext`, so removing the rule here is a clean deletion, not
 * a stub.
 */

/*
 * SECTION: desktop/tablet header right-alignment (header-CTA-right commission,
 * 2026-07-30). Owner report: the CTA pair "reads as off" — live-diagnosed via
 * Playwright as a genuine Gutenberg bug, not a misconfigured attribute.
 * `.header-nav-group` and `.header-cta-col` both carry
 * `layout.justifyContent: "flex-end"` in parts/header.html and both render the
 * matching `is-content-justification-flex-end` class, but the computed style
 * for BOTH resolves to `justify-content: normal` — Gutenberg's generated
 * per-block layout rule for their shared style hash
 * (`.wp-container-core-group-is-layout-306f870f`) only ever emits
 * `align-items: center`, and no generic `.is-content-justification-flex-end`
 * fallback rule is loaded on this site to catch the gap (confirmed absent
 * from every loaded stylesheet, live). Net effect: nav + CTA buttons default
 * to flex-start and pack flush LEFT under the logo instead of flush right —
 * exactly the "off" look reported. This is not fixable by editing the block
 * attribute (already correct); it has to be asserted directly in CSS, same
 * class of fix as the sticky-header outer-tag bug above.
 *
 * FIX: `.header-nav-group` (parts/header.html: wraps <nav> + .header-cta-col)
 * is unboxed with `display: contents` at >=768px — its own broken
 * justify-content no longer matters once it has no box; <nav> and
 * .header-cta-col become direct flex children of `.header-brand-row`
 * instead, whose OWN justify-content: space-between is NOT affected by the
 * bug above (verified live: it's a different generated hash, computes
 * correctly) and reliably pins the last item on each line to the row's right
 * edge. `order` promotes .header-cta-col ahead of <nav> in the effective
 * layout sequence (nav is first in DOM/tab order — unchanged, screen-reader
 * users still meet the nav links before the buttons) so line 1 packs
 * logo + CTA pair (flush left / flush right via space-between); <nav> is
 * given `flex-basis: 100%`, which can never share a line with anything else,
 * so it always wraps to its own full-width line 2 — centered via its own
 * internal `justify-content` (overriding Gutenberg's working-but-now-wrong
 * "right" alignment on <nav> itself; (0,2,0) beats the compiled
 * `.items-justified-right` utility class at (0,1,0)).
 *
 * This structurally satisfies the "nav collides with right-aligned buttons"
 * concern the commission brief raised for 987px: nav and the CTA pair are
 * never on the same line to begin with, at any of the three desktop widths
 * tested (1280/987/768), so there is no collision to resolve — nav simply
 * always owns its own centered row underneath. Verified live: nav renders as
 * a single unwrapped line at all three widths (651px of links vs. 720px+
 * available at the narrowest, 768px), no horizontal overflow.
 *
 * `!important` is required on the `display: contents` override — Gutenberg's
 * own generic `body .is-layout-flex { display: flex; }` rule is
 * (0,1,1)-specificity, one level above a bare `.header-nav-group` class
 * selector (0,1,0), so a plain override silently loses the cascade (live
 * A/B'd: without `!important` the group never unboxes and .header-cta-col
 * stays trapped flush-left inside it, same bug as above one level down).
 *
 * Net result measured live: header shrinks from ~139.9px to ~86.4px at
 * 1280/987/768 (two short rows instead of three — the Amendment 13
 * subtext-removal above accounts for the last ~19px of that drop, on top of
 * the ~105.8px this restructure alone produced) — see the scroll-margin-top
 * re-sync below. <768px is untouched: this whole block is scoped
 * `min-width: 768px`, and below that .header-cta-col is already
 * `display: none` (existing rule below), leaving header-nav-group's box
 * governing just the hamburger/inline nav exactly as before.
 */
@media (min-width: 768px) {
	.header-nav-group {
		display: contents !important;
	}
	.header-nav-group > .header-cta-col {
		order: 1;
	}
	.header-nav-group > nav.wp-block-navigation {
		order: 2;
		flex-basis: 100%;
		justify-content: center;
	}
}

/*
 * SECTION: single-row header unification (2026-07-30, header-unification
 * commission). Owner directive: ONE cohesive row wherever it fits, logo
 * left, nav spanning the middle, CTA pair right, instead of always
 * dropping nav to its own second line the moment the two-row rule above
 * kicks in at 768px. This is a THIRD state, layered on top of the two
 * above it (mobile hamburger below 768px, two-row fallback 768-1299px),
 * active only once there is genuinely enough width to avoid a cramped fit.
 *
 * BREAKPOINT DERIVATION (measured live via Playwright, not estimated):
 * with the enlarged 192px logo (~48.2px tall, see the site-logo SECTION
 * above), the nav's own natural content width (6 visible links, Counseling
 * submenu + Dyslexia & Reading Tutoring, the two mobile-only nav-cta-*
 * items excluded via their existing >=768px display:none) is 651.4px, and
 * the CTA pair is 397.9px. A live pixel-by-pixel sweep (viewport width
 * held constant, header content-box measured) found the bare-minimum
 * viewport where logo, nav, and CTA pair fit on one line with the owner's
 * required 24px minimum clearance on each side of the nav block: 1337px
 * still wraps, 1338px is the first clean single-row width. This matched
 * the arithmetic (192 + 24 + 651.4 + 24 + 397.9 + 48px of site-header
 * side padding = 1337.3) almost exactly. 1360px was used below instead of
 * this bare-minimum figure, giving enough headroom to clear
 * font-rendering/sub-pixel variance across browsers (the wrap edge is
 * fragile, as the hard 1337/1338 snap shows) and landing the very common
 * 1366px laptop width inside the single-row treatment rather than just
 * outside it. This was higher than the "~1150-1250px" estimate in the
 * original commission brief; that estimate assumed the pre-upgrade 120px
 * logo, and enlarging the logo to fill the header (SECTION above) added
 * ~72px to the row's required width, which is why the real breakpoint
 * landed further out.
 *
 * RE-DERIVED (Amendment 15, 2026-07-31, nav label + reorder): "Services"
 * became "Counseling" and "Dyslexia & Reading Tutoring" moved to 2nd
 * position (label text unchanged, reorder alone does not change total nav
 * width). "Counseling" is wider than "Services", so the nav's natural
 * width grew 651.4px -> 673.8px (+22.4px). Re-swept live the same way:
 * bare-minimum single-row width moved from 1338px to 1360px (1359px still
 * wraps), matching the arithmetic (192 + 24 + 673.8 + 24 + 397.9 + 48 =
 * 1359.7) almost exactly again.
 *
 * The Amendment 15 addendum also commissioned a conditional shortening
 * ("Dyslexia & Reading Tutoring" -> "Dyslexia & Reading") gated on whether
 * it brought the bare-minimum fit down to <=1280px. Measured (same live
 * sweep, label swapped in the DOM before re-measuring): nav width drops to
 * 597.1px and the bare-minimum single-row width drops to 1284px (1283px
 * still wraps) — a real 76px improvement, but 4px short of the <=1280px
 * threshold the owner set, so per the owner's own rule ("if the
 * measurement shows only cosmetic gain, keep the full label") the
 * shortening was NOT taken. Label stays "Dyslexia & Reading Tutoring" in
 * parts/header.html; the breakpoint below is derived from the full-label
 * 1360px bare minimum, not the rejected 1284px figure.
 *
 * Because the new bare minimum (1360px) already sat at the OLD
 * breakpoint value, keeping 1366px (the common laptop width) inside
 * single-row mode was no longer feasible at a safe margin: 1366px would
 * give only 6px of slack over the 1360px bare minimum, well under the
 * ~15px floor this section already treats as the safety line against
 * font-rendering/sub-pixel variance (the exact problem the original
 * 1338->1360 headroom was chosen to avoid). This was reported rather than
 * forced (no font-size or padding shrunk to try to reclaim 1366px) — see
 * the RE-DERIVED AGAIN paragraph below for how the owner resolved it.
 *
 * RE-DERIVED AGAIN (Amendment 15 addendum 2, 2026-08-01, owner-confirmed
 * option (b)): the conditional shortening above was NOT rejected after
 * all — the owner reviewed the 1284px/1280px 4px-miss and the 1366px
 * infeasibility together and chose to take the shortening anyway,
 * because its real value was never the 1280px target, it was recovering
 * 1366px. "Dyslexia & Reading Tutoring" -> "Dyslexia & Reading" shipped
 * in parts/header.html (nav label only; the URL and every other
 * occurrence of "Tutoring" sitewide — page title, in-body links,
 * functions.php nav-menu name — are untouched). Fresh live sweep on the
 * actual deployed change (not simulated) confirmed the earlier estimate
 * exactly: nav width 597.1px, bare-minimum single-row width 1284px
 * (1283px still wraps), matching the arithmetic (192 + 24 + 597.1 + 24 +
 * 397.9 + 48 = 1283.0) almost exactly.
 *
 * New breakpoint: 1300px, 16px of clearance over the 1283px bare minimum
 * (within the ~15-20px safety-margin convention used throughout this
 * section). Both 1366px and 1440px now render single-row again with
 * comfortable slack (66px and 156px respectively) — the 1366px recovery
 * that motivated taking the shortening. Verified live: no wrap/overlap at
 * 1299/1300/1301, 1366, 1440, 1280, 987, 768; axe 0 violations on
 * porch-v2 at 1280/375; keyboard and mobile-overlay behavior unchanged
 * (unaffected by label width).
 *
 * MECHANISM: `.header-brand-row` carries WP's `alignwide`, which caps it at
 * `var(--wp--style--global--wide-size)` (1120px, confirmed live via the
 * compiled `.alignwide{max-width:...}` global-styles rule) regardless of
 * viewport width. This is what silently defeats a single row even on very
 * wide screens if left in place (live A/B'd: without this override the row
 * still wraps ctaCol onto its own line past 1120px of available content,
 * since logo+nav+cta together always exceed 1120px). `max-width: none`
 * lets the row use the header's own available width instead (viewport
 * minus the site-header's 48px side padding), which is what the 1300px
 * figure above is actually measuring against. `!important` is required;
 * live-tested without it first (same class of fix as the header-CTA-right
 * SECTION above): a plain override loses to the compiled alignwide rule
 * despite equal single-class specificity, so `!important` is not
 * decorative here.
 *
 * Reorders `.header-nav-group`'s two unboxed children (see the
 * `display: contents` restructure above, still in effect) so nav sits
 * BETWEEN the logo and the CTA pair instead of after both: nav drops its
 * two-row-only `flex-basis: 100%` (which would otherwise force it onto its
 * own line no matter how much room is available) back to auto-sizing at
 * its natural width (597.1px with the shortened label), and moves to
 * `order: 1`, ahead of the CTA pair's `order: 2`, so the DOM's tab order
 * (nav links, then the two buttons, unchanged from every other state)
 * matches the visual order too. With all three of `.header-brand-row`'s
 * children (site-logo, nav,
 * header-cta-col) now normal flex-basis items on one line, the row's own
 * `justify-content: space-between` (already present, driving the two-row
 * state's logo/CTA split above) automatically splits the leftover width
 * into two equal gaps, one on each side of the nav block, which both
 * satisfies the "spaced/spanning across... in a unified manner" brief and
 * guarantees the 24px-minimum clearance grows, never shrinks, as the
 * viewport widens past 1300px. No vertical-alignment CSS is needed here:
 * `.header-brand-row`'s existing `align-items: center` (from its own
 * `verticalAlignment: center` block attribute) already centers all three
 * items on their shared line once they're no longer stacked, confirmed
 * live, matching the logo's own box exactly since the enlarged logo is now
 * the tallest of the three.
 *
 * Header height in this mode measures ~64.2px live (16px padding plus the
 * 48.2px logo, the row's tallest child), well under the two-row
 * fallback's ~90.6px and the ~90px cap the SIZE requirement targeted,
 * since single-row mode has no second line to add height. This height is
 * constant across every width >=1300px (space-between only redistributes
 * horizontal gap, not height), so it never becomes the tallest sticky
 * state; the two-row fallback below still governs the scroll-margin-top
 * resync (see that SECTION further down).
 */
@media (min-width: 1300px) {
	.header-brand-row {
		max-width: none !important;
	}
	.header-nav-group > nav.wp-block-navigation {
		order: 1;
		flex-basis: auto;
		justify-content: flex-start;
	}
	.header-nav-group > .header-cta-col {
		order: 2;
	}
}

/*
 * SECTION: mobile-only Book/Call nav items (Amendment 11 commission item 3).
 * parts/header.html adds two extra wp:navigation-link entries after
 * "Contact" (className nav-cta-book / nav-cta-call) so the hamburger
 * overlay and the narrow inline nav row (the ~600-767px band where
 * Gutenberg renders the full nav inline with no hamburger, see the A6-1 fix
 * comment above) both carry the Book + tap-to-call actions once the
 * standalone header-cta-col hides below 768px. Hidden here at >=768px so
 * desktop/tablet never shows them twice (once as real buttons, once as nav
 * text) — inherits the header nav's own uppercase/teal/base styling
 * unchanged, no new color to verify.
 */
@media (min-width: 768px) {
	.site-header .nav-cta-book,
	.site-header .nav-cta-call {
		display: none;
	}
}

/* Mobile: the header nav already wraps below ~987px (round 2b finding) even
 * without this button, and porch-v2 separately carries its own sticky
 * bottom "Book a Consultation" bar (.pinned-cta) for phone widths. Rather
 * than risk worsening the header's known narrow-width wrap fragility (or
 * duplicating the pinned bar's job on porch-v2), the header CTA pair is
 * tablet/desktop-only; phone visitors reach both actions via the
 * nav-cta-book/nav-cta-call items above (hamburger overlay <600px, inline
 * nav row 600-767px), plus the pinned bar on porch-v2.
 *
 * CORRECTION (2026-07-30, header-overhaul commission): the old comment here
 * claimed this 767px threshold "matches the Navigation block's own
 * mobile-overlay breakpoint." Live diagnosis for the A6-1 hamburger fix
 * (above) found that's not accurate — Gutenberg's actual overlay breakpoint
 * is 600px, not 767px. This 767px value is a separate, deliberate design
 * choice (keep the button pair off the header until there's comfortable
 * width for two buttons + logo + nav), not a mirror of the nav's own
 * breakpoint; left at 767px unchanged since nav-fit testing (Amendment 11
 * item 3) found no overlap/wrap at 768px and up with both buttons present.
 *
 * !important + the extra .header-cta-wrap qualifier are both needed here:
 * core's own inline block-support CSS ships an unconditional
 * ".wp-block-buttons > .wp-block-button { display: inline-block }" at
 * two-class specificity, which otherwise beats a bare ".btn-header-cta"
 * hide rule (one class) regardless of source order or the media query.
 */
@media (max-width: 767px) {
	.header-cta-col { display: none !important; }
}

/*
 * ============================================================================
 * Dyslexia hub v2 rebuild (/dyslexia-tutoring/), 2026-07-29
 * ============================================================================
 * Three-card variant of the U8 program-card grid above (which is fixed at
 * repeat(2, 1fr)). Reuses .program-card's existing padding/border/radius
 * chrome unmodified — this modifier only changes the column count, so a
 * future edit to card chrome still lands in one shared place. Collapses to
 * a single column at the same 767px breakpoint already used sitewide (see
 * the header-CTA rule directly above), matching the existing grid's own
 * single-step collapse pattern rather than introducing an intermediate
 * two-column tablet state.
 */
/*
 * SECTION: hero photo frame (2026-07-30 design-review fix, item 3f).
 * page-dyslexia-tutoring.html's hero image (className hero-photo-frame)
 * had lost its mock treatment (a 6px white inset frame + a thin teal ring
 * around it) somewhere along the way. Padding on the figure creates the
 * white frame (the figure has no background of its own otherwise, so the
 * hero's paper ground would show through without this); the 1px teal
 * border is the ring. Decorative border/frame only — no text contrast to
 * verify.
 */
.hero-photo-frame {
	padding: 6px;
	background-color: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--teal);
}

/*
 * SECTION: rail-align-left (2026-07-30 design-review fix, item 3d — "rail
 * stagger"). page-dyslexia-tutoring.html's §2 intro group (kicker heading +
 * paragraphs above the three-card grid) and §6 kicker heading (Who's
 * Teaching Your Child) are un-aligned "constrained" blocks, so they center
 * on the page's contentSize (680px) instead of sharing the wide grid's own
 * left edge — live measured x=300 vs the grid's x=80. A plain margin
 * override can't fix this: WP's own constrained-layout centering compiles
 * with !important, so beating it needs !important too (confirmed via a
 * live A/B: an unprefixed override was silently dropped). The formula below
 * reproduces exactly the centering math WP uses for align:wide children
 * (half the gap between the available width and --wide-size, floored at
 * 0 once the viewport is narrower than wide-size) — so it tracks the grid's
 * edge at every breakpoint instead of a fixed pixel value that would only
 * be correct at 1280px. Verified live: matches the grid's x at
 * 1280/987/900/768/375 (768px and down, both sit flush at the shared
 * padding edge, x=24, since wide-size no longer exceeds the viewport).
 */
.rail-align-left {
	margin-left: max(0px, calc((100% - var(--wp--style--global--wide-size, 1120px)) / 2)) !important;
}

.program-card-grid.is-three-col {
	grid-template-columns: repeat(3, 1fr);
	gap: 28px;
}
@media (max-width: 900px) {
	.program-card-grid.is-three-col {
		grid-template-columns: 1fr;
	}
}

/*
 * SECTION: education-banding restoration (2026-07-30 design-review fix,
 * item 3a). Scoped to THIS three-col grid only — the sitewide .program-card
 * rule above (base bg / tint border) stays untouched for any other page
 * still using the two-col grid. Restores the approved mock treatment: paper
 * ground (warmer than plain white, matches the wing's paper/tint/base
 * banding elsewhere on this hub) + a teal border + a 5px teal-deep top
 * rule as the card's accent. Text stays the sitewide navy default, so
 * contrast/navy on paper = 15.86:1 (same pair already computed for
 * .program-card__badge above) — PASS, no new ratio to derive.
 */
.program-card-grid.is-three-col .program-card {
	background-color: var(--wp--preset--color--paper);
	border-color: var(--wp--preset--color--teal);
	border-top: 5px solid var(--wp--preset--color--teal-deep);
}

/*
 * SECTION: fee chip (2026-07-30 design-review fix, item 3b). Each card's
 * "Sessions & fee" paragraph (page-dyslexia-tutoring.html, className
 * fee-chip) gets a tint-background chip treatment per the mock, instead of
 * running as plain paragraph text at the bottom of the paper-ground card.
 * navy (contrast, the sitewide default text color) on tint = 15.27:1 —
 * PASS, well clear of the 4.5:1 text bar.
 */
.program-card-grid.is-three-col .program-card .fee-chip {
	background-color: var(--wp--preset--color--tint);
	border-radius: 3px;
	padding: 0.85rem 1rem;
	margin-top: var(--wp--preset--spacing--30, 1.5rem);
}

/*
 * ============================================================================
 * SECTION: sticky header (Amendment 11 commission item 2, 2026-07-30)
 * ============================================================================
 * Desktop/tablet (>=768px) only. The header's own ground is already solid
 * navy (has-contrast-background-color, parts/header.html) so content never
 * ghosts through while stuck; z-index kept well under the mobile overlay
 * dialog's 100000 (Gutenberg-assigned, verified live) and the pinned-cta
 * bar's 80 — the two never coexist with the sticky header anyway (pinned-cta
 * is display:none >=768px, this rule is scoped to exactly that width and up)
 * but a low, boring value avoids any future stacking surprise.
 *
 * SELECTOR IS THE OUTER TAG, NOT `.site-header` (live-diagnosed root cause,
 * Playwright scroll test): every template includes the header via
 * `<!-- wp:template-part {"slug":"header","tagName":"header"} /-->`, which
 * WordPress wraps in a real `<header class="wp-block-template-part">`
 * element around parts/header.html's own markup — so the actual DOM is
 * `.wp-site-blocks > header.wp-block-template-part > .site-header (this
 * file's block)`. Per the CSS positioned-layout spec, a position:sticky
 * element can only move within the bounds of ITS OWN CONTAINING BLOCK
 * (formed by the nearest block-container ancestor's content box, regardless
 * of that ancestor's own position value). `.site-header`'s containing block
 * is the `<header>` wrapper — which is auto-sized to EXACTLY `.site-header`'s
 * own height (it has no other content), giving zero room to stick; the
 * child hits both edges of its containing block simultaneously and just
 * scrolls away with the page, computed style still reporting
 * `position: sticky` the whole time (confirmed live: this looked like a
 * silent no-op, not a broken/overridden rule). Putting the sticky
 * declaration on `header.wp-block-template-part` instead gives it
 * `.wp-site-blocks` (the whole ~4700px page) as its containing block, which
 * has plenty of room — confirmed live via Playwright (forced inline-style
 * test, then shipped as the real CSS fix): header now stays pinned to the
 * viewport top through scrollY 2000. Visually identical either way — the
 * one-and-only child (.site-header) fills the sticky box completely, so its
 * navy background/shadow-adjacent edge lines up exactly with the stuck box.
 *
 * `overflow-x: clip` on html/body (ADV-1 above) is NOT the culprit here (a
 * live A/B test with that rule unchanged, moving only the sticky declaration
 * between the two elements, isolated the containing-block explanation
 * above) and continues to preserve sticky as documented.
 *
 * MOBILE DECISION (<768px): NOT sticky, left as default static/in-flow.
 * Reasoning: porch-v2 already ships a persistent bottom booking bar
 * (.pinned-cta, sticky bottom) on mobile; stacking a second sticky chrome
 * element at the top would cost real viewport height on small phones for
 * two permanent bars sandwiching the content ("double-chrome" the
 * commission brief warned against), and non-porch pages built by the
 * education/dyslexia hub have no bottom bar to justify the trade either way,
 * so a single sitewide mobile rule is simplest: no top-sticky header on
 * phones. The header itself was not slim enough to make a no-cost exception
 * (measured live at 1280px: ~223px tall with the new Book+Call button pair
 * and subtext row — well over the "~64px or it's not worth it" bar from the
 * brief), so the slim-header carve-out doesn't apply here.
 *
 * CORRECTION (2026-07-30, header-slimming fix): the desktop/tablet header
 * is now ~139px (see .header-brand-row's SECTION comment above), which
 * would clear a "~64px" bar even less than before — but the double-chrome
 * reasoning above is about the MOBILE header specifically, and mobile's own
 * `.header-cta-col { display: none }` rule (below 768px) means the
 * mobile header was already just logo + hamburger, not the full 223px/139px
 * figure quoted above. This desktop-scoped slimming pass doesn't change the
 * mobile sticky decision; left as-is.
 *
 * FIX (2026-07-31, owner-reported admin-bar overlap): logged-in users saw the
 * sticky header's top edge slide under #wpadminbar on scroll, because top: 0
 * pins to the viewport top with no regard for the admin bar sitting above it.
 * WordPress core sets --wp-admin--admin-bar--height on the html element only
 * when the admin bar renders (logged-in), so top: var(...) with a 0px
 * fallback clears the bar when logged in and is byte-identical to top: 0 when
 * logged out (the var is simply undefined, fallback wins). Core's admin bar
 * is position: fixed at >=783px viewport width and position: absolute below
 * that (it scrolls away with the page at small logged-in widths); this rule
 * only runs >=768px anyway and the var approach degrades acceptably in that
 * narrow 768-783px logged-in band without further special-casing.
 */
@media (min-width: 768px) {
	header.wp-block-template-part {
		position: sticky;
		top: var(--wp-admin--admin-bar--height, 0px);
		z-index: 40;
		box-shadow: 0 1px 0 rgba(3, 23, 75, 0.12);
	}
}

/*
 * SECTION: anchor scroll-margin under the sticky header (Amendment 11
 * commission item 2). Only needed >=768px, the only range the header is
 * actually sticky — below that the header scrolls away with the page like
 * normal, so an anchor jump already lands correctly with no header to hide
 * behind. Value matches the header's measured height at 1280/987/768px
 * (was ~139px after the header-slimming fix, 2026-07-30 — margin was 150px);
 * #Services, #About, #Contact (parts/header.html nav targets, plus
 * front-page/porch-v2 section ids) are the anchors this protects live on the
 * site today. Verified live via Playwright (click #Services, confirm the
 * target's top edge is not covered by the sticky header's bottom edge).
 *
 * RE-SYNCED (header-CTA-right commission, 2026-07-30): the right-alignment
 * fix above (.header-nav-group `display: contents` restructure) collapses
 * the header from three stacked rows to two; the same-day Amendment 13
 * resolution (subtext line removed, SECTION above) trims it further. Header
 * measured live at ~86.4px at 1280/987/768 (was ~139.9px before either
 * change) — margin dropped 150px -> 96px (~10px safety, same margin ratio as
 * the prior value) to match.
 *
 * RE-SYNCED AGAIN (header-unification commission, 2026-07-30): the logo
 * enlargement (120px -> 192px, see the site-logo SECTION above) makes the
 * logo taller than the 44px-min-height CTA buttons that used to govern row
 * 1's height, growing the TWO-ROW fallback (768-1299px, below the
 * single-row breakpoint) from 86.4px to 90.6px live-measured. The new
 * single-row state (>=1300px, SECTION above) is shorter, 64.2px, a
 * constant regardless of viewport width since it has no second line, so
 * the two-row fallback remains the tallest sticky state across the whole
 * >=768px sticky range and is what this margin must clear. 96px -> 101px
 * (same ~10px-safety ratio as the prior resync: 90.6 * ~1.11 = 101).
 *
 * Amendment 15 (2026-07-31) moved the single-row breakpoint 1360px ->
 * 1380px (nav-rename re-derivation, SECTION above); Amendment 15 addendum
 * 2 (2026-08-01) moved it again, 1380px -> 1300px (shortened-label
 * re-derivation). Neither move touched the two-row fallback's own height
 * (90.6px), which depends on stacked-row content height, not nav label
 * width, so this scroll-margin-top value did not need to move either
 * time.
 */
@media (min-width: 768px) {
	[id] {
		scroll-margin-top: 101px;
	}
}

/*
 * SECTION: footer-gutter fix (C1, 2026-07-30 estate-banding review,
 * Amendment 12). WordPress's default block-theme spacing gives
 * `.wp-site-blocks > footer` (the footer template-part wrapper) a
 * `margin-block-start` equal to the theme's blockGap, which showed up live
 * as a ~24px white gutter between the last section's ground color and the
 * footer on every one of the 19 pages — visible even when the last section
 * is navy (e.g. grief-counseling's capstone), where it read as a stray white
 * stripe. Zeroing it here is sitewide and page-agnostic (no per-template
 * fix needed); verified live on grief-counseling (navy-ending) and /unbox/
 * with the gap script (mainBot === footTop after this ships).
 */
.wp-site-blocks > footer {
	margin-block-start: 0;
}
