/*
 * base.css — GravityStack Starting Point
 *
 * Shared composition layer, common to every GravitySite fleet build: the
 * theme half of each pattern in the patterns plugin, the card rules, and the
 * shared .u-* utilities. Cards are not component classes; their rules are
 * *Card styles* in gravitysite-design-system-standards.md, with values in the
 * card contract in gravitysite-design-system-constants.md (both in ops).
 *
 * Token values live in theme.json, never here. The child's theme.json carries
 * one site's values (presets, the `rounded`, `tracking`, `weight`, and `color`
 * custom families); the parent's carries settings, styles that only name
 * tokens, and the fleet values this file reads as custom properties. Both are
 * written by design-to-theme-json, and neither carries custom CSS, because a
 * child's custom CSS replaces the parent's: fleet CSS rules live in this file
 * (theme system Contract B). The Spectra One alias block that used to sit in
 * the child's `styles.css` retired at theme system Ship 2 with the parent it
 * contained.
 *
 * 2026-08-11: first content added — three interactive-state rules that
 * theme.json cannot express (see the named section below). Corrected at
 * 4.58.0: this header pointed at a Components section the design file does
 * not have and said no component classes existed.
 */

/* ── Page layout: the root gutter and section spacing ──────────────────────
 * Fleet CSS rules that used to be generated into each child's theme.json
 * `styles.css`. They moved here at theme system Ship 2, because a child's
 * custom CSS replaces the parent's and neither theme.json carries any. Both
 * stay at `:root:root`, the specificity they had there, so they outrank
 * WordPress's own global-styles rules (0-1-0) and core's layout rules.
 */

/* Root gutter re-assertion. Every constrained group, and the post content,
   keeps the root gutter as its own side padding. WordPress zeroes the side
   padding of a `.has-global-padding` container nested inside another, and this
   rule overrides that, so a constrained group placed directly in the post
   content carries the post content's gutter and its own: the layout every
   GravitySite page has rendered with. Full-width sections are unaffected, since
   WordPress pulls an alignfull block out to the edge and pads its inside.
   It began as Spectra One containment and outlived it: the render comparison
   at theme system Ship 2 found that deleting it moves the plain-width groups
   on a page and nothing else. Whether a nested group should keep a doubled
   gutter is a design question, not a containment one; change it deliberately,
   with a render comparison, never as a tidy-up. A class-conditional selector is
   not expressible in theme.json, which is why this is CSS. */
:root:root .wp-block-post-content.has-global-padding,
:root:root .wp-block-group.has-global-padding {
  padding-right: var(--wp--style--root--padding-right);
  padding-left: var(--wp--style--root--padding-left);
}

/* Section spacing. The page spaces its sections; no section reads its
   neighbour. The two values are the section spacing contract's, in
   gravitysite-design-system-constants.md, emitted by design-to-theme-json into
   the parent's theme.json as `--wp--custom--section--inset` and `--gap`; this
   file is the selector's home.
   The selector carries position and self-knowledge and nothing else: `>` means a
   top-level child of the content container, `.wp-block-group` means the element
   is itself a section. Neither reads a property of any sibling, and no sibling
   combinator belongs in this rule.
   The inset is the whole distance. Two adjacent sections read as inset + inset,
   and the first and last sections' own insets are the header and footer seams —
   except for a flush section, which is excluded below and sits against them.
   The gap is zero so that two filled sections meet edge to edge — a margin
   between them would let the page background show through.
   The Group half of the selector is load-bearing. A top-level child is a section
   only on a page built from sections; on a prose page or a blog post it is a
   paragraph, and a position-only rule insets every one of them.
   The :not() excludes flush sections, which own their own vertical edges. It
   matches on the class because pattern naming puts the whole slug there and
   `flush` is the Edge term. THE EDGE TERM IS LOAD-BEARING HERE — renaming
   `flush` in ops-pattern-standards.md walks this rule.
   Never scope it to `.entry-content`: the editor canvas root carries
   `wp-block-post-content` and not `entry-content`.
   This is a CSS rule rather than two style properties because theme.json has no
   property for padding on a container's top-level children, and a block-level
   blockGap cannot be scoped by class. A platform limit, not a preference. */
:root:root .wp-block-post-content > .wp-block-group:not([class*="-flush-"]) {
  margin-block-start: var(--wp--custom--section--gap);
  padding-block: var(--wp--custom--section--inset);
}

/* ── Interactive state conventions ───────────────────────────────────────
 * Fleet-constant rules from gravitysite-design-system-standards.md,
 * "Interactive state conventions". Each one is inert without CSS because
 * theme.json has no way to scope an element style to "inside prose" or
 * "inside a dark band." All are fleet decisions — see
 * gravitysite-css-standards.md, "Files": a rule wanted on the next client
 * site belongs in base.css, not site.css.
 */

/* 1. In-content prose link underline.
 * Obligation: WCAG 1.4.1 Level A, technique G183. At design v4.0 the
 * link/body differential fails G183's 3:1 bar outright — action-9 measures
 * 2.23:1 against body text, and the hover colour change alone 1.57:1
 * (measured 2026-08-30; the amber-era link scraped by at 3.06:1) — so
 * colour cannot carry this alone, on this palette or on a future client's,
 * and this rule is the whole of the G183 cue.
 *
 * Scope is prose elements, not a container class: links inside p, li, dd,
 * and blockquote are underlined; navigation, breadcrumbs, logos, and
 * card-wrapper links are not, because they are not prose. A linked
 * point-grid-icon label sits in a paragraph but is a card-wrapper link, so both
 * selectors exempt it by name (*Linked cards* in the standard's *Card
 * styles*, v2.36), inside :where() so the rule's specificity does not move.
 * This depends
 * on no class anyone has to remember to apply — do not substitute a
 * container class or a utility class here.
 *
 * `.entry-content` was removed from the selector 2026-08-29. It was a
 * container standing in for a semantic condition, and it held only while
 * all prose lived in post content. A template part carrying a sentence
 * with a link in it — the footer's proof band is the first — sits outside
 * `.entry-content` and received no underline, which is the G183 gap this
 * rule exists to close. The standard already said "prose elements, not a
 * container class"; the implementation had diverged from it.
 *
 * Navigation is excluded by landmark, not by location. A `nav` ancestor is
 * the marker, which is why footer link columns are authored as core Group
 * with `tagName: nav` — semantically correct on its own terms, and the
 * thing that lets this selector tell a nav list from a prose list without
 * an authored class. Core's Navigation block already emits `nav`, so the
 * header needs no change.
 *
 * A HEADING LINK IS NOT PROSE, AND THAT IS THE ONE EXCEPTION THIS RULE
 * CARRIES. Added 2026-09-16. `core/post-template` renders its loop as `<ul>`
 * and `<li>`, so every link in every query loop on this estate has an `li`
 * ancestor and was read as prose by the selector below. Index row titles were
 * the first instance; card grids, related sets, and the Signals index are the
 * same case BY CONSTRUCTION rather than by likelihood.
 *
 * WCAG 1.4.1 / G183 concerns a link WITHIN A BLOCK OF TEXT, distinguishable
 * from the text around it only by colour. A heading is not a block of text. A
 * link that IS a heading is already distinguished by size, weight, and
 * position before colour enters it, so the obligation is SATISFIED by the
 * typography rather than DROPPED. This is a judgement about how an obligation
 * is met, not a decision to stop meeting it. Do not read what follows as a
 * hole in an accessibility rule, and do not "restore" the underline here.
 *
 * THE EXCEPTION IS SEMANTIC. It keys on the heading element and on nothing
 * else — never on a list ancestor, never on `.wp-block-post-template`, never
 * on a component class. Drawn on the list instead, a real prose link inside a
 * query loop's post content would lose its underline on every client site,
 * silently, and the failure would be invisible to anyone not testing for it.
 * `base.css` is a fleet file and that bound is the whole of the risk.
 *
 * WHAT `> a:only-child` ACTUALLY MATCHES, AND THE KNOWN LIMITATION. It matches
 * a link that is a heading's only ELEMENT child. `:only-child` counts elements
 * and IGNORES TEXT NODES, so `<h2>Read our <a>guide</a> today</h2>` — an
 * anchor with text either side of it and no element siblings — ALSO MATCHES
 * AND IS ALSO EXEMPTED. That is a partial link inside a heading losing its
 * underline, and it is stated here as a limitation rather than sold as a
 * protection. An earlier draft of this comment claimed the opposite; the claim
 * was false and is corrected here, because a false claim at an accessibility
 * rule is worse than the gap it describes.
 *
 * THERE IS NO SELECTOR THAT CLOSES IT. "The link is the heading's entire text
 * content" cannot be expressed in CSS at all — there is no text-node
 * condition. The choice is not between this selector and a better one.
 *
 * THE GAP IS UNREACHABLE IN PRACTICE, which is why it is accepted rather than
 * chased into markup or PHP. This rule only ever reaches a heading link that
 * sits inside `p`, `li`, `dd`, or `blockquote`. A heading in post content is a
 * direct child of the content container, so the selector never matched it —
 * measured on the deployed site 2026-09-16,
 * `rule1WouldEverMatchBareHeadingLink: false`, and such a link computes to
 * `none` both before and after this exception existed. The only heading links
 * that do sit inside a list item come from `core/post-title` in a query loop,
 * which renders `<h2 class="wp-block-post-title"><a>…</a></h2>` — always a
 * whole link. So the population the limitation applies to is empty today.
 *
 * `:only-child` IS RETAINED BECAUSE IT STILL DOES REAL WORK. It blocks the
 * exemption when a heading carries a SECOND ELEMENT CHILD — a badge or a chip
 * beside a title being the obvious case — and that link keeps its underline.
 * That is the conservative direction for an accessibility rule: its failure
 * mode is an unexpected underline, which someone sees, rather than a missing
 * one, which nobody does.
 *
 * DELIBERATELY NOT COVERED, and left uncovered rather than absorbed: a
 * button-styled link inside a list item that carries no `.wp-element-button`
 * class. It hits this rule, it wants no underline, and it is neither a
 * heading nor a stretched link — it is findable by its chrome rather than by
 * its typography. That is a different judgement and is to be argued on its
 * own merits.
 *
 * THIS REPLACED A PER-COMPONENT OVERRIDE rather than joining one. `site.css`
 * carried a scoped `text-decoration: none` on the Gravity Hacks index titles
 * from 4.39.0, and it is gone at 4.40.0. Overriding at each consumer requires
 * remembering at every future query loop and getting it wrong once, and two
 * mechanisms producing one result is how the next reader gets a false reading
 * about which one is working.
 *
 * A stretched-link exception was drafted alongside this one and CUT before it
 * shipped. The index row titles are `<h3><a>`, so the heading exception
 * already covers them, which left the stretched-link clause with no consumer
 * — and an exception on a fleet accessibility rule with no consumer is
 * speculative. `.u-stretched-link` survives as a pure stretch utility with no
 * role here; see "Stretched link" below.
 *
 * Specificity, verified live on one.gravitystack.dev: the competing
 * `text-decoration: none` rule ships inside `:where()`, which carries zero
 * specificity. A plain class selector beats it. Do not reach for
 * !important — if a rule appears not to apply, read the merged output
 * before assuming this selector is losing.
 */
:is(p, li, dd, blockquote) a:not(.wp-element-button):not(nav a):not(:is(h1, h2, h3, h4, h5, h6) > a:only-child):not(:where(.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > p > a)) {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
  text-decoration-skip-ink: auto;
}

/* Hover thickens the underline rather than only changing colour. This is
 * the G183 non-colour cue itself, not decoration — do not remove it as
 * cosmetic in a later pass.
 */
:is(p, li, dd, blockquote) a:not(.wp-element-button):not(nav a):not(:is(h1, h2, h3, h4, h5, h6) > a:only-child):not(:where(.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > p > a)):hover {
  text-decoration-thickness: 2px;
}

/* 1b. Dark-band link hover.
 * `styles/band-dark.json` sets the inverse link colour, the underline, and
 * the inverse focus ring, but emits no hover, so a link inside a dark band
 * falls through to theme.json's global `elements.link:hover` — `action-11`
 * since the v4.0 repoint, measured 2026-08-30 at 1.17:1 on
 * gravity-surface-dark. The founding instance was amber (accent-12 at
 * 1.06:1, measured 2026-08-29); the repoint changed the hex and not the
 * fault class, so this rule stays load-bearing across repoints. The link
 * disappears at the moment the reader points at it, which is worse than no
 * hover at all — a state may never be less legible than rest.
 *
 * The standard already rules this: gravity-link-inverse hovers on underline
 * weight, not colour, because it shares its value with gravity-text-inverse
 * and there is no second value to measure. This implements that. Holding the
 * colour is the operative half; the thickening is the cue.
 */
.is-style-band-dark a:not(.wp-element-button):hover {
  color: var(--wp--preset--color--gravity-link-inverse);
  text-decoration-thickness: 2px;
}

/* 1c. Footer nav lists carry no bullet or indent.
 * Scoped by landmark inside a template part rather than by an authored
 * style class, for the same reason rule 1 is scoped by element: a class
 * someone has to remember is a class someone will forget.
 */
.wp-block-template-part nav ul {
  list-style: none;
  padding-left: 0;
}

.wp-block-template-part nav li {
  margin: 0;
}

/* 1d. Navigation links carry no underline, on any surface.
 * `styles/band-dark.json` sets `elements.link.typography.textDecoration:
 * underline` for every link in the band, because theme.json has no way to
 * say "except navigation." That over-applies a rule the standard scopes to
 * prose, and it put an underline under eight footer nav links, the phone
 * number, and the legal row. Removing it conforms to the standard rather
 * than overriding it.
 *
 * Placed after rule 1b deliberately: both selectors compute to (0,2,2) and
 * source order decides. Moving either rule breaks this. If a third rule
 * ever ties here, give this one a real specificity edge instead of relying
 * on position.
 */
.wp-block-template-part nav a:not(.wp-element-button) {
  text-decoration: none;
}

/* Wayfinding hover on the dark band holds the link colour and underlines.
 *
 * THIS RULE TOOK THE BRAND HUE UNTIL 0.4.2, and the hue was the fault. It was
 * written on the strength of `gravity-brand` reading 5.34:1 on
 * `gravity-surface-dark` for GravityStack's own palette, but nothing in the
 * design file's checks holds a child to that: `design-to-theme-json` checks
 * `gravity-link-inverse` on the dark band (4.5:1) and never the brand. The
 * starter's grey brand, `#8d8d8d` on `#333333`, measured 3.81:1 on 2026-10-25
 * by hovering a footer menu link in the local render, under the 4.5:1 AA bar
 * and under the rest state's 12.63:1. A client whose brand is a deep blue or
 * green would fail far worse, to the point of the link vanishing under the
 * pointer, which is rule 1b's fault class one element over. The rule that
 * made it safe was a property of one palette, so it could not be a fleet rule.
 *
 * The fix is rule 1b's, applied to navigation: hold `gravity-link-inverse`,
 * which the generator already checks, and carry the hover in the underline.
 * Nav links have no underline at rest (rule 1d), so a 2px underline appearing
 * is a visible change of state without any colour measure to fail. The
 * brand-hue hover was only ever the cue on the dark band, so it goes with the
 * hue. Light surfaces are unchanged: `action-11` hover is checked by the
 * generator and measures well clear of AA there.
 *
 * `text-decoration-line` is set rather than only the thickness, because rule
 * 1d's `text-decoration: none` on navigation links computes to (0,2,2) and
 * this rule's selector, at (0,3,2), already outranks it; the line must be
 * restated because 1d turned it off.
 *
 * KEPT FROM THE VERSION THIS REPLACES. This selector reaches the footer's
 * plain `core/navigation` anchors and a `core/navigation` block's own links
 * alike, but it was written against the footer, and a navigation block's links
 * only change on hover because rule 2d authors it: core ships no hover of its
 * own, and its doubled `.wp-block-navigation .wp-block-navigation-item__content`
 * class (0,3,0) beats theme.json's link hover at (0,1,1). The standard's open
 * item on hover direction, that a down-ramp walks toward a dark surface rather
 * than away from it, is the general form of this fault and is still open.
 */
.is-style-band-dark nav a:not(.wp-element-button):hover {
  color: var(--wp--preset--color--gravity-link-inverse);
  text-decoration-line: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.15em;
}

/* 1e. Dark-band inverse button hover.
 * `styles/band-dark.json` sets the primary button's rest pair — the white
 * `gravity-cta-inverse` fill carrying `gravity-cta-label-inverse` (7.32:1
 * on the fill; the fill 12.16:1 against the band) — but a band partial
 * carries rest only, so without this rule the button falls through to
 * theme.json's global `elements.button:hover`: an `action-10` fill at
 * 1.38:1 against the band. The primary call to action would dim to
 * near-invisible at the moment of interaction — the same fault class as
 * 1b, on the loudest element in the band. A state may never be less
 * legible than rest.
 *
 * The hover inverts the ordinary button's direction. The fill cannot dim
 * — every violet step measures under 2.4:1 on the band, which is why the
 * button inverts at all — so it tints toward the action hue and the label
 * deepens one step: fill `action-3`, label `action-10`. Measured
 * 2026-08-30: label on fill 7.65:1 (rest 7.32:1 — hover raises label
 * legibility), fill against the band 10.54:1 (rest 12.16:1; both far
 * clear of every bar). Both halves bind a token: the fill takes `action-3`
 * from the primitive scale channel as `--wp--custom--color--action-3`, the
 * label the CTA Hover palette entry. The fill was a hex literal until theme
 * 4.19.0, because a step outside the palette contract had no custom property
 * to bind — which is the break the channel closes. A hex does not move when
 * the design file does, so the rule would have kept this value through a
 * repoint, silently.
 *
 * This lives here rather than in `band-dark.json` because it has to beat
 * a global element style, which a style variation cannot reliably do.
 * theme.json's element styles ship inside `:where()`, so this plain class
 * selector wins without `!important`. Authored against the plain
 * `.is-style-band-dark` class, never the numbered per-instance
 * `is-style-band-dark--N` WordPress also writes — that number is assigned
 * per instance and is not stable.
 *
 * Cards are the deliberate exception: a card is a light ground even
 * inside a dark band, so its button keeps the ordinary CTA pair. The card
 * rules below re-assert that at higher specificity, rest and hover both.
 * The focus ring needs nothing here — `band-dark.json` emits it with its
 * non-zero offset, and the offset is what keeps the white ring measured
 * against the band (12.16:1) rather than against this fill.
 */
.is-style-band-dark .wp-element-button:hover {
  background-color: var(--wp--custom--color--action-3);
  color: var(--wp--preset--color--action-10);
}

/* ── Section heading area ────────────────────────────────────────────────
 *
 * Where a left-aligned heading area sits over wide content. Copy-to-Site
 * Decision 120 (Scott, GravityBrain, 2026-10-19); the rule, its tests, and its
 * field basis are *Section heading area* in
 * `gravitysite-design-system-standards.md`, and the authoring shape is
 * *Constrained width* in `ops-pattern-standards.md`. Not restated here.
 *
 * THE SHAPE THIS READS. A pattern's root is a constrained full-width group. Its
 * first child is the heading area (eyebrow, H2, intro), a group with no align;
 * the content follows as its next sibling. Core caps the heading area at the
 * reading column and centres it, which put it 250px inside wide content at
 * 1440px. Where the next sibling is `alignwide`, the heading area widens to the
 * wide measure so it starts on the content's edge, and its children keep the
 * reading measure.
 *
 * THE ROOT IS FOUND BY ITS CONSTRAINED LAYOUT, NOT BY `alignfull` (6.2.1). The
 * block inserter draws a pattern preview without the root's `alignfull` class,
 * while the editor canvas and the front end keep it, so keyed on `alignfull` the
 * rule never reached a preview, and every preview showed the old inset heading.
 * `is-layout-constrained` is on the root in all three. Checked on the dev site
 * 2026-10-19: the constrained selector matched the same six headings as before
 * in the canvas, and the heading in the preview (GR9-T45).
 *
 * THE WIDE CONTENT IS A LATER SIBLING, NOT NECESSARILY THE NEXT ONE (6.2.2). The
 * editor canvas injects a `<style>` element between the heading area and a
 * flex row that carries per-viewport layout overrides (`point-split`'s
 * stacking), so `+ .alignwide` missed it in the editor while the front end,
 * which has no such element, matched. `~ .alignwide` matches in both; checked
 * on the dev site 2026-10-19 on two `point-split` sections, with every other
 * match unchanged.
 *
 * SPECIFICITY, ON PURPOSE. The widening must beat core's
 * `.is-layout-constrained > :where(...)` cap at (0,1,0). `:first-child` and
 * the `:has()` argument carry it to (0,2,0); the root and the exclusions are
 * wrapped in `:where()` so they add nothing.
 * The children's measure is wrapped whole in `:where()` at (0,0,0), so a
 * pattern's own shorter measure always wins (guarantee-split's 24ch at `bp-1`).
 *
 * WHAT IT LEAVES ALONE, and why each exclusion is here rather than in markup:
 * - A centred heading area (it carries `has-text-align-center`). It centres
 *   over centred content and its placement does not change.
 * - `person-grid`. Its people sit centred on the reading measure whenever they
 *   stack, at Columns 1 and at Columns 2 below the unit floor, and only a pair
 *   side by side starts on the wide edge. Columns writes no class and no
 *   selector can see the side-by-side state, so its heading stays on the
 *   reading column, which is right for every stacked state.
 * - `step-list`, whose list is `alignwide` in markup but capped at the reading
 *   measure here, except Direction: Horizontal from `bp-2`, handled below.
 * A pattern whose first child is itself wide (`hero-split`,
 * `qa-aside-accordion`, and `testimonial-aside-quote`, whose heading area is a
 * wide group placed by its own block) or whose content is at reading width
 * (`text-stack`, `qa-accordion`) never matches.
 *
 * Phones are unaffected: below about 690px the reading column is wider than
 * the content box, and both placements render the same.
 */
:where(.is-layout-constrained[class*="c-"]:not(.c-person-grid):not(.c-step-list)) > :first-child:has(~ .alignwide):where(:not(.alignwide):not(.alignfull):not(:has(.has-text-align-center))) {
  max-width: var(--wp--style--global--wide-size);
}
:where(.is-layout-constrained[class*="c-"]:not(.c-person-grid):not(.c-step-list) > :first-child:not(.alignwide):not(.alignfull):has(~ .alignwide):not(:has(.has-text-align-center))) > * {
  max-width: var(--wp--style--global--content-size);
}
@media (min-width: 1200px) { /* bp-2: step-list's Horizontal list goes wide */
  :where(.c-step-list--horizontal) > :first-child:has(~ ol.alignwide) {
    max-width: var(--wp--style--global--wide-size);
  }
  :where(.c-step-list--horizontal > :first-child:has(~ ol.alignwide)) > * {
    max-width: var(--wp--style--global--content-size);
  }
}

/* Unboxed points hang their padding. Each point carries `spacing|40` padding
 * in markup for a card it does not draw under Cards None, so its text would
 * sit 24px inside the heading's edge, at every width. The set moves toward the
 * start edge by that padding and is not widened: widening moved the grid from
 * two columns to three at 768px, so column counts, track widths, and gaps
 * stay as they are. If the unit padding in `point-grid.php` changes, this
 * custom property changes with it. Boxed treatments draw their edge and are
 * left alone. */
.c-point-grid:not(.c-point-grid--separate):not(.c-point-grid--grouped) > .wp-block-group-is-layout-grid.alignwide {
  --c-point-grid-unit-inset: var(--wp--preset--spacing--40);
  position: relative;
  inset-inline-start: calc(-1 * var(--c-point-grid-unit-inset));
}

/* ── Card styles ─────────────────────────────────────────────────────────
 * Fleet defaults for the two point-grid style variations, per
 * `gravitysite-design-system-standards.md`, "Card styles". Two roles named
 * for the job the section is doing: `cards-separate` where the reader
 * weighs items against each other, `cards-grouped` where the items are a
 * set read straight through. A card grid carrying no style is padding
 * only, which is the pattern's naked state.
 *
 * These are fleet defaults, not GravityStack's taste. Every value is a
 * role token, so a client's own palette carries through with no work. A
 * client whose brand wants something else — a shadow, a gradient, a
 * heavier radius — redeclares the selector in site.css, which depends on
 * this file on the front end and registers after it in the editor canvas.
 * Equal specificity wins on both legs; do not reach for !important.
 *
 * The bar for anything added here: the least opinionated treatment that
 * still does the job. An opinion in this file becomes every client's
 * starting point. If `gravitystack-startingpoint` ever needs to override a card
 * style in its own site.css, the default is wrong and this file is what
 * changes.
 *
 * No hover and no elevation on either role, and this is not an oversight.
 * A card carrying a title and a body has nothing to click. Hover is a
 * false affordance on it and does nothing on touch; a shadow says the
 * surface lifts off the page, which is a cue borrowed from interactive
 * material. Both belong to a linked card variant if one is ever built.
 *
 * Selected as a Pattern Style Option, not a Block Style. WordPress allows
 * one Block Style per block and the band owns that slot; the plugin's
 * Inspector control writes these modifier classes alongside it. Measured
 * 2026-08-25 — see "Card styles" in the design-system standard.
 *
 * Layer 5 rather than Layer 3: these reach the card children from the
 * section class, which is a descendant selector, and `core/group` is
 * present on every page, so block-scoped loading would buy nothing.
 *
 * Both treatments take `gravity-surface-nested` as their fill, per the
 * card contract in `gravitysite-design-system-constants.md`. They differ
 * structurally, not tonally: separate is many surfaces with an edge each,
 * grouped is one surface with none. No colour differentiator sits between
 * them on any band — an amber border on separate was considered and
 * refused, because `gravity-cta` on a non-interactive card is a false
 * affordance, dilutes a scarce action colour, and at 2.85 against a light
 * card is loud in hue and weak in luminance.
 *
 * Grouped paints the grid container, not each card, so the set reads as
 * one field rather than three separate tints — that was the whole point
 * of moving the fill off the per-card rule. The container carries its own
 * padding, `--wp--preset--spacing--40` (1.5rem), so the outer cards are
 * not flush to the field edge: the middle step of the scale, picked to
 * stay subordinate to the section-level inset (spacing--60/70, a much
 * larger clamp) while reading as more than the micro steps
 * (spacing--20/30) reserved for fine utility nudges. The radius moves
 * with the fill.
 *
 * Every value below is a role token, resolved through the design file's
 * `color-roles` exactly as the band contract resolves. Per *A fill obliges
 * a tonal set* in the design-system constants: whoever paints a fill emits
 * every colour that fill obliges — text, heading, link, link decoration,
 * link focus ring, button focus ring, button fill, and button label, the
 * nine-entry set as of constants v1.12. Both treatments carry the
 * complete set, emitted on every ground and not scoped to band-dark: the
 * card's own fill is what the foregrounds are measured against, and that
 * fill is the same whatever sits behind it. `styles/band-dark.json` sets
 * `elements.heading.color`, `elements.link.color`, both elements'
 * `:focus-visible` outline, and — since theme 4.0.0 — the inverse button
 * pair on `elements.button.color`, all via descendant selectors that
 * reach into a card. A card left to inherit would take
 * `gravity-link-inverse` and `gravity-focus-inverse`, both at 1.00
 * contrast against a light card — SC 1.4.3 and SC 2.4.7, both AA, live
 * the moment the card holds a link or anything focusable — and the band's
 * white inverse button fill at 1.16:1 against the card's own fill. Link
 * decoration is `inherit` for both treatments — the prose underline rule
 * above already reaches card copy, so no separate rule is needed here.
 *
 * `person-grid` JOINS BOTH TREATMENTS at 4.48.0, under its own modifiers,
 * `c-person-grid--separate` and `c-person-grid--grouped`, with every selector
 * below duplicated rather than restated, so the tonal set is emitted once.
 * Its Separate card takes its padding from *Person grid*, because that
 * pattern's unit carries none in markup; point-grid's comes from its markup.
 *
 * `stat-grid` JOINS BOTH TREATMENTS at 4.57.0, under `c-stat-grid--separate`
 * and `c-stat-grid--grouped`, the same way, so the card's tonal set is emitted
 * once for all three. Its stat carries no padding in markup, as person-grid's
 * unit carries none, so Open keeps the full track for the figure; Separate's
 * padding is in *Stat grid* below. What a stat adds on top of the treatment (Grouped's inset and
 * hairlines, the figure's colour on a card) is in *Stat grid* below.
 *
 * NESTED, ONE BLOCK PER TREATMENT (Copy-to-Site Decision 111). Each
 * treatment names its declarers once, and its six rule groups (fill, text,
 * links, button, button hover, focus) sit inside with native CSS nesting.
 * Adding a declarer is one edit per treatment. The declarers share one
 * selector shape, so `&` resolving like `:is()` changes no specificity: a
 * client override at the old equal specificity still wins. Scoped to the
 * card grids only; questions, quotes, the idea cards, and the single-card
 * patterns keep flat selectors, because merging them would raise some.
 *
 * No `!important`. Core's band rule is `:root :where(...)`, which
 * contributes nothing, and the band's own style-variation overrides
 * compile to plain descendant-selector classes rather than `:where()`, so
 * specificity settles this on both the front end and the editor canvas —
 * do not reach for it here either.
 */

/* Separate. Each card is its own surface with an edge. */
.c-point-grid--separate,
.c-person-grid--separate,
.c-stat-grid--separate {
  & .wp-block-group-is-layout-grid > .wp-block-group {
    background-color: var(--wp--preset--color--gravity-surface-nested);
    border: 1px solid var(--wp--preset--color--gravity-border);
    border-radius: var(--wp--custom--rounded--md);
  }

  /* Text and headings. Headings are targeted directly rather than left to
   * inherit — see the docblock above.
   */
  & .wp-block-group-is-layout-grid > .wp-block-group,
  & .wp-block-group-is-layout-grid > .wp-block-group :is(h1, h2, h3, h4, h5, h6) {
    color: var(--wp--preset--color--gravity-text);
  }

  /* Links. Excludes button-styled links, which take the CTA tokens
   * below and are covered by the focus-ring rule instead.
   */
  & .wp-block-group-is-layout-grid > .wp-block-group a:not(.wp-element-button) {
    color: var(--wp--preset--color--gravity-link);
  }

  /* Buttons — see the Grouped block below for why. */
  & .wp-block-group-is-layout-grid > .wp-block-group .wp-element-button {
    background-color: var(--wp--preset--color--gravity-cta);
    color: var(--wp--preset--color--gravity-cta-label);
  }

  & .wp-block-group-is-layout-grid > .wp-block-group .wp-element-button:hover {
    background-color: var(--wp--preset--color--action-10);
    color: var(--wp--preset--color--gravity-cta-label);
  }

  /* Link and button focus rings. */
  & .wp-block-group-is-layout-grid > .wp-block-group a:focus-visible,
  & .wp-block-group-is-layout-grid > .wp-block-group .wp-element-button:focus-visible {
    outline-color: var(--wp--preset--color--gravity-focus);
  }
}

/* Grouped. The grid container is one surface and the cards sit on it, so
 * every rule targets the container's descendants rather than each card.
 */
.c-point-grid--grouped,
.c-person-grid--grouped,
.c-stat-grid--grouped {
  & .wp-block-group-is-layout-grid {
    background-color: var(--wp--preset--color--gravity-surface-nested);
    padding-block: var(--wp--preset--spacing--40);
    padding-inline: var(--wp--preset--spacing--40);
    border-radius: var(--wp--custom--rounded--md);
  }

  /* Text and headings. */
  & .wp-block-group-is-layout-grid,
  & .wp-block-group-is-layout-grid :is(h1, h2, h3, h4, h5, h6) {
    color: var(--wp--preset--color--gravity-text);
  }

  /* Links. */
  & .wp-block-group-is-layout-grid a:not(.wp-element-button) {
    color: var(--wp--preset--color--gravity-link);
  }

  /* Buttons — the card contract's button fill and button label, on both
   * treatments. A card is a light ground even when it sits inside a dark
   * band, so its button carries the ordinary CTA pair rest and hover, never
   * the band's inverse pair — which would render a white fill on a light
   * card at 1.16:1. These selectors outrank both the band variation's
   * button pair and the dark-band hover rule above (1e), so they win on
   * every ground. Hover is the fleet pair: `action-10` fill under the
   * unchanged label, 8.83:1 measured — a card button's hover obligation is
   * this rule's, not 1e's, per "whoever paints a fill emits every colour
   * that fill obliges."
   */
  & .wp-block-group-is-layout-grid .wp-element-button {
    background-color: var(--wp--preset--color--gravity-cta);
    color: var(--wp--preset--color--gravity-cta-label);
  }

  & .wp-block-group-is-layout-grid .wp-element-button:hover {
    background-color: var(--wp--preset--color--action-10);
    color: var(--wp--preset--color--gravity-cta-label);
  }

  /* Link and button focus rings. */
  & .wp-block-group-is-layout-grid a:focus-visible,
  & .wp-block-group-is-layout-grid .wp-element-button:focus-visible {
    outline-color: var(--wp--preset--color--gravity-focus);
  }
}

/* ── Text split: the Grouped frame ───────────────────────────────────────
 * Added 2026-10-20 at 6.6.0, ahead of the pattern. `text-split` offers Cards
 * as None (the default, no class) or Grouped, and Grouped is one surface
 * around the image half: its optional label, the image, and its optional
 * caption. The prose half is never framed. This is the "tinted frame around
 * the image" ruled a style variation for the copy-beside-an-image pattern on
 * 2026-10-02 (GR9-T19), which `text-split` absorbed on 2026-10-20.
 *
 * ITS OWN SELECTOR, NOT A MEMBER OF THE GRID TREATMENTS' LIST. Those share one
 * selector shape keyed to `.wp-block-group-is-layout-grid`, and this pattern
 * has no grid, so joining the list would mean a second shape inside it. The
 * values are the same card contract: `gravity-surface-nested`, spacing `40`,
 * `rounded.md`, and every colour the fill obliges, per "whoever paints a fill
 * emits every colour that fill obliges". A frame is a light ground on every
 * band, so its text and links take the light-surface roles even inside Band,
 * dark.
 *
 * The image half is the row's first child in the DOM at every width; the
 * Orientation flip reverses the visual order only, so `:first-child` holds on
 * both sides.
 */
.c-text-split--grouped > .is-layout-flex > :first-child {
  background-color: var(--wp--preset--color--gravity-surface-nested);
  padding-block: var(--wp--preset--spacing--40);
  padding-inline: var(--wp--preset--spacing--40);
  border-radius: var(--wp--custom--rounded--md);
  color: var(--wp--preset--color--gravity-text);

  & a:not(.wp-element-button) {
    color: var(--wp--preset--color--gravity-link);
  }

  & a:focus-visible {
    outline-color: var(--wp--preset--color--gravity-focus);
  }
}

/* ── Card icon position ──────────────────────────────────────────────────
 * Fleet expression of the Icon position axis on `point-grid`, per
 * Copy-to-Site Decision 91 and *Pattern Style Options* in
 * `ops-pattern-standards.md`. Three values: Above, Above centred, Beside.
 * ABOVE IS THE DEFAULT AND WRITES NO CLASS, which is why nothing below
 * styles it: the pattern's own markup already stacks the icon over the
 * title. There is no None either, on the axis's own ruling — whether a card
 * has an icon is decided by whether the icon is in the card. A card without
 * one is unaffected by every rule here: the Beside selectors key on a group
 * holding an icon as a direct child, and Above centred keys on the icon's wrapper.
 *
 * Layer 5 for the reason Card styles gives above: every rule reaches card
 * children from the section class. Fleet rather than client because each
 * rule expresses what the axis value MEANS, the same on every site; the one
 * client-variable number, the title's line box, is read from a custom
 * property this file does not set (see the last rule).
 *
 * ABOVE CENTRED is text-align on the icon's own wrapper, and that is the whole
 * rule. It centres the icon and nothing else: the title and the body stay at
 * the card's start edge (Copy-to-Site Decision 118, amending Decision 91, which
 * centred all three). It works because core renders the icon's SVG inline inside
 * a block wrapper, so the wrapper's text-align reaches it. Measured 2026-10-19 in
 * the local render harness, on this rule as written: Cards None, Separate, and
 * Grouped at Columns 2, 3, and 4, at 390, 768, and 1440px, 27 cases. On every
 * card, wrapped titles and four-line bodies included, the icon is within 0.01px
 * of the card's content centre and the title and body first lines are flush with
 * the content's start edge. Against 5.0.0 on the same markup at 390 and 1440px,
 * the only computed style that differs is text-align going from center to start
 * on the card, the icon-and-title wrapper, the title, and the body; Above and
 * Beside are unchanged. The editor canvas was not re-measured for this rule; the
 * 2026-10-02 measurement of 397, 800, and 1440 was of the all-centred rule it
 * replaces. A dedicated flex rule on
 * `.wp-block-icon`, mirroring core's own `.aligncenter`, was drafted and dropped
 * as redundant. **If core ever renders that SVG as a block, this stops centring
 * the icon**, so re-measure on a core upgrade rather than reading it. The section
 * header is outside the card selector and stays start-aligned.
 *
 * BESIDE sets the icon inline with the title's first line and leaves the
 * body beneath at the card's own content edge — not hung past the icon,
 * which Decision 91 considered and refused. The unit is the pattern's
 * icon-and-title wrapper group, which carries no class, so it is reached
 * structurally as the group holding a direct `.wp-block-icon`. Its block gap
 * comes from core's flow layout as `margin-block-start` on the second child,
 * which a row turns into a 16px drop on the title, so the children's start
 * margin is zeroed and the same step becomes the row gap.
 *
 * `point-split` TAKES BESIDE ALWAYS, ON ITS ROOT CLASS ALONE. Decision
 * 92 sets every point's icon at the title's height with no axis and no
 * modifier, so the three Beside rules below carry a second selector keyed on
 * `.c-point-split`. The pattern's icon-and-title wrapper is
 * `point-grid`'s wrapper byte for byte (no class, a `30` step, the icon at
 * `2em`), so the same structural reach, the same gap, and the same size rule
 * serve both. Added at 4.47.0. The `point-grid` selectors are unchanged and a
 * selector list adds no specificity, so nothing `point-grid` renders moved.
 *
 * COUPLED TO THE PATTERN: the gap below is `spacing--30` because the wrapper's
 * `blockGap` in `patterns/point-grid.php` is `spacing|30` — the icon sits one
 * step from the title in either orientation. Change one, change the other.
 * The same holds for `patterns/point-split.php`, whose wrapper carries
 * the same `spacing|30`.
 */
.c-point-grid--icon-above-centred .wp-block-group-is-layout-grid > .wp-block-group > .wp-block-group > .wp-block-icon {
  text-align: center;
}

.c-point-grid--icon-beside .wp-block-group:has(> .wp-block-icon),
.c-point-split .wp-block-group:has(> .wp-block-icon) {
  display: flex;
  align-items: flex-start;
  gap: var(--wp--preset--spacing--30);
}

.c-point-grid--icon-beside .wp-block-group:has(> .wp-block-icon) > *,
.c-point-split .wp-block-group:has(> .wp-block-icon) > * {
  margin-block-start: 0;
}

.c-point-grid--icon-beside .wp-block-group:has(> .wp-block-icon) > .wp-block-heading,
.c-point-split .wp-block-group:has(> .wp-block-icon) > .wp-block-heading {
  flex: 1 1 0;
  min-width: 0;
}

/* The Beside icon is one title line box tall, and only the font-size moves.
 *
 * Core writes the icon's width inline on the SVG as `2em`, which a stylesheet
 * could only outrank with `!important`. What it can set without a contest is
 * the font-size that `2em` resolves against, so this rule sets that and the
 * inline width does the rest.
 *
 * COUPLED TO THE PATTERN: the `/ 2` mirrors the `2em` the pattern writes on
 * the icon. Change one, change the other.
 *
 * THE MECHANISM IS FLEET, THE VALUE IS CLIENT. The title's line box depends on
 * how a client's theme maps `h3`, so this rule reads
 * `--c-point-grid-title-line-box` and does not set it; the client theme sets it
 * in its own `site.css`, the split the mega menu's icons already use. No CSS
 * route derives the value from the title itself: `lh` resolves against an
 * element's own or its parent's line height, never a sibling's, and
 * `anchor-size()` reads the whole title box, which is two line boxes on a
 * two-line title. theme.json emits element typography as plain declarations,
 * not custom properties, so there is nothing to read either.
 *
 * THE FALLBACK IS ONE LINE OF THE CARD'S OWN TEXT, `1lh`, and it is why this
 * rule sits on the icon's wrapper rather than on the SVG. In `font-size`, `lh`
 * resolves against the parent's line height. Core sets `line-height: 0` on
 * `.wp-block-icon`, so on the SVG `1lh` would be zero; on the wrapper it is the
 * card's body line box, which the SVG then inherits through its font-size. On
 * this theme the fallback alone renders the icon 27.2px against a 27.5px title
 * line, so a client that never sets the property gets an icon within a fraction
 * of a pixel of right wherever its title and body leading are close, and never
 * an unsized one.
 *
 * ONE PROPERTY FOR BOTH PATTERNS, KEPT UNDER ITS `point-grid` NAME. Since
 * 4.47.0 this rule also sizes `point-split`'s icons, and it reads the
 * same `--c-point-grid-title-line-box`. Not renamed, because the name is the
 * fleet contract every client theme sets, written into
 * `gravitysite-design-system-standards.md` *Icon position*; a rename would
 * change that contract's input for every client. Instead each client sets it
 * on both section classes, as this theme's `site.css` does. A client theme
 * that sets it on `.c-point-grid` only leaves `point-split` on the
 * `1lh` fallback, within a fraction of a pixel here, never unsized.
 *
 * Measured 2026-10-02 with the property set by this theme: icon 27.5px against
 * a 27.5px line box, top edges level, on every band, with Cards None
 * and Separate, one-line and two-line titles, on the front end at 390, 800,
 * and 1440 and in the editor canvas at 397, 800, and 1440. Without this rule
 * the icon renders at `2em` of the card text, 34px against 27.5px.
 */
.c-point-grid--icon-beside .wp-block-group:has(> .wp-block-icon) > .wp-block-icon,
.c-point-split .wp-block-group:has(> .wp-block-icon) > .wp-block-icon {
  flex: none;
  font-size: calc(var(--c-point-grid-title-line-box, 1lh) / 2);
}

/* ── Section dividers ────────────────────────────────────────────────────
 * Fleet defaults for the Divider axis, per
 * `gravitysite-design-system-standards.md`, "Section dividers", with the
 * geometry in the divider contract in
 * `gravitysite-design-system-constants.md`. Three values — None, Top,
 * Bottom — and None writes no class, which is why nothing below styles it.
 *
 * THE TRIANGLE WAS REMOVED 2026-08-30, ruled in GravityBrain (Scott) on the
 * render: it read dated. What remains is the rule alone, full width, on the
 * section's own box edge. Everything the triangle dragged with it went with
 * it — the second pseudo-element, the overhang, the clipping-ancestor risk,
 * and the contrast deviation the standard had to record. Do not reintroduce
 * it as a refinement; it was the expensive half of this treatment and it
 * bought a look we have now rejected.
 *
 * One selector list rather than one rule per pattern. The classes are
 * per-pattern because Decision 60 makes the absence of `--` the thing that
 * identifies a pattern address, so a shared `c-section-divider` would be
 * read as an address by the plugin's own detection. Adding a declarer to
 * this axis means adding to the lists below, not writing a new block of
 * rules: one selector in the root list, one in the `::before` list, and
 * one in the direction list, for each value the declarer takes. A declarer
 * that takes one value adds THREE selectors; one that takes Top and Bottom
 * adds SIX, across the root list, the `::before` list, the top list, and the
 * bottom list. **This clause said "two selectors" until 4.41.0 and "THREE
 * selectors" until 6.5.0.** Three is the count for the `gs-` declarers, which
 * take one value each, and it was wrong for the four patterns, which take
 * both.
 *
 * THE FLUSH HERO RAISES ITS RULE, AND NO OTHER DECLARER DOES.
 * `c-hero-flush-split` declares None, Top, and Bottom, ruled 2026-10-20 in
 * GravityBrain (Scott), and takes the same colour and geometry as every
 * other declarer from the lists below. Its media region is
 * `position: relative` and holds a photograph that is `position: absolute;
 * inset: 0` (see *Hero, flush split*), and the root's `::before` comes
 * before both regions in tree order. A positioned element paints over an
 * earlier positioned one at the same `z-index`, so an unraised rule is
 * painted over by the photograph: Top disappears at the top of the photo
 * region and Bottom disappears across its foot. The fix is the one
 * `z-index: 1` rule after the direction lists, scoped to this declarer. It is
 * one step above the regions' `auto` and below the stuck header's `10`
 * (`:root.has-header-stance .c-header-shell`), so a stuck header still paints
 * over it. `c-hero-split`, `c-hero-stack`, and `c-cta-stack` need none of
 * this: the theme carries no positioned element in any of their regions, and
 * their markup writes none, so nothing paints over their rule and the same
 * lift on them would change nothing. Read at 6.4.0. If one of them gains a
 * positioned region, it takes the same rule, in the same place.
 *
 * ON WIDE SCREENS THE FLUSH HERO'S BOTTOM DRAWS ACROSS THE FOOT OF THE
 * PHOTOGRAPH as well as under the copy, so the "paints on the host's own
 * band" claim below holds for it only under the copy region. The rule
 * carries no information, so no contrast bar reaches it; whether it reads
 * over a photograph is judged on the render, per *Section dividers*.
 *
 * TWO DECLARERS ARE NOT PATTERNS, and both take the `gs-` prefix for the same
 * reason. `gs-masthead-split--divider-bottom` is the split masthead, added
 * 2026-09-16 at 4.39.0 with the Gravity Hacks archive: an organism whose markup is authored inline in
 * `templates/archive-gravity-hacks.html`, so it is template markup on the
 * section's own schedule rather than editor-placed content. Its design layer
 * is `assets/css/organisms/split.css`, which declares this axis and
 * deliberately does not draw it.
 * `gs-masthead-bar--divider-bottom` is the bar masthead, added 2026-09-16 at
 * 4.41.0 with the Gravity Hacks single, on the same terms — template markup
 * in `templates/single-gravity-hacks.html`, design layer
 * `assets/css/organisms/bar.css`.
 *
 * ON THE BAR MASTHEAD THE PREFIX IS A CONSEQUENCE OF THIS AXIS RATHER THAN A
 * PROPERTY OF THE ROW, and that is worth knowing before anyone proposes
 * dropping its divider. `gravitysite-css-standards.md`, *Naming*, sanctions
 * `gs-` only where a `--` must sit outside the `c-` namespace. Ship that
 * masthead with no divider and its class is `c-masthead-bar`, and adding the
 * divider back later is a rename of every selector that names it. A change to
 * the divider there is therefore a rename, not a value edit.
 *
 * The prefix is load-bearing on both, precisely so Decision 60 cannot
 * reach them: the detection is scoped to the `c-` namespace, so a `--` outside
 * that namespace is free to carry the axis value and keep this list
 * greppable by treatment. Do not rename either into `c-`. Both write the
 * value unconditionally rather than as a modifier on a picker, because
 * neither has a picker: the masthead's ground and divider are authored in git
 * per `assets/css/organisms/organisms-reference.md`, *Masthead axes*.
 *
 * THE FOOTER'S CLOSE BAND WAS A THIRD, UNTIL 6.8.0. `gs-footer-close--divider-top`
 * topped a light ask strip in `parts/footer.html`. The strip was removed so a
 * page's own closing ask meets the dark footer directly (Copy-to-Site
 * Decision 128), and the class went with it.
 *
 * A SECOND `gs-` DECLARER IS NOT A TREND TOWARDS THE RETIRED PREFIX.
 * `gravitysite-css-standards.md`, *Naming*, retires `gs-` with exactly this
 * exception, and a 2026-09-01 sweep once renamed the footer's class by
 * applying the removal rule literally. Read this docblock before renaming
 * anything in these lists.
 *
 * TOP AND BOTTOM NOW RENDER IDENTICALLY AT A SHARED SEAM, and that is an
 * open question rather than a defect. Sections are contiguous, so a 4px
 * rule on section A's bottom edge and a 4px rule on section B's top edge
 * are adjacent pixel rows at the same seam and cannot be told apart. The
 * two values only diverge where a section has no neighbour — Bottom on the
 * last section draws at the foot of the page. Whether the axis collapses
 * to one value is a standards decision and is deliberately not taken here.
 *
 * Drawn on the pattern root because nothing else reaches the edge. Every
 * child block sits inside the section inset, so a mark drawn from a child
 * lands the width of the inset away from the edge, and closing that gap
 * would need a negative margin equal to an inset Decision 56 deliberately
 * took out of patterns' knowledge.
 *
 * Only one pseudo-element is consumed now. The contract said both were,
 * which was true while the triangle existed; a pattern carrying a divider
 * has `::after` free again.
 *
 * The strip paints nothing but the rule itself, so this treatment owes no
 * tonal set. Per *A fill obliges a tonal set* the obligation follows a
 * fill that changes what sits behind a foreground; a divider has no
 * contents — no text, no link, nothing focusable — so there is no
 * foreground to carry through.
 *
 * The rule is painted `gravity-brand`, whose job in the role set already
 * names accent rules. Because it binds a role, every client's divider
 * comes out in that client's brand colour with no work here.
 *
 * Geometry is literal rather than tokenised, per the divider contract:
 * the spacing scale is a spacing tool, 4px is the reserved-and-
 * unregistered slug 10, and a decorative mark's dimensions are not
 * spacing steps. Same precedent as the `1px` card border above.
 */

.c-cta-stack--divider-top,
.c-cta-stack--divider-bottom,
.c-hero-flush-split--divider-top,
.c-hero-flush-split--divider-bottom,
.c-hero-split--divider-top,
.c-hero-split--divider-bottom,
.c-hero-stack--divider-top,
.c-hero-stack--divider-bottom,
.gs-masthead-bar--divider-bottom,
.gs-masthead-split--divider-bottom {
  --c-divider-rule: 4px;

  position: relative;
}

/* The rule, edge to edge, sitting exactly on the section's box edge. */
.c-cta-stack--divider-top::before,
.c-hero-flush-split--divider-top::before,
.c-hero-split--divider-top::before,
.c-hero-stack--divider-top::before,
.c-cta-stack--divider-bottom::before,
.c-hero-flush-split--divider-bottom::before,
.c-hero-split--divider-bottom::before,
.c-hero-stack--divider-bottom::before,
.gs-masthead-bar--divider-bottom::before,
.gs-masthead-split--divider-bottom::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  height: var(--c-divider-rule);
  background-color: var(--wp--preset--color--gravity-brand);
}

.c-cta-stack--divider-top::before,
.c-hero-flush-split--divider-top::before,
.c-hero-split--divider-top::before,
.c-hero-stack--divider-top::before {
  top: 0;
}

.c-cta-stack--divider-bottom::before,
.c-hero-flush-split--divider-bottom::before,
.c-hero-split--divider-bottom::before,
.c-hero-stack--divider-bottom::before,
.gs-masthead-bar--divider-bottom::before,
.gs-masthead-split--divider-bottom::before {
  bottom: 0;
}

/* The flush hero's rule paints above its regions, and no other declarer's
 * does: its media region is positioned, and an unraised rule is painted over
 * by the photograph. The reason and the ceiling are in the docblock above. */
.c-hero-flush-split--divider-top::before,
.c-hero-flush-split--divider-bottom::before {
  z-index: 1;
}


/* ── Question styles ─────────────────────────────────────────────────────
 * Fleet defaults for the Cards axis on the FAQ patterns, per
 * `gravitysite-design-system-standards.md`, "Question styles", with the
 * values in the question treatment contract in
 * `gravitysite-design-system-constants.md`. Two values — Ruled and Separate —
 * and no None, because a question list with neither a rule nor a fill has
 * no separation at all. Ruled is the fleet default and writes no class,
 * which is why the first rule below is unconditional on both pattern
 * addresses rather than scoped to a modifier.
 *
 * THESE RULES SERVE TWO PATTERNS, `c-qa-accordion` and
 * `c-qa-aside-accordion`, both of which declare the Cards
 * axis. One selector list per rule group, never a duplicated block per
 * pattern — the same model as the Section dividers block above, and for the
 * same reason: the treatment is one thing, and a second copy of it is a
 * second thing to keep in step. Adding a third `qa` pattern means one
 * selector in each list below and nothing else. The Separate modifier is each
 * pattern's own address plus `--separate`, so the two never collide.
 *
 * THE RULE IS DRAWN FROM `currentColor`, NOT FROM A BORDER TOKEN, and that
 * is the one thing here most likely to be "corrected" by a later reader.
 * The mechanism and its 20% derivation are in *An edge on the band tracks
 * the band* in the constants file. What that section predicts is confirmed
 * by measurement here: on this palette the edge reads 1.51 against white
 * and `band-neutral`, 1.50 against `band-subtle`, and 1.87 against
 * `band-dark`, from one declaration — where `gravity-border` reads 1.41,
 * 1.34, and 8.63 and would put a hard bright line across the dark band.
 * `color-mix` and `currentColor` are used deliberately, not as a local
 * improvisation. Their one other use is the footer's divider (*Footer*,
 * from 0.3.4), at a 40% share because a divider is held to 3:1.
 *
 * The edge is drawn on the accordion item rather than between items,
 * because core gives the toggle its own 8px of vertical padding and the
 * item box ends at the panel. So the rule closes the item it belongs to
 * and the pattern's own gap opens the next one, which is the reading the
 * proximity is meant to produce.
 *
 * BOXED CARRIES A FILL AND A RADIUS AND NO BORDER. Three edge devices on
 * one object is one too many; the fill and the radius already bound it,
 * which is the same reading that gives `cards-grouped` no border. It also
 * has to remove the ruled edge, since the default above is unconditional.
 *
 * Separate's own padding is this file's, and that is the same split as
 * `cards-grouped`: the pattern ships an unstyled list, and the inset a fill
 * needs arrives with the fill. Ruled therefore sits flush to the section
 * measure, which is what an editorial list should do. One inset does belong
 * to the pattern and is already in the markup — the panel's `spacing|20`
 * bottom padding, so that an item's bottom edge sits the same distance
 * below an open answer as below a closed question. Separate's padding sits
 * outside that rather than replacing it, which is what makes a separate card
 * symmetric top to bottom.
 *
 * Per *A fill obliges a tonal set*, Separate emits the whole set — text,
 * heading, link, link decoration, link focus ring, and button focus ring —
 * ON EVERY GROUND and not scoped to `band-dark`. The fill is what the
 * foregrounds are measured against and it does not change with the band
 * behind it. Measured on all four grounds at this theme: text 14.06, link
 * 4.60, focus ring 4.60, identical on every one. Ruled owes no set at all,
 * because it paints nothing and every foreground keeps the band's own
 * tokens; measured 12.16 on `band-dark` and 15.49–16.32 on the light
 * grounds.
 *
 * Link decoration is `inherit` for the same reason as the card styles: the
 * prose underline rule at the top of this file already reaches an answer.
 *
 * STANDING DEFECT, REPORTED NOT FIXED: Separate on `band-subtle` measures 1.10
 * between the fill and the ground, so the panels are near-invisible there.
 * That is the reading already recorded for `cards-grouped` with the same
 * upstream cause — `gravity-surface-subtle` is only a step from white, so
 * the accent band barely reads as a band even with nothing on it. Fixing it
 * means moving the band's own fill, which is a decision about bands. The
 * light grounds read 1.16 and `band-dark` reads 10.48.
 *
 * THE TOGGLE FOCUS RING IS EMITTED HERE BECAUSE NOTHING ELSE REACHES IT.
 * The accordion heading renders a `<button>` that is not a
 * `.wp-element-button`, so `theme.json`'s `elements.button` never applies
 * to it — including the `gravity-focus-inverse` switch `band-dark` sets on
 * that element. Core's own rule is `:not(:focus-visible){outline:none}`,
 * which leaves the browser default. Three cases and not two: the dark
 * band switches the ring to the inverse token, and Separate switches it back,
 * because inside a Separate card the ring sits on the light fill whatever
 * band is behind it. Dropping the third would put the inverse ring at 1.00
 * against that fill, which is SC 2.4.7.
 *
 * THE ROW HAS A FLOOR, AND IT IS THE ONLY HEIGHT THIS BLOCK DECLARES. Core's
 * 8px of padding around the marker's 26.4px box makes a one-line row 42.4px,
 * under the fleet's 44px target, the deliberate exceedance in
 * `ops-accessibility-standards.md`. A minimum rather than more padding, so a
 * row that already clears 44px, which is any wrapped question, renders exactly
 * as before. Core sets the toggle to `display: flex`, `align-items: center`,
 * and `box-sizing: border-box`, so the floor is the whole row and the marker
 * and the question stay centred in it. The value is `--c-target-min`, declared
 * under *Target size* below with the reason it is a literal. Measured
 * 2026-10-02: one-line rows 42.4 to 44px on every band, Ruled and Separate, both
 * patterns, at 390 and 1440, front end and canvas; the question's centre sits
 * 0.62px from the row's centre before and after; two- and three-line rows
 * (58.5px, 79.75px) unchanged. Each one-line row adds 1.6px, so a five-row
 * list grows 8px.
 *
 * THE MARKER GLYPH IS CORE'S AND ONLY ITS SIZE AND ITS BOX MOVE. Core
 * renders a text `+` and rotates that span 45 degrees on open from its own
 * inline CSS. Replacing the glyph means suppressing it, redrawing it in a
 * pseudo-element, and then defeating core's transform, which the question
 * treatment contract does not sanction. The size bump is one step up the
 * ramp, from the question's own `text-3` to `text-4`: enough that the
 * marker reads as an affordance rather than as punctuation, and not so far
 * that it drives the row's height — measured at 1536px, an unbumped row is
 * 37.3px, `text-4` gives 43.2px, and `text-5` gives 50.4px, which is the
 * marker setting the rhythm instead of the words.
 *
 * THE MARKER SITS ON THE LEFT, AND A LEFT MARKER OWES THREE RULES WHERE A
 * RIGHT ONE OWED NONE. Core emits the icon span before the title, sets the
 * toggle to flex with no `gap`, and gives the icon box a flat `1.2em`. So
 * the marker would sit hard against the question text, and the panel — a
 * sibling starting at the item's left edge — would begin a marker-width to
 * the left of the question it answers. The three rules are the box's width,
 * the gap, and a panel offset that is the sum of the two. All three bind
 * our own spacing tokens rather than inheriting core's coefficient:
 * overriding the width costs the same as inheriting it and leaves no
 * foreign number to rot silently when core moves its own. The width sits in
 * the glyph rule above rather than in a rule of its own, because a separate
 * group would carry the identical selector list — which is the fault this
 * block's own model is written against.
 *
 * `spacing|30` FOR THE BOX IS A CHOSEN VALUE CARRYING A CONSTRAINT. It has
 * to be at or above the rendered width of the `+` at `text-4`, in the open
 * state as well as the closed one, because core rotates the glyph 45 degrees
 * and the toggle sets `overflow: hidden`. Narrowing the box below core's own
 * is what puts that in play. Step up the scale if the glyph clips; do not
 * reach for core's coefficient, and do not change this without changing the
 * panel offset below, which is its sum with the gap.
 *
 * NOTHING PAINTS THE OPEN ITEM, and that is not an oversight. The panel
 * being open is the state indicator, and any colour applied to an open
 * question would opt that element out of every band — the mechanism
 * recorded against the `hero-split` eyebrow.
 *
 * The family override is the last rule and it is the one to argue with
 * first. The design ruled the question into the body family at body size.
 * The size is bound in the pattern, where a `text-*` slug is fleet-constant
 * and portable. The family cannot be: font-family slugs are each client's
 * own font names, so a pattern binding one would be binding a theme-local
 * primitive, and no published pattern binds a family. `inherit` expresses
 * the same intent portably — it resolves to whatever family the section's
 * prose is set in, on any client — and it belongs here rather than in the
 * pattern because it is a value.
 *
 * No `!important` anywhere below. The band variations compile to plain
 * descendant-selector classes and core's own rules sit inside `:where()`,
 * so specificity settles this on the front end and in the editor canvas
 * alike — confirmed by reading the computed colours inside `band-dark`,
 * where Separate's own text, link, and heading tokens all win.
 */

/* Ruled — the fleet default, unconditional. */
.c-qa-accordion .wp-block-accordion-item,
.c-qa-aside-accordion .wp-block-accordion-item {
  border-bottom: 1px solid color-mix(in srgb, currentColor 20%, transparent);
}

/* Separate. */
.c-qa-accordion--separate .wp-block-accordion-item,
.c-qa-aside-accordion--separate .wp-block-accordion-item {
  border-bottom: 0;
  background-color: var(--wp--preset--color--gravity-surface-nested);
  border-radius: var(--wp--custom--rounded--md);
  padding-block: var(--wp--preset--spacing--30);
  padding-inline: var(--wp--preset--spacing--40);
}

/* Separate's tonal set. Headings are targeted directly rather than left to
 * inherit, for the reason given against the card styles above.
 */
.c-qa-accordion--separate .wp-block-accordion-item,
.c-qa-accordion--separate .wp-block-accordion-item :is(h1, h2, h3, h4, h5, h6),
.c-qa-aside-accordion--separate .wp-block-accordion-item,
.c-qa-aside-accordion--separate
  .wp-block-accordion-item
  :is(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--preset--color--gravity-text);
}

.c-qa-accordion--separate .wp-block-accordion-item a:not(.wp-element-button),
.c-qa-aside-accordion--separate
  .wp-block-accordion-item
  a:not(.wp-element-button) {
  color: var(--wp--preset--color--gravity-link);
}

.c-qa-accordion--separate .wp-block-accordion-item a:focus-visible,
.c-qa-accordion--separate .wp-block-accordion-item .wp-element-button:focus-visible,
.c-qa-aside-accordion--separate .wp-block-accordion-item a:focus-visible,
.c-qa-aside-accordion--separate
  .wp-block-accordion-item
  .wp-element-button:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus);
}

/* The toggle's focus ring, all three cases. */
.c-qa-accordion .wp-block-accordion-heading__toggle:focus-visible,
.c-qa-aside-accordion .wp-block-accordion-heading__toggle:focus-visible {
  outline: 2px solid var(--wp--preset--color--gravity-focus);
  outline-offset: 2px;
}

.c-qa-accordion.is-style-band-dark
  .wp-block-accordion-heading__toggle:focus-visible,
.c-qa-aside-accordion.is-style-band-dark
  .wp-block-accordion-heading__toggle:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus-inverse);
}

.c-qa-accordion--separate.is-style-band-dark
  .wp-block-accordion-heading__toggle:focus-visible,
.c-qa-aside-accordion--separate.is-style-band-dark
  .wp-block-accordion-heading__toggle:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus);
}

/* The marker glyph, one step up the ramp from the question, in a box wide
 * enough to hold it. Core gives the icon a flat 1.2em; the width is overridden
 * here rather than copied into the offset below, so that offset is the sum of
 * two of our own tokens rather than a foreign coefficient left to rot.
 */
.c-qa-accordion .wp-block-accordion-heading__toggle-icon,
.c-qa-aside-accordion .wp-block-accordion-heading__toggle-icon {
  font-size: var(--wp--preset--font-size--text-4);
  width: var(--wp--preset--spacing--40);
}

/* The gap between the marker and the question, which core ships without, and
 * the row's floor. See *The row has a floor* in the docblock above.
 */
.c-qa-accordion .wp-block-accordion-heading__toggle,
.c-qa-aside-accordion .wp-block-accordion-heading__toggle {
  gap: var(--wp--preset--spacing--20);
  min-block-size: var(--c-target-min);
}

/* The answer aligns with its question. The offset is the icon box plus the gap.
 */
.c-qa-accordion .wp-block-accordion-panel,
.c-qa-aside-accordion .wp-block-accordion-panel {
  padding-left: calc(
    var(--wp--preset--spacing--40) + var(--wp--preset--spacing--20)
  );
}

/* The question's family. See the last paragraph of the docblock above. */
.c-qa-accordion .wp-block-accordion-heading,
.c-qa-aside-accordion .wp-block-accordion-heading {
  font-family: inherit;
}

/* ── Target size ─────────────────────────────────────────────────────────
 * The fleet's 44px floor for controls that do not reach it by their own
 * geometry: SC 2.5.5, the deliberate exceedance in
 * `ops-accessibility-standards.md`, checked by validation item 21 in
 * `ops-pattern-standards.md`. Ruled 2026-10-02 in GravityBrain (Scott) for
 * three published patterns, with no pattern change.
 *
 * THE FLOOR IS ONE LITERAL, DECLARED ONCE, AND THAT IS A RECORDED DEVIATION.
 * *The raw-value exception* in `gravitysite-css-standards.md` admits literals
 * only inside media conditions. No token carries a target size: the spacing
 * scale measures spacing, which is the argument `split.css` makes for
 * composing its field height instead. Composing is not open here. The
 * accordion row needs a floor that leaves taller rows alone, and a link's line
 * box is set by the client's type, not by this file. So `--c-target-min` is
 * declared once, on the pattern addresses that read it, the way
 * `--c-divider-rule` is above, and no rule restates 2.75rem. The structural
 * fix is a target-size entry in `settings.custom`, emitted by
 * `design-to-theme-json`, at which point this declaration becomes an alias and
 * then goes. `rem` rather than `px`, so the floor grows with a reader's
 * default text size the way the type does.
 *
 * Two consumers: the accordion row in *Question styles* above, and the
 * standalone links below. A pattern adding a consumer adds its address to the
 * declaration list and nothing else.
 *
 * STANDALONE LINKS. The hero's secondary link, the `cta-secondary` slot in
 * `hero-split` and `hero-stack`, and the "contact us" link in
 * `qa-aside-accordion` each sit alone as their own paragraph. They are
 * controls, not links in running prose, so the inline exemption item 21
 * carries from WCAG does not reach them. Before this rule they measured
 * 18.4px and 20px tall.
 *
 * `person-grid`'s bio link joins them at 4.48.0, the `person-link` slot: the
 * last paragraph of each person's text group, alone on its line, the bridge
 * to that person's bio page. Not yet measured; it is measured on the
 * pattern's pull request preview, at 390 and 1440 on every band.
 *
 * THE HIT AREA GROWS AND THE TEXT DOES NOT MOVE. The link becomes inline-block
 * and takes `padding-block` of half the gap between its own line box (`1lh`)
 * and the floor, cancelled by an equal negative `margin-block`. The
 * paragraph's line box, and everything after it, stays the size it was.
 * Inline-block rather than padding on the inline box, because an inline box's
 * block padding applies per line fragment and a two-line link would carry two
 * overlapping boxes; inline-block gives one. `max()` and `min()` clamp at zero,
 * so a client whose line box already reaches the floor gets nothing.
 *
 * Measured 2026-10-02 on draft 1241 by injection, nothing saved: every band, at
 * 390 and 1440, front end and editor canvas. Link text rects identical before
 * and after to 0.01px. Hero link 480.6 by 44 at 1440, and on two lines at 390,
 * 342.4 by 68 (327.2 by 68 in the canvas). Aside link 95.6 by 44. No other
 * element in the four patterns moved. Hit-tested 6px above and below the text:
 * the surrounding group before, the link after.
 *
 * WHAT DOES CHANGE IS THE FOCUS RING. It traces the 44px box, one rectangle
 * around a two-line link, where it used to follow each line. Ruled acceptable
 * 2026-10-02 (Scott) over a pseudo-element hit area, which keeps the old ring
 * but leaves the end of line one unpadded and is sized differently by Chrome
 * and Firefox. Editing in the canvas is unaffected: a click lands the caret
 * inside the text, and typing there stays inside the link.
 *
 * SELECTED BY POSITION, because neither pattern gives these slots a class,
 * and a pattern change belongs to the patterns workspace. The hero link is the
 * paragraph after the group that holds the buttons. The aside link is the
 * second and last paragraph of its group, which no accordion answer matches,
 * because a panel is not a group. Both patterns are locked `contentOnly`, so
 * the structure holds. `p > a:only-child` was rejected: `:only-child` ignores
 * text nodes, so it also matches a link inside a sentence (rule 1's docblock
 * at the top of this file). KNOWN LIMIT: if either pattern restructures, these
 * selectors match nothing and the targets fall back to 18.4 and 20px without a
 * sound. A slot class from the patterns workspace would be the durable address.
 *
 * `person-grid` ADDS A THIRD LIMIT OF ITS OWN. The bio link is optional and
 * uniform by section, so a section that drops it leaves the credentials or the
 * body as the last paragraph, and a link inside that prose would take the
 * floor too, kept on one line by inline-block. It is the third consumer
 * selected by position, which is the count at which the slot class stops
 * being a nicety. Raised in GravityBrain 2026-10-12.
 * `point-stack-split`'s card link joins them at 4.51.1, the `card-link`
 * slot: the last paragraph of a card's text region, after the points. It
 * measured 20px tall at 1440 before this rule (2026-10-13). Optional and
 * uniform by section; a section that drops it leaves the points group last,
 * which is not a paragraph, so nothing else matches. The fourth consumer
 * selected by position.
 */
.c-hero-split,
.c-hero-stack,
.c-qa-accordion,
.c-qa-aside-accordion,
.c-person-grid,
.c-point-stack-split,
.c-hero-split-form,
.c-location-split,
.c-header,
.c-footer {
  --c-target-min: 2.75rem;
}

.c-hero-split .wp-block-group:has(> .wp-block-buttons) + p > a,
.c-hero-stack .wp-block-group:has(> .wp-block-buttons) + p > a,
.c-qa-aside-accordion .wp-block-group > p + p:last-child > a,
.c-person-grid .wp-block-group-is-layout-grid > .wp-block-group > .wp-block-group > p:last-child > a,
.c-point-stack-split > .alignwide > .wp-block-group > .wp-block-group > :first-child > p:last-child > a,
.c-hero-split-form > .alignwide > .wp-block-group:first-child p > a,
.c-hero-split-form > .alignwide > .wp-block-group:last-child > .wp-block-group > p:last-child > a,
.c-location-split > .alignwide > .wp-block-group:first-child p > a {
  display: inline-block;
  padding-block: max(0px, calc((var(--c-target-min) - 1lh) / 2));
  margin-block: min(0px, calc((1lh - var(--c-target-min)) / 2));
}

/* THE FOOTER, ADDRESSED BY SLOT CLASS, NOT BY POSITION. Added at 0.3.4
 * (Design Team review of the Brand Preview page, 2026-10-25). Before it, the
 * footer's phone and menu links measured 27px tall and Privacy and Terms 18px,
 * and on a phone the three menu links sat about 2px apart. The parts are this
 * theme's own, so they carry slot classes: `c-footer__phone`, `c-footer__nav`,
 * and `c-footer__legal`, the last on both `parts/footer.html` and
 * `parts/footer-landing.html`. That is the durable address the docblock above
 * asks for, and none of these selectors breaks if the parts are re-laid out.
 *
 * THE MENU LINKS TAKE REAL HEIGHT, NOT THE PADDING TRICK. Stacked links with a
 * negative margin would overlap their neighbours' hit areas, so each item is
 * a 44px row and the menu's own gap is 0 in the part. A one-word legal link
 * also takes a minimum width, so "Terms" in a narrow face still clears 44 by
 * 44. Measured 2026-10-25 on the starter site by injection, nothing saved:
 * phone 155 by 44, menu links 44 tall, Privacy 48 by 44, Terms 44 by 44, at
 * 390 and 1440; the footer at 390 grew from 476px (with the old light base
 * strip) to 543px, under one phone screen.
 */
.c-footer__nav .wp-block-navigation-item__content {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--c-target-min);
}

.c-footer__phone > a,
.c-footer__legal a {
  display: inline-block;
  padding-block: max(0px, calc((var(--c-target-min) - 1lh) / 2));
  margin-block: min(0px, calc((1lh - var(--c-target-min)) / 2));
}

.c-footer__legal a {
  min-inline-size: var(--c-target-min);
  text-align: center;
}

/* THE HEADER'S LINKS, SAME FLOOR, SAME METHOD. Added at 0.4.2 by the
 * accessibility pass over the inherited parts (2026-10-25). Two header links
 * sat under 44px while the footer's had been raised: the desktop menu's
 * top-level links, 27px tall, and the landing header's phone number, 27px
 * tall, which is the one tap-to-call target on a landing page and on a phone.
 * Both clear the AA minimum of 24px; neither met the fleet's 44px, which
 * `ops-accessibility-standards.md` records as an exceedance on purpose so that
 * nobody later settles for the minimum.
 *
 * THE PADDING METHOD, NOT A TALLER HEADER. The header's height is set by its
 * button and its logo, and `--c-header-height` sizes every scroll-margin and
 * the hold offset, so a link that made the bar taller would move all of them.
 * The negative margin gives back exactly what the padding adds, so the bar,
 * the mega panel's offset parent and the stance module's measurement are
 * unchanged, and only the hit area grows. Links sit side by side with a gap,
 * so the added height overlaps no neighbour. The mobile overlay's own links
 * are 52px rows already and are not touched.
 *
 * TOP-LEVEL ITEMS ONLY, by child combinator, so the mega panel's cells and a
 * submenu's links keep their own rules (2d).
 *
 * A SUBMENU PARENT IS A `<button>` AND TAKES A DIFFERENT METHOD. Under
 * `submenuVisibility: click` the parent is a button with the same class, and
 * the padding method moved the open panel up 1px when measured on 2026-10-25
 * (panel top 57px before, 56px after). The browser's own 1px of padding on a
 * button is part of its natural height, so padding that cancels exactly for a
 * link over-cancels for a button. The panel's geometry was recorded as
 * unchanged by the stance module's comment and is not to be disturbed, so the
 * parent's hit area grows by a pseudo-element instead, which takes no part in
 * layout at all: nothing moves, and a click or tap on it lands on the button.
 * The focus ring still traces the visible button. `min()` clamps the inset at
 * zero so a client whose parent is already 44px tall gets nothing.
 */
.c-header__nav .wp-block-navigation__container > .wp-block-navigation-item > a.wp-block-navigation-item__content,
.c-header__phone > a {
  padding-block: max(0px, calc((var(--c-target-min) - 1lh) / 2));
  margin-block: min(0px, calc((1lh - var(--c-target-min)) / 2));
}

.c-header__nav .wp-block-navigation__container > .wp-block-navigation-item > .wp-block-navigation-submenu__toggle {
  position: relative;
}

.c-header__nav .wp-block-navigation__container > .wp-block-navigation-item > .wp-block-navigation-submenu__toggle::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block: min(0px, calc((100% - var(--c-target-min)) / 2));
}

.c-header__phone > a {
  display: inline-block;
}

/* ── Quote styles ────────────────────────────────────────────────────────
 * Fleet defaults for the Cards axis on the testimonials, per
 * `gravitysite-design-system-standards.md`, "Quote styles", with the values
 * in the quote treatment contract in
 * `gravitysite-design-system-constants.md`. Two values — Ruled and Separate —
 * and no None, on the same ground as the Question styles above: three
 * untreated accounts at similar length are a parsing problem rather than a
 * restrained state. Ruled is the fleet default and writes no class, which is
 * why the rule below is unconditional on the pattern address and Separate has
 * to cancel it.
 *
 * TWO PATTERNS DECLARE THIS AXIS, `c-testimonial-grid` and
 * `c-testimonial-aside-quote`, and the selector lists below are written as
 * lists for the reason the Question styles block gives: a declarer is one
 * selector in each list and nothing else. In the aside, Separate fills the
 * testimonials only: the founder's quote sits outside the grid container the
 * unit address names, so no rule here reaches it. The unit is addressed as a direct child of the grid container,
 * which is `point-grid`'s address for the same shape.
 *
 * THE FIRST RULE IS NOT OURS AND HAS TO GO. Core's
 * `wp-block-library-theme` stylesheet gives every `.wp-block-quote` a
 * 0.25em inline-start border, 1em of inline-start padding, and 1.75em of
 * block-end margin. The border is the one the operator saw on the first
 * render and read as our treatment; the padding takes roughly 17px out of
 * a track whose content column is already near 310px at `wideSize` 1140px.
 * Neither is a value this estate chose, and the quotation is not a
 * pull-quote sitting inside a wide measure — it is the content of a narrow
 * grid track and should have all of it.
 *
 * THE MARGIN RESET IS NOT REDUNDANT, and it is the rule most likely to be
 * cut as such. Core writes `margin` on `.wp-block-quote`, and the layout
 * stylesheet writes `margin: 0` on `.wp-container-x > *` — both plain
 * classes at 0,1,0, so which one wins is decided by source order alone,
 * which is not something to depend on. `.wp-block-image` carries the
 * identical 1em bottom margin and the identical tie, which is why the
 * attribution frame at the end of this block zeroes it too. This file
 * settles both rather than inheriting whichever order the page happens to
 * print.
 *
 * NO MEASURE CAP, AND ONE WOULD BE INERT. At `wideSize` 1140px the grid
 * lays three tracks of roughly 359px, and the unit's own `spacing|40`
 * padding leaves a content column near 310px — about 37 characters a line
 * at the body step, under a comfortable 45 to 75 rather than over it. The
 * exposure runs the other way from prose: a cap protects nothing here and
 * the reading to watch is a track too narrow, not too wide. Carried as an
 * open entry in `patterns/testimonial-grid-refinements.md` with its trigger,
 * matching the answer-measure entry beside it.
 *
 * RULED IS A PSEUDO-ELEMENT AND NOT A BORDER, and the reason is the unit's
 * padding: the pattern writes it as an inline style, which no author rule
 * in this file can reach, so a `border-inline-start` would move the content
 * edge by its own thickness with nothing able to compensate. The
 * mechanism, the 3px, and the 20% are the contract's — see *An edge on the
 * band tracks the band*, which is also why the rule draws from
 * `currentColor` rather than from `gravity-border` and is the one thing
 * here most likely to be "corrected" by a later reader.
 *
 * `position: relative` IS UNCONDITIONAL RATHER THAN SCOPED TO RULED. It
 * hosts Ruled's rule and Separate's mark both, changes no box on its own, and
 * scoping it would mean writing the same declaration in two selector lists
 * that already differ by one class.
 *
 * BOXED CARRIES A FILL AND A RADIUS AND NO BORDER, the same reading that
 * gives `cards-grouped` and `questions-separate` none: the fill and the radius
 * already bound the object and a third edge device is one too many.
 *
 * BOXED ADDS NO PADDING, AND THAT IS THE ONE PLACE THIS BLOCK DEPARTS FROM
 * THE QUESTION STYLES ABOVE. There the pattern ships an unstyled list and
 * the inset a fill needs arrives with the fill. Here the unit's padding is
 * structure and ships regardless of treatment — `point-grid`'s ruling that
 * padding is the inset a surface needs — so Ruled and Separate sit on the same
 * box and only the paint changes.
 *
 * Per *A fill obliges a tonal set*, Separate emits the whole set on every
 * ground and not scoped to `band-dark`: the fill is what the foregrounds
 * are measured against and it does not change with the band behind it.
 * `styles/band-dark.json` reaches into a unit by descendant selector, so a
 * unit left to inherit would take `gravity-link-inverse` and
 * `gravity-focus-inverse` at 1.00 against a light fill — SC 1.4.3 and SC
 * 2.4.7, live the moment an account holds a link. Headings are targeted
 * directly rather than left to inherit, and the unit now has one: the
 * optional `quote-title` is a real H3. Link decoration is `inherit`, since
 * the prose underline rule at the top of this file already reaches a
 * `blockquote`. Ruled owes no set at all, because it paints nothing.
 *
 * STANDING DEFECT, REPORTED NOT FIXED: `gravity-surface-nested` measures
 * 1.10 against `band-subtle`'s ground, so Separate units are near-invisible on
 * that band. Same reading already recorded for `cards-grouped` and
 * `questions-separate`, same upstream cause, and it is a decision about bands
 * rather than about this treatment.
 *
 * THE MARK BELONGS TO BOXED ALONE. On Ruled the rule already anchors the
 * leading edge and two marks doing one job is noise. Three things about it
 * are this file's rather than the contract's.
 *
 * First, IT HAS ITS OWN LINE, above the unit's first block (6.2.3). It was a
 * watermark behind the text until 6.2.2, which put the title on top of it
 * once units opened with one. In flow it cannot collide with anything, and
 * it no longer needs a stacking context or a corner offset restating the
 * pattern's padding token, so both went with the move.
 *
 * Second, IT TAKES `::before`, the ruled rule's pseudo-element, because
 * Separate cancels that rule anyway and `::before` sits first in the flow.
 * The base rule's `position`, `inline-size`, and `background` are reset on
 * it for that reason.
 *
 * Third, THE GLYPH IS DECLARED TWICE ON PURPOSE. The second declaration
 * carries the empty alternative text that keeps a decorative character out
 * of the accessibility tree; where that syntax is not supported the whole
 * declaration is dropped and the first one stands. Do not collapse them
 * into one.
 *
 * Fourth, THE SIZE IS A LITERAL AND THE FAMILY IS PINNED, and both come
 * from the same rule in the contract: decorative and frame dimensions are
 * written literally and never bound to the type ramp, because a ramp step
 * expresses an em box and an em box is not a physical dimension. `text-6`
 * shipped first and rendered roughly 10px of visible ink from a 35px step,
 * which at 20% is nothing. `96px` was the first value read off the render;
 * `64px` replaced it at 6.2.3, read off the render once the mark stood on
 * its own line, where it no longer needs size to survive text over it.
 *
 * The family pin is that rule one order smaller. A literal font-size is
 * still not a literal ink size: 96px is 96px of em box in whatever font
 * renders it, and a serif and a sans put very different ink in that box.
 * The pin NARROWS the variance band rather than closing it — Georgia is
 * present on Windows, macOS, and iOS, and on Linux and most Android the
 * stack falls through to the generic `serif`, whose quote glyphs carry
 * less ink. That is accepted rather than escalated to an inline SVG,
 * because this is a decorative mark at 35%: a lighter mark on some
 * devices loses no information. The frame below is the opposite case,
 * which is why it got a rule where this gets a mitigation.
 *
 * THE ATTRIBUTION FRAME IS NOT A TREATMENT and applies under both values.
 * Width belongs to the theme resolver under `image-handling-architecture.md`
 * and that layer is not built; until it is, this is the only place the
 * frame's dimensions can live.
 *
 * HEIGHT IS THE CONSTRAINED AXIS AND WIDTH FLOATS TO THE CAP. A square
 * headshot renders 48 x 48; a wide wordmark renders up to 96 wide at the
 * same 48 tall, so both sit on the baseline the attribution row needs and
 * neither sizes the row. `flex: 0 0 auto` keeps the frame off the row's
 * shrink budget. THIS REQUIRES THE PATTERN TO DECLARE NO `aspectRatio` —
 * an inline ratio beats every rule in this file regardless of selector, and
 * `!important` is forbidden here, so a declared ratio would make these two
 * declarations unachievable rather than merely awkward. The pattern keeps
 * `scale: contain`, and that is what makes the cap safe: at 48px tall a
 * 250x39 mark wants 307px of width, the cap clamps it to 96, and the box
 * and the content then disagree at 6.4:1. `object-fit` is exactly what
 * resolves that — 96 x 15, centred. Without it the mark stretches.
 *
 * THE CAP IS BOUNDED BY THE TRACK, and no value here can change that. At
 * `wideSize` 1140 the content column is 311px, the row's gap is 16px, and
 * the name stack needs about 190px to hold its second line, which leaves
 * roughly 105px. So a 6.4:1 mark is about 15px tall whatever this file
 * says. Constraining height fixes the alignment and the wrap and takes a
 * headshot from 44px to 48px; it does not make a wide wordmark legible.
 * The lever for that is the unit's layout and is deliberately not pulled.
 * The consequence is a selection rule in `ops-pattern-reference.md` — drop
 * the slot for a section whose marks will not read, since it is optional
 * and uniform by use, and an unreadable signal is noise rather than a
 * weaker signal.
 *
 * No `!important` anywhere below, for the reason the two blocks above give.
 */

/* The quotation takes the full measure of its track. See the first two
 * paragraphs of the docblock: none of these three declarations is ours.
 */
:is(.c-testimonial-grid, .c-testimonial-aside-quote) .wp-block-quote {
  border: 0;
  padding-inline-start: 0;
  margin-block-end: 0;
}

/* The unit hosts both pseudo-elements under either treatment. */
:is(.c-testimonial-grid, .c-testimonial-aside-quote) .wp-block-group-is-layout-grid > .wp-block-group {
  position: relative;
}

/* Ruled — the fleet default, unconditional. Full height of the unit, seated
 * at the inline-start edge with no offset taken out of the padding.
 */
:is(.c-testimonial-grid, .c-testimonial-aside-quote) .wp-block-group-is-layout-grid > .wp-block-group::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: 3px;
  background-color: color-mix(in srgb, currentColor 20%, transparent);
}

/* Separate. Its mark below takes over `::before`, which cancels the ruled
 * rule, since the default above is unconditional.
 */
:is(.c-testimonial-grid--separate, .c-testimonial-aside-quote--separate) .wp-block-group-is-layout-grid > .wp-block-group {
  background-color: var(--wp--preset--color--gravity-surface-nested);
  border-radius: var(--wp--custom--rounded--md);
}

/* Separate's tonal set. Headings are targeted directly rather than left to
 * inherit, for the reason given against the card styles above.
 */
:is(.c-testimonial-grid--separate, .c-testimonial-aside-quote--separate) .wp-block-group-is-layout-grid > .wp-block-group,
:is(.c-testimonial-grid--separate, .c-testimonial-aside-quote--separate)
  .wp-block-group-is-layout-grid
  > .wp-block-group
  :is(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--preset--color--gravity-text);
}

:is(.c-testimonial-grid--separate, .c-testimonial-aside-quote--separate)
  .wp-block-group-is-layout-grid
  > .wp-block-group
  a:not(.wp-element-button) {
  color: var(--wp--preset--color--gravity-link);
}

:is(.c-testimonial-grid--separate, .c-testimonial-aside-quote--separate)
  .wp-block-group-is-layout-grid
  > .wp-block-group
  a:focus-visible,
:is(.c-testimonial-grid--separate, .c-testimonial-aside-quote--separate)
  .wp-block-group-is-layout-grid
  > .wp-block-group
  .wp-element-button:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus);
}

/* The mark — Separate alone. `\201C` is the left double quotation mark; the
 * second `content` is the same glyph with empty alternative text, and is
 * dropped whole where that syntax is unsupported.
 *
 * THE MARK HAS ITS OWN LINE, above the unit's first block (6.2.3, Scott,
 * 2026-10-19). Until 6.2.2 it was an absolutely positioned layer behind the
 * text, seated in the unit's padding, which held while a unit opened with its
 * quotation. Once units opened with a title, the title sat on the mark, and a
 * glyph under text reads as a printing fault rather than an anchor. In flow,
 * it can never collide with whatever block opens the unit, the title or, with
 * the title deleted, the quotation. `block-size` is half the glyph's em
 * because a left double quotation mark's ink sits in the top half of its
 * line; the rest would be empty space above the title. It is a pseudo-element
 * rather than a block, so the pattern asserts nothing about it, and
 * `::before` is free because Separate has no ruled rule. Size and colour are
 * the quote treatment contract's rows 6 and 7.
 */
:is(.c-testimonial-grid--separate, .c-testimonial-aside-quote--separate) .wp-block-group-is-layout-grid > .wp-block-group::before {
  content: "\201C";
  content: "\201C" / "";
  position: static;
  display: block;
  inline-size: auto;
  block-size: 0.5em;
  margin-block-end: var(--wp--preset--spacing--30);
  background: none;
  font-family: Georgia, "Times New Roman", Times, serif;
  font-size: 64px;
  line-height: 1;
  color: color-mix(in srgb, currentColor 35%, transparent);
}

/* The attribution image frame. Both treatments. The margin is core's and is
 * zeroed here rather than left to a source-order tie — see the docblock.
 */
.c-testimonial-grid figure.wp-block-image {
  margin: 0;
  flex: 0 0 auto;
}

/* Height constrained, width floating to the cap. The pattern declares no
 * aspect ratio, which is what makes this reachable, and keeps `object-fit:
 * contain`, which is what makes the cap safe.
 */
.c-testimonial-grid figure.wp-block-image img {
  block-size: 48px;
  inline-size: auto;
  max-inline-size: 96px;
}

/* The mark sits above the name in every testimonial, in the grid and in the
 * aside. Beside the name it wrapped under it whenever the detail line ran long,
 * so one grid mixed both arrangements once four testimonials made the tracks
 * wide; one rule removes the raggedness (6.0.0).
 */
:is(.c-testimonial-grid, .c-testimonial-aside-quote) .wp-block-group-is-layout-grid .wp-block-group.is-layout-flex:has(> figure.wp-block-image) {
  flex-direction: column;
  align-items: flex-start;
}

/* Columns follow the count, so a set never breaks three plus one (6.0.0).
 * Four sit two by two from `bp-1`, three sit three across from `bp-1`, and
 * five or six keep three to a row. Below `bp-1` every set is one column. The
 * doubled class is the same specificity lift `stat-grid` uses over core's
 * grid rule, which is why no `!important` is needed.
 */
.c-testimonial-grid .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid {
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 768px) { /* bp-1 */
  .c-testimonial-grid .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .c-testimonial-grid .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid:has(> :nth-child(4):last-child) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* The name and its role read as one signature: about 2px between them and a
 * tighter line height, in the grid and in the aside, founder included. The
 * pattern writes a vertical flex group around the two, whose gap is core's
 * class rule at 0,1,0, so this lifts above it without `!important`.
 */
:is(.c-testimonial-grid, .c-testimonial-aside-quote) .wp-block-group.is-layout-flex.is-vertical {
  gap: 2px;
}
:is(.c-testimonial-grid, .c-testimonial-aside-quote) .wp-block-group.is-layout-flex.is-vertical > p {
  line-height: 1.35;
}

/* ── Testimonial aside quote ─────────────────────────────────────────────
 *
 * Skin for `gravitystack/testimonial-aside-quote`: a founder's quote beside
 * four testimonials. Ruled in GravityBrain 2026-10-19 (Scott, GR9-T42). The
 * testimonial units share the rules above through the selector lists; this
 * block carries what is the aside's own. Every rule is keyed on the pattern's
 * class, so nothing reaches a page until the pattern ships; it merges first so
 * the pattern's pull request preview shows the real layout.
 *
 * THE SPLIT IS THE LAST `alignwide` CHILD, because the optional heading area is
 * also an `alignwide` child and must not take the split's grid (fixed 6.0.1:
 * the first render of the real pattern showed the heading and intro side by
 * side).
 *
 * THE MARKUP CONTRACT THIS BLOCK READS, written into the pattern's brief. The
 * section is one constrained group. Its optional first child is the heading
 * area. Its `alignwide` child is the SPLIT: a plain group with no grid layout,
 * holding two children, the founder group first and the testimonial grid
 * second. The split is deliberately not a core grid layout, because the unit
 * address above is `.wp-block-group-is-layout-grid > .wp-block-group` and a
 * grid-layout split would make the founder group match it. The founder group
 * holds the quote, then the attribution (an optional portrait beside a
 * vertical name-and-role stack), then the optional signature. The testimonial
 * grid is a core grid group of exactly four units shaped as in
 * `testimonial-grid`.
 *
 * LAYOUT. One column below `bp-2`, the quote first. From `bp-2` the split is
 * two columns, one to two, with the quote vertically centred against the
 * grid. The testimonials sit two by two from `bp-1` and one per row below it.
 *
 * THE QUOTE PINS AS THE TESTIMONIALS SCROLL (6.2.0, Scott, 2026-10-19). From
 * `bp-2`, in a window at least 700px tall, the founder group is
 * `position: sticky` below the site header and aligns to the top of the split
 * instead of its centre, so it stays in view while the visitor reads the four
 * testimonials, then releases at the split's bottom edge. Measured on the real
 * pattern at 1440 by 900: the grid is about 1,310px tall and the founder group
 * about 285px, so the quote has about 1,000px of travel. Centring and pinning
 * cannot both hold, because a centred item gets only the travel left below it,
 * about half. The pin sits at `--c-header-height` plus `spacing-40`, because
 * the header is itself sticky; without the offset the quote slides under it.
 * Below 700px tall the centred layout stays, since a group taller than the
 * space under the header would clip. The rule needs no ancestor with `overflow`
 * set, and none exists between the split and the root, so a future `overflow`
 * on a wrapper or the section would silently end the pin.
 *
 * THE QUOTE IS SET LARGE AND BOLD: heading family, weight 600, `text-5` from
 * `bp-2` and `text-4` below it. Bold is the ruled default; there is no weight
 * setting until a real case needs a conversational voice.
 *
 * THE LOGO SITS ABOVE THE NAME on every testimonial, by the shared rule above,
 * so a wide wordmark is not squeezed into the track.
 *
 * THE SIGNATURE renders 144px tall, the ruled size (double the first render),
 * at its own width up to the column. The portrait is a 64px circle. Neither
 * is a card-role image, so neither is declared in the delivery role config.
 */
/* The heading area is a constrained wide group, and core gives such a group the
 * theme's global side padding, which would inset it from the split below it.
 * Its text then sits on the left edge of the wide content, as ruled.
 */
/* `:root:root` lifts it over the root gutter re-assertion at the top of this file. */
:root:root .c-testimonial-aside-quote > .alignwide.has-global-padding {
  padding-inline: 0;
}

.c-testimonial-aside-quote > .alignwide:last-child {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--wp--preset--spacing--60);
}
.c-testimonial-aside-quote > .alignwide:last-child > * {
  margin: 0;
}
@media (min-width: 1200px) { /* bp-2 */
  .c-testimonial-aside-quote > .alignwide:last-child {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    align-items: center;
  }
}
@media (min-width: 1200px) and (min-height: 700px) { /* bp-2, tall enough to pin */
  .c-testimonial-aside-quote > .alignwide:last-child {
    align-items: start;
  }
  .c-testimonial-aside-quote > .alignwide:last-child > :first-child {
    position: sticky;
    top: calc(var(--c-header-height, 0px) + var(--wp--preset--spacing--40));
  }
}

.c-testimonial-aside-quote .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid {
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 768px) { /* bp-1 */
  .c-testimonial-aside-quote .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.c-testimonial-aside-quote > .alignwide:last-child > :first-child {
  max-inline-size: var(--wp--style--global--content-size);
}
@media (min-width: 1200px) { /* bp-2 */
  .c-testimonial-aside-quote > .alignwide:last-child > :first-child {
    max-inline-size: none;
  }
}

.c-testimonial-aside-quote > .alignwide:last-child > :first-child .wp-block-quote p {
  font-family: var(--wp--preset--font-family--heading);
  font-weight: 600;
  font-size: var(--wp--preset--font-size--text-4);
  line-height: 1.3;
  text-wrap: pretty;
}
@media (min-width: 1200px) { /* bp-2 */
  .c-testimonial-aside-quote > .alignwide:last-child > :first-child .wp-block-quote p {
    font-size: var(--wp--preset--font-size--text-5);
  }
}

/* The founder's images: the portrait and the signature. */
.c-testimonial-aside-quote > .alignwide:last-child > :first-child figure.wp-block-image {
  margin: 0;
  flex: 0 0 auto;
}
.c-testimonial-aside-quote > .alignwide:last-child > :first-child figure.wp-block-image img {
  inline-size: auto;
  max-inline-size: 100%;
  object-fit: contain;
  object-position: left center;
}
.c-testimonial-aside-quote > .alignwide:last-child > :first-child .is-layout-flex > figure.wp-block-image img {
  block-size: 64px;
  inline-size: 64px;
  border-radius: 50%;
  object-fit: cover;
}
.c-testimonial-aside-quote > .alignwide:last-child > :first-child > figure.wp-block-image img {
  block-size: 144px;
}

/* The testimonials' attribution image: the same frame as the grid, and
 * always above the name.
 */
.c-testimonial-aside-quote .wp-block-group-is-layout-grid figure.wp-block-image {
  margin: 0;
  flex: 0 0 auto;
}
.c-testimonial-aside-quote .wp-block-group-is-layout-grid figure.wp-block-image img {
  block-size: 48px;
  inline-size: auto;
  max-inline-size: 96px;
}

/* ── Person grid ─────────────────────────────────────────────────────────
 *
 * Skin for `gravitystack/person-grid`, the one to four people the reader will
 * actually deal with. Ruled in GravityBrain 2026-10-12 (Scott). Every rule is
 * keyed on the pattern's class, so nothing here reaches a page until the
 * pattern ships; it merges first so the pattern's pull request preview shows
 * the real layout.
 *
 * THE PATTERN CARRIES THE ARRANGEMENT, THIS FILE CARRIES THE MEASURES. Each
 * person is a core group in flex layout that wraps, aligned to the top, with
 * two children: the portrait and a flow-layout text group. Core emits the
 * flex CSS off that attribute and its column gap, `spacing|40`. What core
 * cannot say is how wide each child is, and that is all this section says.
 *
 * BESIDE OR ABOVE IS DECIDED BY WRAPPING, NOT BY A QUERY. The text never
 * takes less than 0.5 of `contentSize` beside the portrait. When a person is
 * too narrow to hold the portrait, the gap, and that minimum on one line, the
 * text wraps under the portrait. The switch point is therefore a sum, not a
 * number: portrait 200px + gap 24px + 0.5 x 640px = 544px on this theme.
 *   - Columns 1: the person is capped at `contentSize`, 640px, so beside,
 *     with the text about 416px wide.
 *   - Columns 2 at 1440px: a cell is (1140 - 32) / 2 = 554px, so beside,
 *     with the text 330px wide. Below a screen of about 1,168px a cell would
 *     drop under 544px, so the grid goes to one column instead: see THE UNIT
 *     FLOOR below.
 *   - A 390px phone: the person is 342px, so above, at either setting.
 * 0.5 IS A MINIMUM LINE LENGTH, NOT A TARGET. It is the narrowest the text may
 * run beside a portrait: 320px, about 44 characters of Figtree at 17px, inside
 * the 40 to 50 typographers allow a narrow column. It clears a 554px cell by
 * 10px. Do not tune it to force or prevent a layout at some width. Ruled
 * 2026-10-12 (Scott, with the Design Team panel), replacing a 0.58 that
 * stacked two people at Columns 2 and left 354px empty beside each portrait.
 * Measured in a headless render with Figtree; the pattern's preview confirms.
 *
 * NO CONTAINER QUERY. Every core group is a layout wrapper, which *Container
 * query strategy* in `gravitysite-css-standards.md` bars from hosting one, and
 * a query's threshold could only be a literal, which drifts as each client's
 * measures move. `organisms/mega-featured.css` records what that drift cost
 * once. Wrapping derives its switch point from its own inputs, so a client
 * theme with different measures gets the right switch unasked.
 *
 * THE UNIT FLOOR: TWO ACROSS ONLY WHEN EACH PERSON CAN SIT BESIDE. The sum
 * above is declared once per card style as `--c-person-grid-unit-floor`,
 * and the pattern's grid reads it as its `minimumColumnWidth`, falling back to
 * 0.48 of `wideSize` where a theme does not set it (*Grid collapse* in
 * `ops-pattern-standards.md`). So a pair forms only where both cells hold the
 * portrait, the gap, and the text minimum on one line, and the layout never
 * flips beside, above, beside as the window widens. On this theme:
 *   - None: 200 + 24 + 320 = 544px. Two across from a screen of 1,168px.
 *   - Separate: 150 + 24 + 320, plus the card's 48px padding and 2px border,
 *     = 544px. Two across from 1,168px.
 *   - Grouped: 150 + 24 + 320 = 494px, inside the field's own padding. Two
 *     across from 1,116px. A proportion of `wideSize` could not hold Grouped:
 *     at 0.48 its 48px of padding left a 1,092px grid that never fitted two
 *     547px cells, measured on the pattern's preview 2026-10-12.
 * The floor is built from the same properties the portrait and text rules
 * read, so it cannot disagree with them. A client theme that wants pairs on
 * narrower screens sets a smaller `--c-person-grid-portrait-width` in its own
 * `site.css` and the floor follows; it never sets the floor itself. A client
 * with a wider `contentSize` gets a wider floor, and fewer pairs, unasked.
 * Ruled in GravityBrain 2026-10-12 (Scott).
 *
 * THE MINIMUM IS CLAMPED TO THE PERSON'S OWN WIDTH. `min(..., 100%)` is not
 * decoration: without it a 342px phone column holds a 320px minimum plus the
 * portrait and the text overflows sideways once it wraps.
 *
 * THE LINES PACK TO THE TOP. The grid stretches every person in a row to the
 * tallest one's height, and a wrapping flex row spreads that extra height
 * between its lines, so a shorter bio pushed its name down. Measured at
 * 1440px with uneven bios: names 41px apart before `align-content`, level
 * after. A card style wants the stretch, for equal card heights, so the
 * fix packs the lines rather than releasing the stretch.
 *
 * THE CAP SETS A SINGLE PERSON ON THE READING MEASURE. At Columns 1 the grid
 * track is the full wide measure, and text beside a portrait there runs to
 * about 105 characters a line. Capped at `contentSize` and centred, the person
 * lines up with the heading. At Columns 2 the cap does nothing, because a
 * 554px cell is already narrower than 640px.
 *
 * THE PORTRAIT IS 200px OR HALF THE PERSON, WHICHEVER IS SMALLER: 200px at
 * every desktop setting, about 171px on a 390px phone, with no breakpoint.
 * The 200px is `--c-person-grid-portrait-width`, declared once below. It is a
 * literal and a recorded deviation from *The raw-value exception*,
 * on the footing of `testimonial-grid`'s 96px cap: a skin decision with no
 * `theme.json` key behind it. `inc/delivery-role-config.php` restates it, so
 * change both together. The 4:5 shape is the pattern's, at `scale: cover`.
 *
 * CARDS SETTING: THE PORTRAIT STEPS DOWN TO 150px INSIDE A CARD. Separate
 * pads each card by `spacing|40` and draws a 1px border; Grouped pads the whole
 * grid by `spacing|40`. Either takes about 50px from each person, which at
 * Columns 2 is the room the beside layout needs: at 200px both stacked again
 * (504px and 530px of text under the photo). The card portrait is 0.75 of
 * `--c-person-grid-portrait-width`, so a client's smaller portrait steps down
 * in a card too, rather than a second literal a client could forget. At
 * 150px: 150 + 24 + 320 = 494px,
 * inside a 504px Separate card and a 530px Grouped cell. 160px fitted to the
 * pixel and was rejected as too fragile. The card frame carries the weight the
 * larger photo did. Grouped is for two or more people, a selection rule in the
 * catalogue entry: at Columns 1 its field spans the wide measure around one
 * person on the reading measure. Colours for both live in *Card styles*.
 *
 * THE BIO AND THE CREDENTIALS. From the pattern's 2026-10-19 amendment the
 * text group holds the name-and-role wrapper, then a bio wrapper holding one
 * or two paragraphs, then the credentials paragraph and the bio link, each on
 * every person or on none. Before it, the bio was one paragraph and the
 * credentials set exactly like it, so they read as one more bio paragraph
 * (Scott, on the GravityONE render, 2026-10-19). The credentials are found as
 * the first paragraph after the bio wrapper that is not the bio link, and set
 * smaller and in `gravity-text-muted`, the stat detail's treatment: 5.96:1 on
 * white, 5.13:1 on the card fill. Markup from before the amendment has no bio
 * wrapper, so nothing here matches it and those pages keep their old look.
 *
 * NO COLOUR ANYWHERE BELOW, EXCEPT THE CREDENTIALS. A band leaves explicitly
 * coloured text alone, so colour set here would stop the Pattern styles bands
 * reaching it. The credentials' muted colour is the one exception, and it
 * hands back to the band where muted fails: on Band dark with no card
 * treatment the credentials take the band's own text colour, as the stat
 * detail does. Inside a card the card fill is the ground on every band.
 */

/* The measures, declared once. The portrait and text rules read the first
 * two, and the pattern's grid reads the floor as its minimum column width.
 * Separate adds its padding on both sides and its 1px border on both sides;
 * Grouped's padding sits outside each person, so it adds nothing.
 */
.c-person-grid {
  --c-person-grid-portrait-width: 200px;
  --c-person-grid-text-min: calc(var(--wp--style--global--content-size) * 0.5);
  --c-person-grid-unit-floor: calc(
    var(--c-person-grid-portrait-width)
    + var(--wp--preset--spacing--40)
    + var(--c-person-grid-text-min)
  );
}

.c-person-grid--separate {
  --c-person-grid-unit-floor: calc(
    var(--c-person-grid-portrait-width) * 0.75
    + var(--wp--preset--spacing--40)
    + var(--c-person-grid-text-min)
    + 2 * var(--wp--preset--spacing--40)
    + 2px
  );
}

.c-person-grid--grouped {
  --c-person-grid-unit-floor: calc(
    var(--c-person-grid-portrait-width) * 0.75
    + var(--wp--preset--spacing--40)
    + var(--c-person-grid-text-min)
  );
}

/* One person at Columns 1 sits on the reading measure, centred. */
.c-person-grid .wp-block-group-is-layout-grid > .wp-block-group {
  inline-size: 100%;
  max-inline-size: var(--wp--style--global--content-size);
  margin-inline: auto;
  align-content: flex-start;
}

/* The portrait frame. The margin is core's and is zeroed, as in testimonial-grid. */
.c-person-grid .wp-block-group-is-layout-grid > .wp-block-group > figure.wp-block-image {
  flex: 0 0 min(var(--c-person-grid-portrait-width), 50%);
  margin: 0;
}

/* The image fills its frame at any upload size; the height follows the 4:5
 * aspect ratio the pattern declares.
 */
.c-person-grid .wp-block-group-is-layout-grid > .wp-block-group > figure.wp-block-image img {
  inline-size: 100%;
}

/* The text: grows into whatever the portrait leaves, and wraps under it when
 * that is less than its minimum.
 */
.c-person-grid .wp-block-group-is-layout-grid > .wp-block-group > .wp-block-group {
  flex: 1 1 0;
  min-inline-size: min(var(--c-person-grid-text-min), 100%);
}

/* Cards, Separate: the card's own padding. Surface and colours are in
 * *Card styles*.
 */
.c-person-grid--separate .wp-block-group-is-layout-grid > .wp-block-group {
  padding: var(--wp--preset--spacing--40);
}

/* Inside either card style the portrait steps down, so the beside layout
 * still fits at Columns 2. See the docblock.
 */
.c-person-grid--separate .wp-block-group-is-layout-grid > .wp-block-group > figure.wp-block-image,
.c-person-grid--grouped .wp-block-group-is-layout-grid > .wp-block-group > figure.wp-block-image {
  flex-basis: min(calc(var(--c-person-grid-portrait-width) * 0.75), 50%);
}

/* The credentials: smaller and muted, so they read as a qualification and not
 * a third bio paragraph. See THE BIO AND THE CREDENTIALS in the docblock.
 */
.c-person-grid .wp-block-group-is-layout-grid > .wp-block-group > .wp-block-group
  > .wp-block-group + .wp-block-group + p:not(:has(> a:only-child)) {
  font-size: var(--wp--preset--font-size--text-2);
  color: var(--wp--preset--color--gravity-text-muted);
}

/* Open on Band dark: muted fails on the band, so the band's text colour. */
.c-person-grid.is-style-band-dark:not(.c-person-grid--separate):not(.c-person-grid--grouped)
  .wp-block-group-is-layout-grid > .wp-block-group > .wp-block-group
  > .wp-block-group + .wp-block-group + p:not(:has(> a:only-child)) {
  color: inherit;
}

/* ── Point split CTA ─────────────────────────────────────────────────────
 *
 * Skin and arrangement for `gravitystack/point-split-cta`: a few short points
 * beside the ask, with an optional face. Ruled in GravityBrain 2026-10-19
 * (Scott, with the Design Team panel, Cugelman, and Kennedy), GravitySite
 * Roadmap GR9-T33; renders approved the same evening. Every rule is keyed on
 * the pattern's class, so nothing here reaches a page until the pattern ships;
 * it merges first so the pattern's pull request preview shows the real layout.
 *
 * THE TREE THESE RULES READ. Only the outer group carries a class (validation
 * item 20), so every rule is structural. The section holds one `alignwide`
 * group, the pair, in core flex layout that wraps, with two children:
 *   1. the main group: the heading area (eyebrow, H2, intro), then the points
 *      group, which holds an optional lead-word paragraph and the list;
 *   2. the panel: an optional person group (portrait, then a group of name and
 *      role), the H3, the Buttons block, the consequence paragraph, and an
 *      optional alternate-channel paragraph.
 *
 * THE PAIR WRAPS, NO QUERY. The main group takes what the panel leaves and
 * never less than 30rem; the panel takes about 19rem to 24rem. When the window
 * cannot hold both, the panel wraps under the points, so on a phone she reads
 * the facts, then the ask. Same footing as *Person grid*: wrapping derives its
 * switch point from its own inputs.
 *
 * THE LEAD WORD. When the points group opens with a paragraph, it is the word
 * every point continues ("One", on GravityONE; "No", on a client page), set
 * very large in Brand Text with the points stacked beside it behind a rule.
 * Its size is a recorded deviation from *The raw-value exception*: the approved
 * renders set it from about 71px on a phone to 136px on a desktop, and the
 * `statement` preset, the nearest token, tops out near 93px at 1440px, which
 * read as a heading rather than the idea of the section. The floor is the
 * `text-7` preset. Below core's wide break of 782px it sits above the points with no rule,
 * because beside it the points squeeze into a column a few words wide (the
 * 2026-10-19 phone render). Without it, the points stand alone. Whether there
 * is a lead word is the content's fact, so there is no setting for it.
 *
 * THE POINTS ARE MARKED WITH AN OPEN RING, the O of One, hung so a wrapped
 * line stays inside its point (Scott, 2026-10-19: without a marker the points
 * did not read as separate, worst on a phone). The ring is decorative and drawn
 * here, so it carries no text alternative; the list markup carries the count.
 *
 * THE PANEL IS A LIGHT GROUND ON EVERY BAND, as Price split's is: it emits its
 * own tonal set (text, heading, muted, link, focus, button fill and label) so a
 * dark band cannot turn its copy white on a light fill. *A fill obliges a tonal
 * set* in `gravitysite-design-system-constants.md`.
 *
 * COLOUR. The lead word, the rule, and the rings are Brand Text (`brand-11`):
 * 4.81:1 on white, large text and non-text only. On Band dark they take Gravity
 * Brand, 5.34:1, as the stat figure does. The role line is muted, 5.13:1 on the
 * panel fill.
 */
.c-point-split-cta > .alignwide > * {
  margin-block: 0;
}
.c-point-split-cta > .alignwide > :first-child {
  flex: 999 1 30rem;
  min-inline-size: min(30rem, 100%);
}
.c-point-split-cta > .alignwide > :last-child {
  flex: 1 1 19rem;
  min-inline-size: min(19rem, 100%);
  max-inline-size: 24rem;
}

/* The points. */
.c-point-split-cta > .alignwide > :first-child > :last-child > * {
  margin-block: 0;
}
.c-point-split-cta > .alignwide > :first-child > :last-child ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.c-point-split-cta > .alignwide > :first-child > :last-child li {
  position: relative;
  margin: 0;
  padding-block: 0.3em;
  padding-inline-start: 1.15em;
  font-family: var(--wp--preset--font-family--outfit);
  font-weight: 600;
  font-size: var(--wp--preset--font-size--text-5);
  line-height: 1.25;
}
/* The ring is decorative and drawn here, so it carries no text alternative. */
.c-point-split-cta > .alignwide > :first-child > :last-child li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 0.62em;
  inline-size: 0.5em;
  block-size: 0.5em;
  box-sizing: border-box;
  border: 0.11em solid var(--wp--preset--color--brand-11);
  border-radius: var(--wp--custom--rounded--full);
}

/* The lead word, above the points. */
.c-point-split-cta > .alignwide > :first-child > :last-child > p {
  font-family: var(--wp--preset--font-family--outfit);
  font-weight: 600;
  font-size: clamp(var(--wp--preset--font-size--text-7), 3rem + 6vw, 8.5rem);
  line-height: 0.9;
  letter-spacing: -0.02em;
  color: var(--wp--preset--color--brand-11);
  margin-block-end: var(--wp--preset--spacing--20);
}
/* Wide: the lead word beside the points, behind a rule. */
@media (min-width: 782px) {
  .c-point-split-cta > .alignwide > :first-child > :last-child:has(> p) {
    display: flex;
    align-items: center;
    gap: var(--wp--preset--spacing--40);
  }
  .c-point-split-cta > .alignwide > :first-child > :last-child > p {
    align-self: stretch;
    display: flex;
    align-items: center;
    margin-block-end: 0;
    padding-inline-end: var(--wp--preset--spacing--40);
    border-inline-end: 3px solid var(--wp--preset--color--brand-11);
  }
}

/* Band dark: Brand Text fails on the band, so Gravity Brand. */
.c-point-split-cta.is-style-band-dark > .alignwide > :first-child > :last-child > p {
  color: var(--wp--preset--color--gravity-brand);
  border-color: var(--wp--preset--color--gravity-brand);
}
.c-point-split-cta.is-style-band-dark > .alignwide > :first-child > :last-child li::before {
  border-color: var(--wp--preset--color--gravity-brand);
}

/* The panel: a light ground on every band, with its own tonal set. */
.c-point-split-cta > .alignwide > :last-child {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--30);
  padding: var(--wp--preset--spacing--50);
  background-color: var(--wp--preset--color--gravity-surface-nested);
  border-radius: var(--wp--custom--rounded--lg);
}
.c-point-split-cta > .alignwide > :last-child > * {
  margin: 0;
}
.c-point-split-cta > .alignwide > :last-child,
.c-point-split-cta > .alignwide > :last-child :where(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--preset--color--gravity-text);
}
.c-point-split-cta > .alignwide > :last-child > h3 {
  font-size: var(--wp--preset--font-size--text-5);
  line-height: 1.2;
}
.c-point-split-cta > .alignwide > :last-child .wp-element-button {
  inline-size: 100%;
  text-align: center;
  background-color: var(--wp--preset--color--gravity-cta);
  color: var(--wp--preset--color--gravity-cta-label);
}
.c-point-split-cta > .alignwide > :last-child .wp-element-button:hover {
  background-color: var(--wp--preset--color--action-10);
  color: var(--wp--preset--color--gravity-cta-label);
}
.c-point-split-cta > .alignwide > :last-child .wp-block-button {
  inline-size: 100%;
}
.c-point-split-cta > .alignwide > :last-child a:not(.wp-element-button) {
  color: var(--wp--preset--color--gravity-link);
}
.c-point-split-cta > .alignwide > :last-child a:focus-visible,
.c-point-split-cta > .alignwide > :last-child .wp-element-button:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus);
}
/* The consequence: the closer, held tight under the button. */
.c-point-split-cta > .alignwide > :last-child > .wp-block-buttons + p {
  margin-block-start: calc(-1 * var(--wp--preset--spacing--20));
  font-size: var(--wp--preset--font-size--text-2);
}
/* The alternate channel. */
.c-point-split-cta > .alignwide > :last-child > p + p {
  font-size: var(--wp--preset--font-size--text-2);
}

/* The person: a small portrait beside a name and role. */
.c-point-split-cta > .alignwide > :last-child > .wp-block-group {
  display: flex;
  align-items: center;
  gap: var(--wp--preset--spacing--30);
}
.c-point-split-cta > .alignwide > :last-child > .wp-block-group > * {
  margin: 0;
}
.c-point-split-cta > .alignwide > :last-child > .wp-block-group > figure.wp-block-image {
  flex: 0 0 4rem;
}
.c-point-split-cta > .alignwide > :last-child > .wp-block-group > figure.wp-block-image img {
  inline-size: 4rem;
  block-size: 4rem;
  border-radius: var(--wp--custom--rounded--full);
}
.c-point-split-cta > .alignwide > :last-child > .wp-block-group > .wp-block-group > * {
  margin: 0;
  line-height: 1.35;
}
.c-point-split-cta > .alignwide > :last-child > .wp-block-group > .wp-block-group > :first-child {
  font-weight: var(--wp--custom--weight--strong);
}
.c-point-split-cta > .alignwide > :last-child > .wp-block-group > .wp-block-group > :last-child {
  font-size: var(--wp--preset--font-size--text-2);
  color: var(--wp--preset--color--gravity-text-muted);
}

/* ── Price split ─────────────────────────────────────────────────────────
 *
 * Skin and arrangement for `gravitystack/price-split`: one price beside what it
 * includes. Ruled in GravityBrain 2026-10-13 (Scott), Copy-to-Site Decision 95,
 * renders approved the same day. Every rule is keyed on the pattern's class, so
 * nothing here reaches a page until the pattern ships; it merges first so the
 * pattern's pull request preview shows the real layout.
 *
 * THE TREE THESE RULES READ. Only the outer group carries a class (validation
 * item 20), so every rule is structural. The section holds a header group and
 * then the panel, the one `alignwide` group. The panel holds three children,
 * in this order:
 *   1. the bar: a group holding a group (figure paragraph, basis paragraph)
 *      and an optional Buttons block;
 *   2. the terms paragraph, optional;
 *   3. a group holding the H3 and the list.
 * With the terms line left out, child 2 is the list group; the rules below
 * select it by `:last-child` and the terms by `> p`, so either shape works.
 *
 * WHY THE THEME CARRIES THE ARRANGEMENT HERE. Core's grid layout cannot place
 * one child across two rows, and the wide layout needs the list to run beside
 * both the bar and the terms. So the panel's grid lives in this file, unlike
 * Person grid, where core's flex carries it.
 *
 * THE PANEL IS A LIGHT GROUND ON EVERY BAND, as a card is (see Card styles):
 * the dark band turns every heading, link, focus ring, and button inside it
 * white, so the panel emits its own tonal set on every ground: text, heading,
 * link, link focus, button focus, button fill, and button label. Text,
 * heading, and button were found in the renders; link and both focus rings
 * were missed until 4.57.1, when an injected link measured 1.00:1 on the
 * panel on Band dark and the button's ring was white on white.
 *
 * THE BAR HOLDS WHILE SHE READS. It is sticky at `--c-hold-top`, so it sits
 * under the header whenever the header shows and moves up when the header
 * tucks away, with the header's own 180ms timing. It lets go where the panel
 * ends, because the panel is its containing block. Phones only, below core's
 * 782px wide break: on wide screens the price column sits beside the list and
 * stays put (Scott, 2026-10-13, from the pattern's preview, 4.50.1). And only on
 * screens at least 600px tall: measured on a 390 by 844 phone the bar is 95px,
 * and with the header showing the two take 172px. The editor's *While scrolling* choice
 * Scroll with the page writes `c-price-split--scroll` and turns it off.
 *
 * THE TERMS LINE IS NEVER FINE PRINT. It inherits body size on every client:
 * regulated professions must state what is charged on top of an advertised
 * fee, and some may not set it in small print. Do not shrink it.
 */
.c-price-split > .alignwide {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  margin-block-start: var(--wp--preset--spacing--60);
  background-color: var(--wp--preset--color--gravity-surface);
  border: 1px solid var(--wp--preset--color--gravity-border);
  border-radius: var(--wp--custom--rounded--lg);
}
.c-price-split > .alignwide > * {
  margin-block: 0;
}
.c-price-split > .alignwide,
.c-price-split > .alignwide :where(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--preset--color--gravity-text);
}
.c-price-split > .alignwide .wp-element-button {
  background-color: var(--wp--preset--color--gravity-cta);
  color: var(--wp--preset--color--gravity-cta-label);
}
.c-price-split > .alignwide .wp-element-button:hover {
  background-color: var(--wp--preset--color--action-10);
  color: var(--wp--preset--color--gravity-cta-label);
}
.c-price-split > .alignwide a:not(.wp-element-button) {
  color: var(--wp--preset--color--gravity-link);
}
.c-price-split > .alignwide a:focus-visible,
.c-price-split > .alignwide .wp-element-button:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus);
}

/* The bar: the figure and basis beside the button on a phone. */
.c-price-split > .alignwide > :first-child {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--wp--preset--spacing--30);
  padding: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40);
  background-color: var(--wp--preset--color--gravity-surface);
  border-block-end: 1px solid var(--wp--preset--color--gravity-border);
  border-radius: var(--wp--custom--rounded--lg) var(--wp--custom--rounded--lg) 0 0;
  z-index: 5;
}
.c-price-split > .alignwide > :first-child > :first-child > * {
  margin: 0;
}
/* The figure: set large, never a heading (validation item 21). */
.c-price-split > .alignwide > :first-child > :first-child > :first-child {
  font-family: var(--wp--preset--font-family--outfit);
  font-weight: 600;
  line-height: 1;
  font-size: var(--wp--preset--font-size--text-6);
}
.c-price-split > .alignwide > :first-child > :first-child > :last-child {
  color: var(--wp--preset--color--gravity-text-muted);
}

.c-price-split > .alignwide > p {
  padding: var(--wp--preset--spacing--40) var(--wp--preset--spacing--40) 0;
}

/* What's included. */
.c-price-split > .alignwide > :last-child {
  padding: var(--wp--preset--spacing--40);
}
.c-price-split > .alignwide > :last-child > h3 {
  margin: 0 0 var(--wp--preset--spacing--20);
}
.c-price-split > .alignwide > :last-child ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.c-price-split > .alignwide > :last-child li {
  position: relative;
  padding-block: 0.55em;
  padding-inline-start: 1.75em;
  border-block-start: 1px solid var(--wp--preset--color--gravity-border);
}
.c-price-split > .alignwide > :last-child li:first-child {
  border-block-start: 0;
}
/* The tick is decorative and drawn here, so it carries no text alternative. */
.c-price-split > .alignwide > :last-child li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0.2em;
  top: 0.95em;
  width: 0.7em;
  height: 0.38em;
  border-inline-start: 2px solid var(--wp--preset--color--brand-11);
  border-block-end: 2px solid var(--wp--preset--color--brand-11);
  transform: rotate(-45deg);
}

/* Wide: the price column beside the list. 782px is core's own wide break. */
@media (min-width: 782px) {
  .c-price-split > .alignwide {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr);
    grid-template-rows: auto 1fr;
  }
  .c-price-split > .alignwide > :first-child {
    grid-column: 1;
    grid-row: 1;
    align-self: start;
    flex-direction: column;
    align-items: flex-start;
    padding: var(--wp--preset--spacing--40) var(--wp--preset--spacing--40) 0;
    border-block-end: 0;
    border-radius: var(--wp--custom--rounded--lg) 0 0 0;
  }
  .c-price-split > .alignwide > :first-child > :first-child > :first-child {
    font-size: var(--wp--preset--font-size--display);
  }
  .c-price-split > .alignwide > p {
    grid-column: 1;
    grid-row: 2;
    align-self: start;
    padding-block-end: var(--wp--preset--spacing--40);
  }
  .c-price-split > .alignwide > :last-child {
    grid-column: 2;
    grid-row: 1 / span 2;
    border-inline-start: 1px solid var(--wp--preset--color--gravity-border);
  }
}

/* While scrolling: Keep price in view, the default, which writes no class.
 * Phones only (below 782px) and tall screens only (600px and up). */
@media (max-width: 781px) and (min-height: 600px) {
  .c-price-split:not(.c-price-split--scroll) > .alignwide > :first-child {
    position: sticky;
    top: calc(var(--c-hold-admin, 0px) + var(--c-hold-top, 0px));
    transition: top 180ms ease-out;
  }
}
@media (prefers-reduced-motion: reduce) {
  .c-price-split > .alignwide > :first-child {
    transition: none;
  }
}

/* ── Form split, Form stack, and Hero with a form ───────────────────────
 *
 * HERO WITH A FORM JOINS THE FORM SPLIT RULES AT 0.3.5 (Copy-to-Site
 * Decision 131, 2026-10-25; the build is GR9-T56). `gravitystack/hero-split-form`
 * is built on Form split's skeleton, the same `alignwide` row of a words column
 * and a form column, so the panel is found by the same position and every rule
 * below is written a second time with `.c-hero-split-form` in place of
 * `.c-form-split`. Nothing reaches a page until the pattern ships.
 *
 * Skin for `gravitystack/form-split` and `gravitystack/form-stack`: a section
 * that holds a SureForms form on a panel, with its copy beside the form
 * (split) or above it (stack). Ruled in GravityBrain 2026-10-21, Copy-to-Site
 * Decision 126; the build is GR9-T20. The reasoning is in the decision and is
 * not restated here. Every rule is keyed on a pattern's class, so nothing here
 * reaches a page until the patterns ship; this merges first so the patterns'
 * pull request previews show the real panel.
 *
 * WHICH GROUP IS THE PANEL. In Form split it is the last column's group: the
 * section's `alignwide` group holds the copy column and then the form column,
 * and the form column's one group is the panel. In Form stack it is the
 * section's last child group. Copy is never boxed; only the form is.
 *
 * THE PANEL IS A LIGHT GROUND ON EVERY BAND, the same reasoning as the price
 * panel (see Price split): Band dark turns every text, heading, link, and focus
 * colour inside it white, so the panel re-asserts its own text, heading, link,
 * and link-focus colours on every ground. Without that, the form's labels and
 * the privacy line sit white on white on Band dark.
 *
 * THE SELECTORS ARE WRITTEN AT (0,4,0) AND ABOVE, because `foundation.css`
 * carries `.wp-block-group.is-layout-flow.wp-block-group-is-layout-flow`, a
 * rule from Spectra One's stylesheet, which resets a flow group's side padding
 * at (0,3,0); at anything lighter the panel's padding
 * loses and the form sits against its own edge (same finding as the Guarantee
 * split document and Step list, 4.52.1). Form stack's panel reads (0,4,0) and
 * Form split's, one group deeper, (0,5,0).
 *
 * THE PRIVACY LINE IS THE PANEL'S LAST PARAGRAPH, set muted. It sits under the
 * form, after the submit button, so `:last-child` finds it.
 *
 * SUREFORMS'S OWN FIELD AND BUTTON STYLING IS DELIBERATELY LEFT ALONE FOR NOW
 * (Copy-to-Site Decision 126, point 7). These rules paint the panel only; the
 * inputs, labels, and button inside the form are SureForms's defaults until
 * that point is taken up.
 */
.c-hero-split-form > .alignwide > .wp-block-group:last-child > .wp-block-group,
.c-form-split > .alignwide > .wp-block-group:last-child > .wp-block-group,
.c-form-stack.wp-block-group > .wp-block-group:last-child {
  background-color: var(--wp--preset--color--gravity-surface);
  border: 1px solid var(--wp--preset--color--gravity-border);
  border-radius: var(--wp--custom--rounded--lg);
  padding: var(--wp--preset--spacing--50);
  color: var(--wp--preset--color--gravity-text);
}
.c-hero-split-form > .alignwide > .wp-block-group:last-child > .wp-block-group :where(h1, h2, h3, h4, h5, h6),
.c-form-split > .alignwide > .wp-block-group:last-child > .wp-block-group :where(h1, h2, h3, h4, h5, h6),
.c-form-stack.wp-block-group > .wp-block-group:last-child :where(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--preset--color--gravity-text);
}
.c-hero-split-form > .alignwide > .wp-block-group:last-child > .wp-block-group a:not(.wp-element-button),
.c-form-split > .alignwide > .wp-block-group:last-child > .wp-block-group a:not(.wp-element-button),
.c-form-stack.wp-block-group > .wp-block-group:last-child a:not(.wp-element-button) {
  color: var(--wp--preset--color--gravity-link);
}
.c-hero-split-form > .alignwide > .wp-block-group:last-child > .wp-block-group a:focus-visible,
.c-form-split > .alignwide > .wp-block-group:last-child > .wp-block-group a:focus-visible,
.c-form-stack.wp-block-group > .wp-block-group:last-child a:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus);
}
.c-hero-split-form > .alignwide > .wp-block-group:last-child > .wp-block-group > p:last-child,
.c-form-split > .alignwide > .wp-block-group:last-child > .wp-block-group > p:last-child,
.c-form-stack.wp-block-group > .wp-block-group:last-child > p:last-child {
  color: var(--wp--preset--color--gravity-text-muted);
}
@media (max-width: 767px) {
  .c-hero-split-form > .alignwide > .wp-block-group:last-child > .wp-block-group,
  .c-form-split > .alignwide > .wp-block-group:last-child > .wp-block-group,
  .c-form-stack.wp-block-group > .wp-block-group:last-child {
    padding: var(--wp--preset--spacing--40);
  }
}

/* ── Hero with a form: the person who replies ───────────────────────────
 *
 * Added at 0.4.3, for the pattern 0.3.5 skinned (`gravitystack/hero-split-form`,
 * Copy-to-Site Decision 131, GR9-T56). 0.3.5 skinned the panel and missed the
 * optional person group, which the catalogue entry shows as a 64px circle beside
 * the name and role. The pattern ships the group as a flow group of a
 * `core/image` and a group of two paragraphs, so with no rule the placeholder
 * drew at the column's full width (400px at 1440, 342px at 390) and pushed the
 * form's first field to 1,151px on a phone, outside the first screen. Measured
 * in the local render harness 2026-10-25; with these rules the first field's
 * bottom edge is inside both first screens, at 1440 by 790 and 390 by 844.
 *
 * THE GROUP IS FOUND BY ITS IMAGE, NOT BY POSITION: the one group in the words
 * column that has a `wp-block-image` as a direct child. The group is optional
 * and goes as a unit, so with no portrait none of these rules match.
 *
 * THE SIZE AND THE SHAPE SIT ON THE FIGURE, NOT THE IMAGE, for the reason in
 * Hero flush split: Spectra Blocks Pro re-emits any geometry a `core/image`
 * declares. The image fills the figure and takes the same circle so the crop
 * holds whichever element the placeholder or a real photograph lands on. Name
 * and role carry no colour here, so a band's colours reach them.
 */
.c-hero-split-form > .alignwide > .wp-block-group:first-child .wp-block-group:has(> .wp-block-image) {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--wp--preset--spacing--30);
}
.c-hero-split-form > .alignwide > .wp-block-group:first-child .wp-block-group:has(> .wp-block-image) > .wp-block-image {
  flex: 0 0 auto;
  inline-size: 4rem;
  aspect-ratio: 1;
  margin: 0;
  overflow: hidden;
  border-radius: 50%;
}
.c-hero-split-form > .alignwide > .wp-block-group:first-child .wp-block-group:has(> .wp-block-image) > .wp-block-image img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  border-radius: 50%;
}
.c-hero-split-form > .alignwide > .wp-block-group:first-child .wp-block-group:has(> .wp-block-image) > .wp-block-group > p {
  margin-block: 0;
}
.c-hero-split-form > .alignwide > .wp-block-group:first-child .wp-block-group:has(> .wp-block-image) > .wp-block-group > p:first-child {
  font-weight: var(--wp--custom--weight--strong);
}
.c-hero-split-form > .alignwide > .wp-block-group:first-child .wp-block-group:has(> .wp-block-image) > .wp-block-group > p:last-child {
  font-size: var(--wp--preset--font-size--text-1);
}

/* ── Location split ──────────────────────────────────────────────────────
 *
 * Skin for `gravitystack/location-split`, "Address, hours, and map": the
 * `location` section of a contact page, the words at the left (address, phone,
 * email, hours, access, directions) and a photograph of the entrance above a
 * map at the right. Ruled in GravityBrain 2026-10-25, Copy-to-Site Decision 131;
 * the build is GR9-T57. The reasoning is in the decision and is not restated
 * here. Every rule is keyed on the pattern's class, so nothing here reaches a
 * page until the pattern ships; it merges first so the pattern's pull request
 * preview shows the real frames.
 *
 * THE TREE THESE RULES READ is Form split's: the root, then the section's one
 * `alignwide` row holding the words column and then the media column. Only the
 * root carries a class (validation item 20), so every rule is structural. The
 * media column holds an optional photograph and then the map.
 *
 * BOTH FRAMES ARE 16:9 AND SHARE ONE CORNER, the theme's large radius. The
 * photograph is a `core/image` carrying no size value, so Spectra Blocks Pro
 * draws no geometry rule over it (see Hero flush split for the reading), and
 * the shape is set here on the figure and the image. The map is the embed code
 * from the practice's Google Business Profile in a core Custom HTML block,
 * which renders the iframe with no wrapper, so the iframe is the frame. The
 * embed code ships fixed `width` and `height` attributes, 600 by 450 from
 * Google; presentation attributes lose to any stylesheet rule, so `width: 100%`
 * with `aspect-ratio` replaces them and the code is never edited for size.
 *
 * LINKS IN THE WORDS COLUMN JOIN THE 44px FLOOR through the shared rule beside
 * Hero split's, selected by the column rather than by position, because the
 * phone, the email, and the directions line are each optional or movable.
 */
.c-location-split > .alignwide > .wp-block-group:last-child .wp-block-image {
  margin-block: 0;
  aspect-ratio: 16 / 9;
}
.c-location-split > .alignwide > .wp-block-group:last-child .wp-block-image img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--wp--custom--rounded--lg);
}
.c-location-split > .alignwide > .wp-block-group:last-child iframe {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  border: 0;
  border-radius: var(--wp--custom--rounded--lg);
}

/* ── Guarantee split ─────────────────────────────────────────────────────
 *
 * Skin for `gravitystack/guarantee-split`, "Guarantee with both sides of the
 * deal": one framed document holding the whole bargain, the reader's side at left
 * and the business's side on a green side at right, an optional seal on the top-right
 * edge, and an optional signature across the foot. Ruled in GravityBrain
 * 2026-10-14, Copy-to-Site Decision 101, which carries the reasoning;
 * none of it is restated here. Every rule is keyed on the pattern's class, so
 * nothing here reaches a page until the pattern ships; it merges first so the
 * pattern's pull request preview shows the real layout.
 *
 * THE TREE THESE RULES READ. Only the outer group carries a class (validation
 * item 20), so every rule is structural:
 *   root > header group (eyebrow paragraph, optional; H2)
 *   root > the document, the one `alignwide` group, holding in this order:
 *     1. the reader's side: a group of paragraphs and one list — IF label, qualification
 *        (optional), lead-in, then the commitments as a `core/list`;
 *     2. the business's side: a group holding, in order, the seal group (optional), THEN
 *        label paragraph, the promise group (numeral paragraph, optional; the
 *        promise paragraph), the supporting paragraph, the remedy group, and
 *        the close paragraph, always last;
 *     3. the signature group (optional): an image, then a group of the signer's
 *        name and role paragraphs.
 *   seal group > image, then the figure group (optional: figure, unit)
 *   remedy group > label paragraph, intro paragraph (optional), the remedies
 *     group, which holds one group per remedy (title paragraph, detail
 *     paragraph).
 * The seal sits inside the business's side so the two sides are always children
 * 1 and 2 and the signature always 3, whatever is left out. Change the tree,
 * change this.
 *
 * THE DOCUMENT IS A LIGHT GROUND ON EVERY BAND, as a card is: it emits its own
 * text, heading, link, and link focus colours on every ground, so a dark band
 * cannot turn them white. Both of its grounds take the same set: the green
 * panel is light too. Link and link focus landed at 4.57.1, after an injected
 * link measured 1.00:1 on the document and 1.11:1 on the panel on Band dark.
 * It holds no button, so it emits no button pair.
 *
 * THE PROMISE IS SIZED FROM WHETHER THE NUMERAL IS THERE. With a numeral the
 * promise sits beside it at body-display size; alone it stands at display size
 * in the brand colour. Both are paragraphs, never headings (validation item 21).
 *
 * THE SEAL IS AN IMAGE BLOCK, placed from the media library by the site's builder, and
 * decorative. Its figure and unit are live text laid over the blank file's
 * centre, turned 6 degrees with the artwork, sized from the seal so the unit
 * never drops below 13px. On the dark band it takes a white keyline, because
 * its charcoal disc measures 1.0:1 against the band.
 *
 * HEIGHT (2026-10-14, after the first deploy). So the whole guarantee fits one
 * laptop screen, three changes landed together: the header aligns to the
 * document's edge rather than the content column, so from `bp-1` the seal shares
 * the heading's row instead of needing its own space above the document (this
 * pattern's exception to the fleet's centred section header, folded into the
 * general rule at 6.1.0); the signature sits
 * under the reader's side, filling the space below the commitments, rather than
 * across the foot; and the business's side is tighter, the lone promise one size
 * down. 985px to 716px from eyebrow to document bottom at 1440px.
 *
 * BREAKPOINTS. The two sides sit side by side from `bp-1`. The remedies sit side
 * by side, and the promise keeps room beside the seal, from `bp-2`. The design
 * ruled "about 1024px" for the second step; the CSS standard refuses a 1024px
 * breakpoint, so it lands on `bp-2` and the remedies stack a little wider.
 */
.c-guarantee-split {
  --c-guarantee-seal: 8.5rem;         /* smallest size keeping every ring word at 13px */
  --c-guarantee-seal-rise: 3.25rem;   /* how far the seal stands above the edge on a phone */
  --c-guarantee-seal-room: 6.5rem;    /* room the promise keeps beside the seal */
  --c-guarantee-keyline: 3px;
  --c-guarantee-sign: 6rem;
  --c-guarantee-numeral: 5.5rem;      /* the count numeral, from the approved renders */
}

/* The header: on the document's edge, which the general *Section heading
 * area* rule now provides (Copy-to-Site Decision 120 folded this pattern's
 * exception into it, 6.1.0), and from `bp-1` short enough to leave the seal
 * room on its row. Below `bp-1` the seal stands on the document's edge under
 * the header, not beside it, so the header takes the full width: reserving the
 * seal's room there left the heading 142px of a 342px card at 390px (4.57.1). */
@media (min-width: 768px) { /* bp-1 */
  .c-guarantee-split > :first-child > * {
    max-inline-size: min(24ch, calc(100% - var(--c-guarantee-seal) - 2 * var(--wp--preset--spacing--50)));
  }
}
.c-guarantee-split > :first-child p:first-child:not(:last-child) {
  color: var(--wp--custom--color--brand-11);
}
.c-guarantee-split.is-style-band-dark > :first-child p:first-child:not(:last-child) {
  color: var(--wp--custom--color--brand-6);
}

/* The document. Its padding rules name `div.wp-block-group` because `foundation.css`
 * zeroes a flow group's side padding at (0,3,0); at (0,3,1) these win whatever
 * the load order (same finding as Step list, 4.52.1). */
.c-guarantee-split > .alignwide {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--wp--preset--spacing--40);
  margin-block-start: var(--wp--preset--spacing--60);
  background-color: var(--wp--preset--color--gravity-surface);
  border: 1px solid var(--wp--preset--color--brand-11);
  border-radius: var(--wp--custom--rounded--lg);
}
.c-guarantee-split > div.wp-block-group.alignwide {
  padding: var(--wp--preset--spacing--40);
}
.c-guarantee-split > .alignwide,
.c-guarantee-split > .alignwide :where(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--preset--color--gravity-text);
}
.c-guarantee-split > .alignwide a:not(.wp-element-button) {
  color: var(--wp--preset--color--gravity-link);
}
.c-guarantee-split > .alignwide a:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus);
}
.c-guarantee-split > .alignwide > *,
.c-guarantee-split > .alignwide > * > *,
.c-guarantee-split > .alignwide > * > * > *,
.c-guarantee-split > .alignwide > * > * > * > * {
  margin-block: 0;
}
/* With a seal: room above the document for it to stand on the edge. */
.c-guarantee-split > .alignwide:has(> :nth-child(2) > :first-child > .wp-block-image) {
  margin-block-start: calc(var(--c-guarantee-seal-rise) + var(--wp--preset--spacing--40));
  padding-block-start: calc(var(--c-guarantee-seal) - var(--c-guarantee-seal-rise) + var(--wp--preset--spacing--30));
}

/* The labels: IF, THEN, IF WE MISS. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(1) > p:first-child,
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > p:first-of-type,
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > :nth-last-child(2) > p:first-child {
  font-size: var(--wp--preset--font-size--text-1);
  font-weight: var(--wp--custom--weight--strong);
  letter-spacing: var(--wp--custom--tracking--wide);
  text-transform: uppercase;
  color: var(--wp--custom--color--brand-11);
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > p:first-of-type,
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > :nth-last-child(2) > p:first-child {
  color: var(--wp--custom--color--brand-12);
}

/* The reader's side. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(1) {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--30);
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(1) > p:first-child + * {
  margin-block-start: calc(var(--wp--preset--spacing--20) - var(--wp--preset--spacing--30));
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(1) > ul {
  list-style: none;
  padding: 0;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(1) li {
  position: relative;
  padding-inline-start: 1.5em;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(1) li + li {
  margin-block-start: var(--wp--preset--spacing--20);
}
/* The bullet is decorative and drawn here, so it carries no text alternative. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(1) li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0.15em;
  inset-block-start: 0.62em;
  inline-size: 0.5em;
  block-size: 0.5em;
  border-radius: var(--wp--custom--rounded--full);
  background-color: var(--wp--custom--color--brand-11);
}

/* The business's side: the green side, unbordered on white. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--20);
  padding: var(--wp--preset--spacing--40);
  background-color: var(--wp--custom--color--brand-3);
  border-radius: var(--wp--custom--rounded--sm);
}


/* The promise, with the numeral. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > p:first-of-type + .wp-block-group {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  column-gap: var(--wp--preset--spacing--40);
  row-gap: var(--wp--preset--spacing--20);
  font-family: var(--wp--preset--font-family--outfit);
  font-weight: 600;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > p:first-of-type + .wp-block-group > p:first-child:not(:only-child) {
  font-size: var(--c-guarantee-numeral);
  line-height: 0.82;
  letter-spacing: -0.03em;
  color: var(--wp--custom--color--brand-11);
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > p:first-of-type + .wp-block-group > p:last-child:not(:only-child) {
  flex: 1 1 11rem;
  font-size: var(--wp--preset--font-size--text-4);
  line-height: 1.15;
}
/* The promise alone: a proof guarantee, at display size. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > p:first-of-type + .wp-block-group > p:only-child {
  max-inline-size: 18ch;
  font-size: var(--wp--preset--font-size--text-6);
  line-height: 1.1;
  letter-spacing: -0.015em;
  color: var(--wp--custom--color--brand-11);
}

/* The supporting line. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > p:first-of-type + .wp-block-group + p {
  color: var(--wp--custom--color--brand-12);
}

/* The remedy. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > :nth-last-child(2) {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--30);
  margin-block-start: var(--wp--preset--spacing--20);
  padding-block-start: var(--wp--preset--spacing--30);
  border-block-start: 1px solid var(--wp--custom--color--brand-5);
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > :nth-last-child(2) > p:first-child {
  margin-block-end: calc(var(--wp--preset--spacing--20) - var(--wp--preset--spacing--30));
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > :nth-last-child(2) > .wp-block-group {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--wp--preset--spacing--30);
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > :nth-last-child(2) > .wp-block-group > * {
  position: relative;
  margin-block: 0;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > :nth-last-child(2) > .wp-block-group > * > p {
  margin-block: 0;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > :nth-last-child(2) > .wp-block-group > * > p:first-child {
  margin-block-end: var(--wp--preset--spacing--20);
  font-family: var(--wp--preset--font-family--outfit);
  font-weight: 600;
  font-size: var(--wp--preset--font-size--text-4);
  line-height: 1.2;
}
/* "or" between two remedies: drawn here, and hidden from screen readers,
 * because the remedy label already says the reader chooses. Stacked, it is a rule
 * with the word in its middle. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > :nth-last-child(2) > .wp-block-group > :nth-child(2)::before {
  content: "or" / "";
  display: block;
  margin-block-end: var(--wp--preset--spacing--30);
  text-align: center;
  font-size: var(--wp--preset--font-size--text-1);
  font-weight: var(--wp--custom--weight--strong);
  letter-spacing: var(--wp--custom--tracking--wide);
  text-transform: uppercase;
  color: var(--wp--custom--color--brand-12);
  background:
    linear-gradient(var(--wp--custom--color--brand-5), var(--wp--custom--color--brand-5)) left center / calc(50% - 1.5em) 1px no-repeat,
    linear-gradient(var(--wp--custom--color--brand-5), var(--wp--custom--color--brand-5)) right center / calc(50% - 1.5em) 1px no-repeat;
}

/* The close: always last, nothing after it. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > p:last-child {
  font-weight: var(--wp--custom--weight--strong);
}

/* The seal: on the document's top-right edge. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child:has(> .wp-block-image) {
  position: absolute;
  z-index: 2;
  inset-block-start: calc(-1 * var(--c-guarantee-seal-rise));
  inset-inline-end: var(--wp--preset--spacing--30);
  inline-size: var(--c-guarantee-seal);
  block-size: var(--c-guarantee-seal);
  margin: 0;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child > .wp-block-image {
  margin: 0;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child > .wp-block-image img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  border-radius: var(--wp--custom--rounded--full);
}
.c-guarantee-split.is-style-band-dark > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child > .wp-block-image img {
  box-shadow: 0 0 0 var(--c-guarantee-keyline) var(--wp--preset--color--gravity-surface);
}
/* The live figure over the blank seal's centre, turned with the artwork. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child > .wp-block-group {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin: 0;
  transform: rotate(-6deg);
  text-align: center;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child > .wp-block-group > p {
  margin: 0;
  font-family: var(--wp--preset--font-family--outfit);
  font-weight: 600;
  line-height: 1;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child > .wp-block-group > p:first-child {
  font-size: calc(var(--c-guarantee-seal) * 0.25);
  letter-spacing: -0.02em;
  color: var(--wp--preset--color--gravity-text);
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child > .wp-block-group > p:last-child:not(:only-child) {
  margin-block-start: calc(var(--c-guarantee-seal) * 0.03);
  font-family: var(--wp--preset--font-family--figtree);
  font-size: calc(var(--c-guarantee-seal) * 0.0975);
  letter-spacing: var(--wp--custom--tracking--wide);
  text-transform: uppercase;
  color: var(--wp--custom--color--brand-11);
}

/* Forced colours (Windows high contrast). The seal image keeps its own colours,
 * so the figure laid over it keeps them too, in charcoal for the most contrast:
 * forced, it turned white on black backplates over the white centre. The centre
 * is repainted behind the figure, inside the seal's inner ring so it hides nothing
 * of a loaded image, so the figure
 * stays readable if the image fails to load. */
@media (forced-colors: active) {
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child > .wp-block-group {
    forced-color-adjust: none;
    background: radial-gradient(circle closest-side, var(--wp--preset--color--gravity-surface) 52%, transparent 52%);
  }
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child > .wp-block-group > p,
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child > .wp-block-group > p:last-child:not(:only-child) {
    color: var(--wp--preset--color--gravity-text);
  }
}

/* The signature, across the document's foot. */
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(3) {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--wp--preset--spacing--20);
  padding-block-start: var(--wp--preset--spacing--30);
  border-block-start: 1px solid var(--wp--preset--color--gravity-border);
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(3) > .wp-block-image {
  margin: 0;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(3) > .wp-block-image img {
  display: block;
  inline-size: var(--c-guarantee-sign);
  block-size: auto;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(3) > .wp-block-group > p {
  margin: 0;
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(3) > .wp-block-group > p:first-child {
  font-weight: var(--wp--custom--weight--strong);
}
.c-guarantee-split > .alignwide > .wp-block-group:nth-child(3) > .wp-block-group > p:last-child:not(:only-child) {
  font-size: var(--wp--preset--font-size--text-2);
  color: var(--wp--preset--color--gravity-text-muted);
}

/* bp-1 and up: the two sides side by side; the seal centred on the edge. */
@media (min-width: 768px) { /* bp-1 */
  .c-guarantee-split {
    --c-guarantee-seal: 9.25rem;
    --c-guarantee-seal-rise: calc(var(--c-guarantee-seal) / 2);
    --c-guarantee-numeral: 7rem;
  }
  .c-guarantee-split > .alignwide {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
    gap: var(--wp--preset--spacing--50);
  }
  .c-guarantee-split > div.wp-block-group.alignwide {
    padding: var(--wp--preset--spacing--50);
  }
  .c-guarantee-split > .alignwide:has(> :nth-child(2) > :first-child > .wp-block-image) {
    margin-block-start: var(--wp--preset--spacing--40);
    padding-block-start: var(--wp--preset--spacing--50);
  }
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(1) {
    padding-block-start: var(--wp--preset--spacing--30);
  }
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > .wp-block-group:first-child:has(> .wp-block-image) {
    inset-inline-end: var(--wp--preset--spacing--50);
  }
  /* Below bp-2 the business's side keeps room above its content for the seal, not beside. */
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2):has(> .wp-block-group:first-child > .wp-block-image) {
    padding-block-start: calc(var(--c-guarantee-seal-rise) - var(--wp--preset--spacing--50) + var(--wp--preset--spacing--40));
  }
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > p:first-of-type + .wp-block-group > p:last-child:not(:only-child) {
    font-size: var(--wp--preset--font-size--text-5);
  }
  /* The signature under the reader's side: the business's side spans both rows. */
  .c-guarantee-split > .alignwide:has(> :nth-child(3)) > .wp-block-group:nth-child(2) {
    grid-row: 1 / span 2;
  }
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(1) {
    align-self: start;
  }
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(3) {
    grid-column: 1;
    grid-row: 2;
    align-self: end;
  }
}

/* bp-2 and up: the remedies side by side, and the promise beside the seal. */
@media (min-width: 1200px) { /* bp-2 */
  .c-guarantee-split > .alignwide {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.45fr);
  }
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) {
    padding: var(--wp--preset--spacing--50) var(--wp--preset--spacing--50) var(--wp--preset--spacing--40);
  }
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2):has(> .wp-block-group:first-child > .wp-block-image) {
    padding-block-start: var(--wp--preset--spacing--50);
  }
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2):has(> .wp-block-group:first-child > .wp-block-image) > p:first-of-type,
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2):has(> .wp-block-group:first-child > .wp-block-image) > p:first-of-type + .wp-block-group {
    padding-inline-end: var(--c-guarantee-seal-room);
  }
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > p:first-of-type + .wp-block-group > p:only-child {
    max-inline-size: 24ch;
  }
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > :nth-last-child(2) > .wp-block-group:has(> :nth-child(2)) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--wp--preset--spacing--70);
  }
  /* Side by side, "or" is a vertical rule in the gap with the word in its middle. */
  .c-guarantee-split > .alignwide > .wp-block-group:nth-child(2) > :nth-last-child(2) > .wp-block-group > :nth-child(2)::before {
    position: absolute;
    inset-block: 0;
    inset-inline-start: calc(-1 * var(--wp--preset--spacing--70) / 2);
    inline-size: 2em;
    margin: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    transform: translateX(-50%);
    background:
      linear-gradient(var(--wp--custom--color--brand-5), var(--wp--custom--color--brand-5)) center top / 1px calc(50% - 1em) no-repeat,
      linear-gradient(var(--wp--custom--color--brand-5), var(--wp--custom--color--brand-5)) center bottom / 1px calc(50% - 1em) no-repeat;
  }
}

/* ── Stat grid ───────────────────────────────────────────────────────────
 * The theme half of `gravitystack/stat-grid`, "Numbers side by side", Copy-to-Site
 * Decision 104. Two to four stats (five flagged), each an optional icon, a
 * figure, a label, and an optional detail, all centred. The figure is a
 * paragraph set large here, never a heading (validation item 21), so every
 * figure in a set is one size and a client theme tunes it in one place.
 *
 * COLUMNS FOLLOW THE COUNT, AT THE BREAKPOINTS. The pattern's own grid
 * collapses by auto-fill, which is its walk-away fallback; this file sets the
 * count from the stats present, so four never break three over one on a
 * tablet. The recorded exception in *Grid collapse*, pattern standards v1.89.
 *   below bp-1   Open two across; Separate and Grouped one per row, because a
 *                padded track two across cannot hold "1,200+" at 44px
 *   bp-1         two stats 2, three 3, four 2 by 2, five 3 over 2
 *   bp-2         one row, two to five
 * Two stats sit at 48rem, so they do not drift apart across the wide measure.
 * `:has()` counts the stats; the shape is the grid's direct children.
 *
 * FIGURE SIZES. 72px on a laptop, with a 44px floor (`text-7`): the Display
 * preset's own 35px floor barely outranked the label on a phone. At bp-2 the
 * figure steps down where the track narrows: 56px at four in Separate and
 * Grouped, 56px at five in Open and 48px at five in a card, measured so the
 * longest budgeted figure ("1,200+") fits with room in Outfit 600.
 *
 * COLOUR. The figure and the icon are Brand Text (`brand-11`): 4.81:1 on
 * white, 4.56:1 on Band accent, 4.14:1 on the card fill, large text and
 * non-text only, never body copy. On Band dark, Open takes Gravity Brand,
 * 5.34:1; Separate and Grouped stay on Brand Text, because the card fill is
 * the ground there whatever band sits behind it. The detail is
 * `gravity-text-muted`, 5.96:1 on white and 5.13:1 on the card fill; on Band
 * dark in Open it takes the band's own text colour, since muted fails there.
 *
 * GROUPED adds an inset and hairlines to the card style's one surface: a
 * `gravity-border` line between the stats in a row, and between stats where
 * they stack.
 */
.c-stat-grid .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.c-stat-grid .wp-block-group-is-layout-grid:has(> :nth-child(2):last-child) {
  inline-size: 100%;
  max-inline-size: 48rem;
  margin-inline: auto;
}
@media (max-width: 767.98px) {
  .c-stat-grid--separate .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid,
  .c-stat-grid--grouped .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}
@media (min-width: 768px) { /* bp-1 */
  .c-stat-grid .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid:has(> :nth-child(3):last-child),
  .c-stat-grid .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid:has(> :nth-child(5):last-child) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media (min-width: 1200px) { /* bp-2 */
  .c-stat-grid .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid:has(> :nth-child(4):last-child) {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
  .c-stat-grid .wp-block-group-is-layout-grid.wp-block-group-is-layout-grid:has(> :nth-child(5):last-child) {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

/* The stat: centred, its own spacing, no flow margins. */
.c-stat-grid .wp-block-group-is-layout-grid > .wp-block-group {
  text-align: center;
}
.c-stat-grid .wp-block-group-is-layout-grid > .wp-block-group > * {
  margin-block: 0;
}
.c-stat-grid .wp-block-group-is-layout-grid > .wp-block-group > .wp-block-icon {
  color: var(--wp--custom--color--brand-11);
  margin-block-end: var(--wp--preset--spacing--30);
}

/* The figure. */
.c-stat-grid .wp-block-group-is-layout-grid > .wp-block-group > p:first-of-type {
  font-family: var(--wp--preset--font-family--outfit);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  font-size: max(var(--wp--preset--font-size--text-7), var(--wp--preset--font-size--display));
  color: var(--wp--custom--color--brand-11);
  margin-block-end: var(--wp--preset--spacing--20);
}
@media (min-width: 1200px) { /* bp-2 */
  .c-stat-grid--separate .wp-block-group-is-layout-grid:has(> :nth-child(4)) > .wp-block-group > p:first-of-type,
  .c-stat-grid--grouped .wp-block-group-is-layout-grid:has(> :nth-child(4)) > .wp-block-group > p:first-of-type,
  .c-stat-grid .wp-block-group-is-layout-grid:has(> :nth-child(5)) > .wp-block-group > p:first-of-type {
    font-size: 3.5rem;
  }
  .c-stat-grid--separate .wp-block-group-is-layout-grid:has(> :nth-child(5)) > .wp-block-group > p:first-of-type,
  .c-stat-grid--grouped .wp-block-group-is-layout-grid:has(> :nth-child(5)) > .wp-block-group > p:first-of-type {
    font-size: 3rem;
  }
}

/* The label and the detail. */
.c-stat-grid .wp-block-group-is-layout-grid > .wp-block-group > p:nth-of-type(2) {
  font-weight: var(--wp--custom--weight--strong);
  font-size: var(--wp--preset--font-size--text-3);
  line-height: 1.3;
}
.c-stat-grid .wp-block-group-is-layout-grid > .wp-block-group > p:nth-of-type(3) {
  font-size: var(--wp--preset--font-size--text-2);
  color: var(--wp--preset--color--gravity-text-muted);
}

/* Open on Band dark: the band is the ground. */
.c-stat-grid.is-style-band-dark:not(.c-stat-grid--separate):not(.c-stat-grid--grouped)
  .wp-block-group-is-layout-grid > .wp-block-group > :is(p:first-of-type, .wp-block-icon) {
  color: var(--wp--preset--color--gravity-brand);
}
.c-stat-grid.is-style-band-dark:not(.c-stat-grid--separate):not(.c-stat-grid--grouped)
  .wp-block-group-is-layout-grid > .wp-block-group > p:nth-of-type(3) {
  color: inherit;
}

/* Separate pads each card here, because the stat carries none in markup:
 * padding in markup would also narrow Open, where "1,200+" then overflows a
 * two-across phone track and a four-across laptop track (measured). */
.c-stat-grid--separate .wp-block-group-is-layout-grid > .wp-block-group {
  padding-block: var(--wp--preset--spacing--50);
  padding-inline: var(--wp--preset--spacing--40);
}
@media (max-width: 767.98px) {
  .c-stat-grid--separate .wp-block-group-is-layout-grid > .wp-block-group {
    padding-block: var(--wp--preset--spacing--40);
  }
}
.c-stat-grid--grouped .wp-block-group-is-layout-grid > .wp-block-group {
  padding-inline: var(--wp--preset--spacing--30);
}

/* Grouped: a deeper inset than point-grid's, and hairlines between stats. */
.c-stat-grid--grouped .wp-block-group-is-layout-grid {
  padding-block: var(--wp--preset--spacing--60);
  row-gap: var(--wp--preset--spacing--60);
}
.c-stat-grid--grouped .wp-block-group-is-layout-grid > .wp-block-group + .wp-block-group {
  border-block-start: 1px solid var(--wp--preset--color--gravity-border);
  padding-block-start: var(--wp--preset--spacing--60);
}
@media (min-width: 768px) { /* bp-1: lines between stats in a row, none between rows */
  .c-stat-grid--grouped .wp-block-group-is-layout-grid > .wp-block-group + .wp-block-group {
    border-block-start: 0;
    padding-block-start: 0;
  }
  .c-stat-grid--grouped .wp-block-group-is-layout-grid:has(> :nth-child(2):last-child) > :nth-child(2),
  .c-stat-grid--grouped .wp-block-group-is-layout-grid:has(> :nth-child(3):last-child) > :nth-child(n + 2),
  .c-stat-grid--grouped .wp-block-group-is-layout-grid:has(> :nth-child(4):last-child) > :nth-child(even),
  .c-stat-grid--grouped .wp-block-group-is-layout-grid:has(> :nth-child(5):last-child) > :not(:nth-child(3n + 1)) {
    border-inline-start: 1px solid var(--wp--preset--color--gravity-border);
  }
}
@media (min-width: 1200px) { /* bp-2: one row, a line before every stat but the first */
  .c-stat-grid--grouped .wp-block-group-is-layout-grid > .wp-block-group + .wp-block-group {
    border-inline-start: 1px solid var(--wp--preset--color--gravity-border);
  }
}

/* ── Point grid icon ───────────────────────────────────────────────────────────
 * The theme half of `gravitystack/point-grid-icon`, "Points with icons and short labels",
 * Copy-to-Site Decisions 102 and 112. Three to twelve items, each a core Icon
 * over a label of one to three words. Unlinked, they sit on the section with
 * no card. Linked, all or none per section, each is a linked card.
 *
 * THIS TREE IS A CONTRACT WITH THE PATTERN. The items are the direct children
 * of the pattern's one flex group (core flex layout, wrapping, centred). Each
 * item is a group holding a core Icon and then one paragraph, the label, whose
 * only child is the link when the section is linked. No class sits below the
 * root, per *The pattern class*, so every rule reaches the items by structure.
 *
 * COLUMNS ARE THE THEME'S, READ FROM A MODIFIER CLASS. Default 3 writes no
 * class; `c-point-grid-icon--columns-4` and `c-point-grid-icon--columns-6` set 4 and 6.
 * The width cap below needs the count, and a stylesheet cannot read core's
 * `columnCount`, so the axis writes a class, the fallback *Pattern Style
 * Options* names for a choice core cannot render from one attribute.
 *   below bp-1   Above centred 2 across; Beside 1 across, full width
 *   bp-1         3 stays 3, 4 stays 4, 6 steps to 3: six tracks at a tablet
 *                are about 100px, too narrow for a two-word label
 *   bp-2         3, 4, or 6
 *
 * THE WIDTH CAP (Decision 102, amended). Each item is at most 12.5rem wide and
 * the row centres, so items keep one compact shape at any count. Beside takes
 * 15rem, because there the label shares the item's width with the icon: at
 * 12.5rem every two-word label wrapped (measured 2026-10-16 at 1440px). The group is
 * capped at N items and N - 1 gaps, and each item takes 1/N of what is left
 * after the gaps, so it reaches 12.5rem only when the group is at its cap. The
 * row is flex rather than grid because a wrapping flex row centres a partial
 * last row by itself, which Decision 102 asks for under Above centred.
 * COUPLED TO THE PATTERN: the gap is `spacing--40` because the flex group's
 * blockGap is `spacing|40`. Change one, change the other.
 *
 * ICONS AND LABELS START AT THE TOP of each item. Items stretch to their row's
 * height, so when one label wraps to two lines the rest keep their icons and
 * labels level with it rather than dropping to centre.
 *
 * THE LINKED CARD is *Linked cards* under the standard's *Card styles*, with
 * values from *The linked card* in the constants file. Surface Nested with an
 * outline; the label in the card's text colour with a small arrow after it in
 * the link colour; on hover the outline takes the link colour and the label is
 * underlined; the focus ring sits inside the card's edge. The label's link is
 * stretched over the card by a ::before, so the whole card is the hit area and
 * the label stays the accessible name; the ::before also draws the focus ring,
 * which is why the link's own outline is off. The arrow is the link's ::after,
 * a no-break space carrying a masked glyph in the link colour, so it never
 * wraps away from the last word and announces nothing. Every colour is set on
 * every ground, never scoped to Band dark, per the tonal-set rule: the card's
 * fill is the ground whatever band sits behind it, and without these rules the
 * dark band's white link and hover would reach the label at 1.16:1.
 */
.c-point-grid-icon {
  --c-point-grid-icon-columns: 3;
  --c-point-grid-icon-gap: var(--wp--preset--spacing--40);
  --c-point-grid-icon-cap: 12.5rem;
}
.c-point-grid-icon--icon-beside {
  --c-point-grid-icon-cap: 15rem;
}
.c-point-grid-icon--columns-4 {
  --c-point-grid-icon-columns: 4;
}
.c-point-grid-icon--columns-6 {
  --c-point-grid-icon-columns: 6;
}
@media (max-width: 1199.98px) {
  .c-point-grid-icon--columns-6 {
    --c-point-grid-icon-columns: 3;
  }
}
@media (max-width: 767.98px) {
  .c-point-grid-icon {
    --c-point-grid-icon-columns: 2;
  }
  .c-point-grid-icon--icon-beside {
    --c-point-grid-icon-columns: 1;
  }
}
.c-point-grid-icon .wp-block-group-is-layout-flex.wp-block-group-is-layout-flex {
  flex-wrap: wrap;
  justify-content: center;
  align-items: stretch;
  gap: var(--c-point-grid-icon-gap);
  max-inline-size: calc(var(--c-point-grid-icon-columns) * var(--c-point-grid-icon-cap) + (var(--c-point-grid-icon-columns) - 1) * var(--c-point-grid-icon-gap));
  margin-inline: auto;
}
@media (max-width: 767.98px) {
  .c-point-grid-icon--icon-beside .wp-block-group-is-layout-flex.wp-block-group-is-layout-flex {
    max-inline-size: none;
  }
}
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group {
  flex: 0 0 calc((100% - (var(--c-point-grid-icon-columns) - 1) * var(--c-point-grid-icon-gap)) / var(--c-point-grid-icon-columns));
  min-inline-size: 0;
  text-align: center;
}
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > * {
  margin-block: 0;
}
/* Above centred, the default: the icon over the label, both centred by the
 * item's text-align, which reaches the icon's inline SVG as on `point-grid`. */
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > .wp-block-icon {
  font-size: var(--wp--preset--font-size--text-4);
  margin-block-end: var(--wp--preset--spacing--30);
}
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > p {
  line-height: 1.3;
}
/* Beside: the icon at the start, the label after it, the icon centred on the
 * label's FIRST line rather than on the whole label. Items start from the top,
 * and the label is padded down by half the difference between the icon's
 * height and one line of the label, so a label that wraps runs its second line
 * below and every icon and first line in the row stays level. Until 4.58.1 the
 * pair was centred on each other, which left a wrapped label's icon 5px low and
 * its first line 6px high against its neighbours (measured 2026-10-16).
 * COUPLED: `2 * text-3` is the icon's height, its `2em` at the Beside font-size
 * set below. Change one, change the other. */
.c-point-grid-icon--icon-beside .wp-block-group-is-layout-flex > .wp-block-group {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  align-content: flex-start;
  gap: var(--wp--preset--spacing--30);
  text-align: start;
}
.c-point-grid-icon--icon-beside .wp-block-group-is-layout-flex > .wp-block-group > .wp-block-icon {
  flex: none;
  font-size: var(--wp--preset--font-size--text-3);
  margin-block-end: 0;
}
.c-point-grid-icon--icon-beside .wp-block-group-is-layout-flex > .wp-block-group > p {
  flex: 1 1 0;
  min-inline-size: 0;
  padding-block-start: calc((2 * var(--wp--preset--font-size--text-3) - 1lh) / 2);
}
/* The Cards setting decides the surface; the link decides the behaviour
 * (Decision 102, amended 2026-10-16, a fourth time). Separate,
 * `c-point-grid-icon--separate`, paints every item as a card; Grouped,
 * `c-point-grid-icon--grouped`, paints the row as one surface; None writes no class
 * and paints nothing, the default, never pre-applied as on every card grid.
 * Values are the card contract's two treatments. Linked or not, an item has a
 * card only when Separate says so.
 *
 * THE COLOURS FOLLOW THE GROUND. Three custom properties carry the link's
 * colours to whatever the item sits on: the arrow and the hover underline,
 * the focus ring, and where the ring sits. On the section (None) the arrow is
 * the band's link colour and the ring sits just outside the item, on the band,
 * in the band's focus colour; on a card or the Grouped surface the arrow is
 * `gravity-link` and the ring is `gravity-focus`, per the tonal-set rule,
 * inside a Separate card's edge and just outside a Grouped item. Measured 2026-10-16: arrow 7.32:1 on white, 6.95:1 on Band
 * subtle, 12.16:1 (link inverse) on Band dark, 6.31:1 on a card. */
.c-point-grid-icon {
  --c-point-grid-icon-arrow: var(--wp--preset--color--gravity-link);
  --c-point-grid-icon-ring: var(--wp--preset--color--gravity-focus);
  --c-point-grid-icon-ring-offset: 2px;
}
.c-point-grid-icon.is-style-band-dark {
  --c-point-grid-icon-arrow: var(--wp--preset--color--gravity-link-inverse);
  --c-point-grid-icon-ring: var(--wp--preset--color--gravity-focus-inverse);
}
.c-point-grid-icon--separate .wp-block-group-is-layout-flex > .wp-block-group,
.c-point-grid-icon--grouped .wp-block-group-is-layout-flex.wp-block-group-is-layout-flex {
  --c-point-grid-icon-arrow: var(--wp--preset--color--gravity-link);
  --c-point-grid-icon-ring: var(--wp--preset--color--gravity-focus);
  color: var(--wp--preset--color--gravity-text);
}
/* The ring sits inside a Separate card's edge. Grouped items carry no padding,
 * so their ring stays just outside the item, on the Grouped surface. */
.c-point-grid-icon--separate .wp-block-group-is-layout-flex > .wp-block-group {
  --c-point-grid-icon-ring-offset: calc(-1 * var(--wp--preset--spacing--20));
  background-color: var(--wp--preset--color--gravity-surface-nested);
  border: 1px solid var(--wp--preset--color--gravity-border);
  border-radius: var(--wp--custom--rounded--md);
  padding-block: var(--wp--preset--spacing--40);
  padding-inline: var(--wp--preset--spacing--30);
}
.c-point-grid-icon--grouped .wp-block-group-is-layout-flex.wp-block-group-is-layout-flex {
  background-color: var(--wp--preset--color--gravity-surface-nested);
  border-radius: var(--wp--custom--rounded--md);
  padding: var(--wp--preset--spacing--40);
  max-inline-size: calc(var(--c-point-grid-icon-columns) * var(--c-point-grid-icon-cap) + (var(--c-point-grid-icon-columns) - 1) * var(--c-point-grid-icon-gap) + 2 * var(--wp--preset--spacing--40));
}
/* A card obliges a tonal set: any other link on a Separate card or the Grouped
 * surface takes the card's link and focus colours, never the band's. The
 * linked label keeps its own, more specific rules below. */
.c-point-grid-icon--separate .wp-block-group-is-layout-flex > .wp-block-group a,
.c-point-grid-icon--grouped .wp-block-group-is-layout-flex a,
.c-point-grid-icon--separate .wp-block-group-is-layout-flex > .wp-block-group a:hover,
.c-point-grid-icon--grouped .wp-block-group-is-layout-flex a:hover {
  color: var(--wp--preset--color--gravity-link);
}
.c-point-grid-icon--separate .wp-block-group-is-layout-flex > .wp-block-group a:focus-visible,
.c-point-grid-icon--grouped .wp-block-group-is-layout-flex a:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus);
}
/* The linked item, on whatever surface the setting gives it (Decision 112).
 * The label takes the ground's text colour and no underline at rest, with the
 * arrow after it; the whole item is the hit area; on hover the label is
 * underlined and, on a Separate card, the outline takes the link colour. */
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group:has(> p > a) {
  position: relative;
}
.c-point-grid-icon--separate .wp-block-group-is-layout-flex > .wp-block-group:has(> p > a:hover) {
  border-color: var(--wp--preset--color--gravity-link);
}
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > p > a,
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > p > a:hover {
  color: inherit;
}
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > p > a {
  text-decoration: none;
}
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > p > a:hover {
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-decoration-color: var(--c-point-grid-icon-arrow);
  text-underline-offset: 0.2em;
}
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > p > a::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--wp--custom--rounded--md);
}
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > p > a::after {
  content: "\00a0";
  content: "\00a0" / "";
  padding-inline-end: 0.95em;
  background-color: var(--c-point-grid-icon-arrow);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") right center / 0.85em no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") right center / 0.85em no-repeat;
}
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > p > a:focus-visible {
  outline: none;
}
.c-point-grid-icon .wp-block-group-is-layout-flex > .wp-block-group > p > a:focus-visible::before {
  outline: 2px solid var(--c-point-grid-icon-ring);
  outline-offset: var(--c-point-grid-icon-ring-offset);
}

/* ── Point stack split ─────────────────────────────────────────────────
 *
 * Skin for `gravitystack/point-stack-split`: three or more framed idea
 * cards in one column, each a split of text and a required image, that stack
 * as the reader scrolls on a screen large enough. Commissioned as Copy-to-Site
 * Decision 94 (2026-10-09); treatment and icon behaviour ruled in GravityBrain
 * 2026-10-13 (Scott) from renders of every combination, against the SureCart
 * reference. Every rule is keyed on the pattern's class, so nothing here
 * reaches a page until the pattern ships; it merges first so the pattern's
 * pull request preview shows the real layout.
 *
 * COUPLED TO THE PATTERN'S TREE, which carries no classes below the root:
 *   root > header group, cards group (`alignwide`)
 *   cards group > card (one group per idea, padding in markup)
 *   card > split (flex row, vertical below Spectra's tablet break)
 *   split > text region (first), media region (second, holding the image)
 *   text region > card header, points (core grid layout), card link
 *   point > icon-and-title wrapper (icon, H4), body paragraph
 * The points group is the only core grid in the pattern, which is how
 * `.wp-block-group-is-layout-grid` finds it. Change the tree, change this.
 *
 * THE CARD IS A LIGHT GROUND ON EVERY BAND, as Card styles' cards are: it
 * re-asserts its text, heading, muted, link, and focus colours, so a dark band
 * cannot turn the copy white on a light card. It must also be opaque, because
 * a later card covers an earlier one while stacking.
 *
 * THE POINTS RUN IN. Each point's H4 title and its body read as one paragraph,
 * the title at body size in the strong weight and the body muted. The H4
 * stays a heading for assistive technology; only its display changes. The
 * titles end with a full stop as a copy rule, because nothing else separates
 * them from the body.
 *
 * ICON POSITION. Beside, the hanging icon, is the base: the icon sits in its
 * own column at the start of the paragraph's first line, and every line of
 * the paragraph aligns past it, so the icons read as a column of markers.
 * Above applies only at the wide breakpoint, 1200px, and only when the
 * pattern's Icon position is Above (its default, which writes no class).
 * Below 1200px every point takes Beside whatever the option says: points sit
 * one per row there, and an icon on its own line costs a row each, 166px per
 * card on a 390px phone, measured. Beside hangs here, unlike `point-grid` and
 * `point-split`, where the title stands on its own line: Decision 91
 * refused a hanging icon for a one-line title, and this paragraph runs three
 * or four lines on a phone (Scott, 2026-10-13).
 *
 * STACKING IS CSS ONLY, keyed on the viewport, because the section spans the
 * page (CSS standards, *Viewport breakpoints are secondary*, Decision 63).
 * It engages at 1200px wide and up, where the cards reach the full 1140px
 * measure, on a screen tall enough for a card, its step, and the deepest
 * offset, and only where the reader has not asked for reduced motion.
 * Heights measured 2026-10-13 on WordPress 7.1.3 with this theme at 4.50.1
 * and the shipped placeholder copy at each slot's budget: a card is 620px
 * with icons Above and 591px Beside, the hanging indent adding a line to
 * some points; the shipped pattern (plugin 1.36.0) measured 612px and 583px.
 * Threshold = card + 24px first hold + 4 x 8px steps (the fifth card) + 24px
 * below = 692px Above, 663px Beside (re-derived at 4.51.2). Everywhere else the cards sit in a plain framed column.
 * Each card holds at the hold offset (2f) plus `spacing|40`, then one
 * `spacing|20` step per card before it, capped at the fifth, so earlier
 * cards show as edges only, never a line of their text (Scott, 2026-10-13,
 * against the SureCart reference; the first release's 24px step let text show).
 * THE STACK ENDS WITH A SPACER, `spacing|80`, while stacking. Every card is
 * held inside the cards group, so when the last card lands, an earlier card
 * taller than it is pushed up by the difference and shows its text above the
 * next. The spacer extends the group past the last card, so a difference up
 * to the spacer's height is absorbed, and the finished stack holds for that
 * much scroll before it leaves. Measured 2026-10-13: a first card two lines
 * longer than the third pushed up 40px without it, held level with it.
 * NO ANCESTOR OF THE CARDS MAY ESTABLISH A CONTAINMENT CONTEXT (CSS standards,
 * hard constraint 3), or the cards would hold against it, not the viewport.
 *
 * WHILE SCROLLING. Stacking is the pattern's default and writes no class. The
 * editor's *While scrolling* choice Scroll with the page writes
 * `c-point-stack-split--scroll`, and every stacking rule below excludes it,
 * spacer and focus lift included, so the section is the plain framed column it
 * already is on phones, short screens, and under reduced motion. Nothing else
 * changes: card padding, gaps, and the image's fill are the same either way.
 * Copy-to-Site Decision 98 (Scott, 2026-10-14).
 *
 * A CARD HOLDING KEYBOARD FOCUS LIFTS ABOVE THE CARDS AFTER IT, so a focused
 * link under a later card stays visible (WCAG 2.2 SC 2.4.11). Measured
 * 2026-10-13: without the lift, tabbing back to card 1's link under card 3
 * leaves the link covered with its ring drawn; with it, visible, at three and
 * five cards, both icon positions. `:focus-visible`, so a mouse click on a
 * link does not lift its card.
 */
.c-point-stack-split > .alignwide > .wp-block-group {
  background-color: var(--wp--preset--color--gravity-surface-nested);
  border: 1px solid var(--wp--preset--color--gravity-border);
  border-radius: var(--wp--custom--rounded--md);
  color: var(--wp--preset--color--gravity-text);
}
.c-point-stack-split > .alignwide > .wp-block-group :where(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--preset--color--gravity-text);
}
.c-point-stack-split > .alignwide > .wp-block-group a:not(.wp-element-button) {
  color: var(--wp--preset--color--gravity-link);
}
.c-point-stack-split > .alignwide > .wp-block-group a:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus);
}

/* The image is a square at the top of its column, with its own corners, and
 * never taller than three quarters of a short screen. Copy-to-Site Decision 99
 * (Scott, 2026-10-14), replacing the fill this block carried from 4.51.2.
 * THE FILL WAS RETIRED BECAUSE ITS WINDOW HAD NO SHAPE OF ITS OWN. Filling the
 * card's height made the crop the product of the text's height and the column's
 * width, two numbers varying independently, which image-handling ruling 9
 * forbids. Measured 2026-10-14 on one.gravitystack.dev at 4.53.0: the window was
 * 0.94 wide-to-tall at 1440x900, 0.70 at 1024x700, and 0.52 at 866x306 (a
 * Pixel in landscape), where a 600x540 doodle kept its middle third. Square is
 * what the window already nearly was on desktop, so the approved look holds
 * there, and editors crop to 1:1 before upload (ruling 11).
 * THE 75svh CAP is the hero split's, for the same reason: in landscape a phone
 * shows about 230px below the header, and a square as wide as its column would
 * be the only thing on screen. `svh` is the screen with the browser toolbar
 * showing, so the image fits on first view; inert on desktop and portrait
 * phones, where the column is narrower than the cap.
 * THE IMAGE CAN NOW SET THE CARD'S HEIGHT, where 4.51.2 made sure it could not,
 * but only as a floor every card shares: the squares are one width, so a card
 * is the taller of its text and the same square, and the stacked cards stay
 * level. At 1440 with the shipped placeholder copy the text region is 548px and
 * the square 513px, so the stacking thresholds above hold unchanged. */
.c-point-stack-split > .alignwide > .wp-block-group > .wp-block-group > :last-child > .wp-block-image {
  margin: 0;
}
.c-point-stack-split > .alignwide > .wp-block-group > .wp-block-group > :last-child img {
  display: block;
  inline-size: min(100%, 75svh);
  block-size: auto;
  aspect-ratio: 1;
  margin-inline: auto;
  object-fit: cover;
  border-radius: var(--wp--custom--rounded--md);
}

/* Run-in points. */
.c-point-stack-split .wp-block-group-is-layout-grid > .wp-block-group > * {
  margin-block-start: 0;
}
.c-point-stack-split .wp-block-group-is-layout-grid > .wp-block-group > .wp-block-group {
  display: inline;
}
.c-point-stack-split .wp-block-group-is-layout-grid > .wp-block-group > .wp-block-group > .wp-block-heading,
.c-point-stack-split .wp-block-group-is-layout-grid > .wp-block-group > p {
  display: inline;
  margin: 0;
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  letter-spacing: inherit;
}
.c-point-stack-split .wp-block-group-is-layout-grid > .wp-block-group > .wp-block-group > .wp-block-heading {
  font-weight: var(--wp--custom--weight--strong);
}
.c-point-stack-split .wp-block-group-is-layout-grid > .wp-block-group > p {
  color: var(--wp--preset--color--gravity-text-muted);
}

/* Icons: decorative, in the brand's darker step, 4.1:1 on the card. The
 * pattern writes `2em` on the icon, so the icon's own font-size sets its
 * size: 0.65em here makes it 1.3em of the paragraph, 22px at 17px.
 * Beside, the base: the point indents by the icon and one `spacing|30` step,
 * and the icon sits in that margin on the first line (top measured level
 * with the title's text within 1px). */
.c-point-stack-split .wp-block-group-is-layout-grid > .wp-block-group {
  position: relative;
  padding-inline-start: calc(1.3em + var(--wp--preset--spacing--30));
}
.c-point-stack-split .wp-block-group-is-layout-grid > .wp-block-group > .wp-block-group > .wp-block-icon {
  position: absolute;
  inset-block-start: 0.15em;
  inset-inline-start: 0;
  margin: 0;
  font-size: 0.65em;
  color: var(--wp--preset--color--brand-11);
}

/* Above, at the wide breakpoint, when the option is Above: the icon on its
 * own line over the paragraph, at 1.5em of the text, 25.5px. */
@media (min-width: 1200px) {
  .c-point-stack-split:not(.c-point-stack-split--icon-beside)
    .wp-block-group-is-layout-grid > .wp-block-group {
    padding-inline-start: 0;
  }
  .c-point-stack-split:not(.c-point-stack-split--icon-beside)
    .wp-block-group-is-layout-grid > .wp-block-group > .wp-block-group > .wp-block-icon {
    position: static;
    display: block;
    margin-block-end: var(--wp--preset--spacing--30);
    font-size: 0.75em;
  }
}

/* Stacking. The depth is set by position; the two queries differ only in
 * the height each icon position needs (see STACKING above). Each also
 * carries the spacer after the last card. */
.c-point-stack-split > .alignwide > .wp-block-group:nth-child(2) { --c-point-stack-split-depth: 1; }
.c-point-stack-split > .alignwide > .wp-block-group:nth-child(3) { --c-point-stack-split-depth: 2; }
.c-point-stack-split > .alignwide > .wp-block-group:nth-child(4) { --c-point-stack-split-depth: 3; }
.c-point-stack-split > .alignwide > .wp-block-group:nth-child(n+5) { --c-point-stack-split-depth: 4; }

@media (min-width: 1200px) and (min-height: 692px) and (prefers-reduced-motion: no-preference) {
  .c-point-stack-split:not(.c-point-stack-split--scroll) > .alignwide::after {
    content: "";
    display: block;
    height: var(--wp--preset--spacing--80);
  }
  .c-point-stack-split:not(.c-point-stack-split--scroll) > .alignwide > .wp-block-group {
    position: sticky;
    top: calc(var(--c-hold-admin, 0px) + var(--c-hold-top, 0px) + var(--wp--preset--spacing--40) + var(--wp--preset--spacing--20) * var(--c-point-stack-split-depth, 0));
    transition: top 180ms ease-out;
  }
  .c-point-stack-split:not(.c-point-stack-split--scroll) > .alignwide > .wp-block-group:has(:focus-visible) {
    z-index: 1;
  }
}
@media (min-width: 1200px) and (min-height: 663px) and (prefers-reduced-motion: no-preference) {
  .c-point-stack-split--icon-beside:not(.c-point-stack-split--scroll) > .alignwide::after {
    content: "";
    display: block;
    height: var(--wp--preset--spacing--80);
  }
  .c-point-stack-split--icon-beside:not(.c-point-stack-split--scroll) > .alignwide > .wp-block-group {
    position: sticky;
    top: calc(var(--c-hold-admin, 0px) + var(--c-hold-top, 0px) + var(--wp--preset--spacing--40) + var(--wp--preset--spacing--20) * var(--c-point-stack-split-depth, 0));
    transition: top 180ms ease-out;
  }
  .c-point-stack-split--icon-beside:not(.c-point-stack-split--scroll) > .alignwide > .wp-block-group:has(:focus-visible) {
    z-index: 1;
  }
}

/* ── Step list ───────────────────────────────────────────────────────────
 *
 * Skin for `gravitystack/step-list`: the steps of a process as a real ordered
 * list, each with a marker joined to the next by a line, then an optional ask.
 * Commissioned and ruled in GravityBrain 2026-10-13 (Scott, with the Design
 * Team panel on the renders) as Copy-to-Site Decision 97. Every rule is keyed
 * on the pattern's class, so nothing here reaches a page until the pattern
 * ships; it merges first so the pattern's pull request preview shows the real
 * layout.
 *
 * COUPLED TO THE PATTERN'S TREE, which carries no classes below the root:
 *   root > header group, steps (a Group rendering as `ol`, `alignwide`),
 *          ask group (buttons, microcopy)
 *   steps > step (a Group rendering as `li`), one per step
 *   step > icon (optional, core Icon), text group, client part (optional p)
 *   text group > timing (optional p), title (H3), body (p)
 * Every step in a section carries the same optional parts, so the rows line
 * up. Change the tree, change this.
 *
 * THE NUMBER IS GENERATED, never typed. A counter on the steps draws it in
 * the marker, or in the "Step N" label when the step carries an icon, so it
 * stays right when a step is added, removed, or reordered. Visible generated
 * text is also what keeps Safari reading the list as a list once its markers
 * are hidden. The word is `--c-step-list-label`, a theme value: a French or
 * bilingual client sets it here and nowhere else.
 *
 * THE LINE AND THE GAPS BETWEEN STEPS ARE THE THEME'S. The connector has to
 * span the gap between two steps, so the gap is padding on the step rather
 * than the steps group's blockGap, which the rule below cancels. In Direction
 * Horizontal the same distance is the grid's column gap.
 *
 * DIRECTION. Vertical is the base and writes no class. Horizontal,
 * `c-step-list--horizontal`, applies from bp-2 only: the steps become one row
 * of equal columns that share a grid, so timing, titles, bodies, and client
 * parts line up across the row whatever their lengths, and the ask is centred
 * under the row. Below bp-2 every step list runs down the page.
 *
 * THE MARKER is a disc in the brand role. A fill obliges its contents, so the
 * number and the icon re-assert the text colour on every band (about 7.2:1 by
 * calculation; the pattern's accessibility line records the measure).
 */
.c-step-list {
  --c-step-list-label: "Step";
  --c-step-list-marker: 2.75rem;
}
.c-step-list > ol.alignwide {
  list-style: none;
  padding: 0;
  counter-reset: c-step;
  max-width: var(--wp--style--global--content-size);
}
.c-step-list > ol > li {
  counter-increment: c-step;
  position: relative;
  padding-block-end: var(--wp--preset--spacing--60);
}
/* The marker column. Written at (0,3,2), with both groups' classes, because
 * `foundation.css`'s `.wp-block-group.is-layout-flow.wp-block-group-is-layout-flow`
 * resets a flow group's side padding at (0,3,0); at (0,1,2) the text sat under
 * the markers on the pull request preview. Horizontal cancels it at the same
 * weight. */
.c-step-list > ol.wp-block-group > li.wp-block-group {
  padding-inline-start: calc(var(--c-step-list-marker) + var(--wp--preset--spacing--40));
}
.c-step-list > ol > li + li {
  margin-block-start: 0;
}
.c-step-list > ol > li:last-child {
  padding-block-end: 0;
}

/* The marker: the number, or the icon when the step carries one. */
.c-step-list > ol > li:not(:has(> .wp-block-icon))::before,
.c-step-list > ol > li > .wp-block-icon {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0;
  inline-size: var(--c-step-list-marker);
  block-size: var(--c-step-list-marker);
  border-radius: var(--wp--custom--rounded--full);
  background-color: var(--wp--preset--color--gravity-brand);
  color: var(--wp--preset--color--gravity-text);
  display: grid;
  place-items: center;
  z-index: 1;
}
.c-step-list > ol > li:not(:has(> .wp-block-icon))::before {
  content: counter(c-step);
  font-family: var(--wp--preset--font-family--outfit);
  font-weight: var(--wp--custom--weight--strong);
  font-size: var(--wp--preset--font-size--text-4);
  line-height: 1;
}
.c-step-list > ol > li > .wp-block-icon svg {
  inline-size: 1.5rem;
  block-size: 1.5rem;
  fill: currentColor;
}
/* The icon is out of flow, so the text group starts the step. */
.c-step-list > ol > li > .wp-block-icon + * {
  margin-block-start: 0;
}

/* The connector: decorative, the border role, always on. */
.c-step-list > ol > li:not(:last-child)::after {
  content: "";
  position: absolute;
  inset-inline-start: calc(var(--c-step-list-marker) / 2 - 1px);
  inset-block-start: var(--c-step-list-marker);
  inset-block-end: 0;
  inline-size: 2px;
  background-color: var(--wp--preset--color--gravity-border);
}

/* The first line of text sits on the marker's centre. */
.c-step-list > ol > li > .wp-block-group > p:first-child {
  padding-block-start: calc((var(--c-step-list-marker) - 1.4em) / 2);
}
.c-step-list > ol > li > .wp-block-group > h3:first-child {
  padding-block-start: calc((var(--c-step-list-marker) - 1.25em) / 2);
}

/* Timing, and the "Step N" label when the step carries an icon. Secondary by
 * size, weight, and tracking rather than colour, so it survives every band. */
.c-step-list > ol > li > .wp-block-group > p:has(+ h3),
.c-step-list > ol > li:has(> .wp-block-icon) > .wp-block-group > h3:first-child::before {
  font-family: var(--wp--preset--font-family--figtree);
  font-size: var(--wp--preset--font-size--text-1);
  font-weight: var(--wp--custom--weight--strong);
  letter-spacing: var(--wp--custom--tracking--wide);
  text-transform: uppercase;
  line-height: 1.4;
}
.c-step-list > ol > li:has(> .wp-block-icon) > .wp-block-group > h3:first-child::before {
  content: var(--c-step-list-label) " " counter(c-step);
  display: block;
  margin-block-end: var(--wp--preset--spacing--20);
}
.c-step-list > ol > li:has(> .wp-block-icon) > .wp-block-group > p:has(+ h3)::before {
  content: var(--c-step-list-label) " " counter(c-step) "\00a0\00a0\00b7\00a0\00a0";
}
/* An optional line left empty in the editor cannot be deleted under the
 * lock, so it hides rather than holding a blank line. */
.c-step-list > ol > li p:empty {
  display: none;
}

/* Direction: Horizontal, from bp-2. */
@media (min-width: 1200px) { /* bp-2 */
  .c-step-list--horizontal > ol.alignwide {
    max-width: var(--wp--style--global--wide-size);
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    grid-template-rows: repeat(4, auto);
    column-gap: var(--wp--preset--spacing--50);
  }
  .c-step-list--horizontal > ol > li {
    display: grid;
    grid-row: 1 / span 4;
    grid-template-rows: subgrid;
    align-content: start;
    padding-block: calc(var(--c-step-list-marker) + var(--wp--preset--spacing--40)) 0;
  }
  .c-step-list--horizontal > ol.wp-block-group > li.wp-block-group {
    padding-inline-start: 0;
  }
  .c-step-list--horizontal > ol > li > .wp-block-group {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
    align-content: start;
  }
  .c-step-list--horizontal > ol > li > .wp-block-group > :is(p, h3):first-child {
    padding-block-start: 0;
  }
  .c-step-list--horizontal > ol > li:not(:last-child)::after {
    inset-block-start: calc(var(--c-step-list-marker) / 2 - 1px);
    inset-block-end: auto;
    inset-inline-start: calc(var(--c-step-list-marker) + var(--wp--preset--spacing--20));
    inset-inline-end: calc(var(--wp--preset--spacing--20) - var(--wp--preset--spacing--50));
    inline-size: auto;
    block-size: 2px;
  }
  .c-step-list--horizontal > ol + .wp-block-group {
    text-align: center;
  }
  .c-step-list--horizontal > ol + .wp-block-group > .wp-block-buttons {
    justify-content: center;
  }
}

/* ── Hero split ──────────────────────────────────────────────────────────
 *
 * THE PANEL IS NEVER TALLER THAN THREE QUARTERS OF THE SCREEN. Copy-to-Site
 * Decision 99 (Scott, 2026-10-14). Below the two-track crossover the panel
 * stacks under the copy at its 4:3 window and natural width, which on a phone
 * in landscape is far taller than the screen: 600x450 against about 230px
 * visible below the header at 866x306, measured on one.gravitystack.dev at
 * 4.53.0. The cap is on the width, so the block's own `aspectRatio` still sets
 * the height and the window keeps its 4:3 shape (ruling 9); the image only gets
 * smaller, and centres. Capping the height instead let the photograph's own
 * shape through (546x491 at 1440, measured 2026-10-14), so this restates the
 * markup's 4:3. THE 4 / 3 BELOW IS `hero-split.php`'s `aspectRatio`, AND THE
 * TWO CHANGE TOGETHER. `svh` is the screen with the browser toolbar showing,
 * so the panel fits on first view. Inert on desktop and portrait phones, where
 * the track is the smaller limit. */
.c-hero-split .d-hero-panel img {
  display: block;
  max-inline-size: min(100%, calc(75svh * 4 / 3));
  margin-inline: auto;
}

/* ── Hero, flush split ───────────────────────────────────────────────────
 *
 * Skin for `gravitystack/hero-flush-split`. The pattern carries structure and
 * no appearance; everything here is a relationship the block markup cannot
 * express, not a treatment that could have been authored as an attribute.
 *
 * The section takes no page inset — the generated section spacing rule excludes
 * it by the `-flush-` substring in its class. Nothing here re-asserts that and
 * nothing here should.
 */

/* The photograph fills its region, which is a relationship to a flex parent
 * that no core image attribute expresses. Core sizes an image by aspect ratio
 * or by explicit dimensions, and both are the wrong instrument for "as tall as
 * whatever the copy beside you turns out to be."
 *
 * THE FIGURE IS OUT OF FLOW, AND THAT IS THE 4.36.1 FIX. Through 4.36.0 this
 * rule set `height: 100%` and the comment here claimed the region "is a flex
 * child stretched to the row's height, and the figure has to pass that height
 * through to the image." **That is true only when something else in the row is
 * taller.** When the image is the tallest thing, `height: 100%` resolves
 * against an auto-height parent, falls back to intrinsic, and the image sizes
 * the row instead of filling it.
 *
 * The consequence was that hero height was `0.38 x viewport width x the
 * image's aspect ratio` — a property of what an editor uploaded and how wide
 * the reader's monitor was, and the `min-block-size` floor this file then
 * carried was inert against it. **That floor is gone as of 4.37.0** — see the
 * window comment below, which retired it. Measured 2026-09-06 at a 1828px
 * layout width with the 520px floor still in place:
 * a 3:2 landscape gave 520px, a square 695px, a 4:5 portrait 868px, and a 2:3
 * portrait 1042px — taller than the viewport. **A portrait is the aspect this
 * pattern exists for, and it was the worst case.**
 *
 * `position: absolute` with `inset: 0` takes the figure out of flow entirely,
 * so it cannot contribute height. The region is already `position: relative`
 * for the caption scrim below, so this costs no new declaration. `object-fit:
 * cover` crops the photograph to whatever the region turns out to be, and
 * `height: 100%` is gone: `inset: 0` supplies the box. **At 4.36.1 row height
 * became the greater of the copy region and the floor; at 4.37.0 it is the
 * greater of the copy region and the media region's own 1:1 height**, which is
 * the whole difference between the two passes.
 *
 * WHY THIS IS NOT AUTHORED IN THE PATTERN. Spectra Blocks Pro writes a
 * `spectraId` into every `core/image` and emits per-instance CSS keyed on
 * `[data-spectra-id]` in the triple-class form, over exactly `width`,
 * `height`, `aspect-ratio`, and `object-fit`. Authoring the attributes in the
 * pattern would put four values in the one place that layer is guaranteed to
 * reach. Here they are at least a rule a reader can find.
 *
 * **THIS PARAGRAPH READ (0,4,0) AND "LATENT RATHER THAN LIVE" UNTIL 4.37.0.**
 * Both were measured wrong on 2026-09-07: the specificity is **(0,4,1)** and
 * the layer is **live**, emitting one rule per sized image instance inside its
 * own three breakpoints. `ops-pattern-standards.md` validation item 3 still
 * carries the stale pair and is not this file's to move. The *Below the
 * collapse* comment holds the full reading; corrected here rather than left
 * standing, because a wrong specificity two comments above the window rule that
 * depends on the right one is a contradiction inside one file. The owed render
 * check is not owed any more — it has been run, and `object-fit: cover`
 * survives only because this image carries no size value, which is why it must
 * keep carrying none.
 *
 * `object-position: top` is the anchor, not a preference — an unexpected crop
 * on a portrait should take the shoulders, never the forehead.
 */
.c-hero-flush-split > .wp-block-group:first-child .wp-block-image {
  position: absolute;
  inset: 0;
  margin-block: 0;
}

.c-hero-flush-split > .wp-block-group:first-child .wp-block-image img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
}

/* The caption sits ON the photograph, which is why it is the pattern's one
 * stated colour exemption. Band inheritance reaches the copy side and does not
 * reach a line sitting over an image, so this is the one place the pattern's
 * no-colour rule would leave a value undecided against an unknown photograph.
 *
 * The scrim is the legibility guarantee and the colour is not. A light value
 * over an unknown image is a coin toss; the scrim is what makes the pair safe
 * on any photograph, so the two ship together and neither is optional.
 *
 * `:first-child` is the media region and is stable under the pattern's
 * `templateLock: contentOnly` — an editor cannot reorder the two regions, which
 * is what makes positional selection legitimate here rather than fragile.
 *
 * THE CAPTION IS SELECTED AS THE REGION'S PARAGRAPH, NOT AS THE FIGURE'S
 * SIBLING, and the difference is not stylistic. This rule read
 * `> .wp-block-image + p` until the first render check. **A `core/image` with
 * no `url` renders nothing at all on the front end** — not an empty figure, no
 * element — so on any instance whose media slot is still unfilled the adjacency
 * has nothing to match and the caption falls back to dark body text sitting
 * under an empty region. That is precisely the state a pattern ships in, so the
 * defect would have been visible on every unfilled instance and invisible on
 * every filled one. Measured on the dev site 2026-09-05.
 */

/* THE SECOND DECLARATION ON THIS RULE IS THE CLIP, ADDED AT 4.38.5, AND IT IS
 * NOT THERE FOR THE FRONT END. The figure above is `position: absolute;
 * inset: 0`, so on a rendered page it already fills this box exactly and there
 * is nothing for `overflow: hidden` to cut. The render is unchanged by it.
 *
 * IT IS THERE BECAUSE CONTAINMENT HAD NO FALLBACK. The photograph is held
 * inside this region by three coupled rules — this one's `position: relative`,
 * the figure's `position: absolute; inset: 0` above, and the `aspect-ratio:
 * 1 / 1` in the window rule below. All three have to land. If any one of them
 * does not, the image renders in flow at its own natural size and nothing stops
 * it leaving the box, because until now no rule in this file said it could not.
 *
 * THAT IS NOT HYPOTHETICAL — IT IS THE EDITOR CANVAS. Observed 2026-09-15 on
 * one.gravitystack.dev: the region measures the correct 600px wide in the
 * canvas, and the image inside it renders at roughly 600 x 740 — its own
 * portrait aspect at 100% width — spilling past the region and over the section
 * beneath it. That is the signature of the figure being IN FLOW: `height: 100%`
 * then has no definite parent to resolve against and `object-fit: cover` has no
 * box to crop to. **Editor style delivery is not the cause** and was re-read the
 * same day: `functions.php`'s editor leg registers this file into the canvas at
 * priority 20, and the iframe leaves its selectors unprefixed.
 *
 * WHAT IS STILL OPEN is which canvas declaration beats
 * `.c-hero-flush-split > .wp-block-group:first-child .wp-block-image` at (0,3,0)
 * on `position`. An inline style on the figure is the only thing that can short
 * of `!important`, which makes the editor's own Image block the first place to
 * look. The DOM read that would settle it is logged in the plugin's
 * `patterns/hero-flush-split-refinements.md`. **This rule does not wait on that
 * answer, and that is the point of it** — a clip holds whatever the cause turns
 * out to be, where a specificity counter-move would only hold against this one.
 *
 * WHY THIS IS A COMPLETION RATHER THAN A PATCH. A fixed-ratio media window that
 * cannot clip its own contents is under-specified as a component, independently
 * of anything the canvas is doing; the clip is the other half of what
 * `inline-size` plus `aspect-ratio` means below. The patch-shaped answer was an
 * editor-only override in `editor.css`, and it is refused: it would fix the
 * canvas and leave the render resting on the same three unguarded rules.
 *
 * ONE BEHAVIOURAL CONSEQUENCE, AND IT IS WANTED. In the canvas an editor now
 * sees the top 600px of an in-flow image rather than a `cover` crop. With
 * `object-position: top` on the render those two are near-identical, so the
 * canvas reads as the page rather than merely stopping short of overflowing.
 */
.c-hero-flush-split > .wp-block-group:first-child {
  position: relative;
  overflow: hidden;
}

.c-hero-flush-split > .wp-block-group:first-child > p {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  margin-block: 0;
  padding: var(--wp--preset--spacing--50);
  color: var(--wp--preset--color--gravity-text-inverse);
  background-image: linear-gradient(
    to top,
    color-mix(in srgb, var(--wp--preset--color--gravity-surface-dark) 78%, transparent),
    transparent
  );
}

/* The two-line action. The microcopy is a span inside the button's own label,
 * so the button reads as one control on two lines rather than a button with a
 * caption beneath it.
 *
 * COUPLED TO `.c-action__meta` IN THE HEADER SECTION BELOW, which supplies the
 * span's own display, size, and weight. That rule is written unscoped and this
 * is now its second consumer, so its home in the header section understates its
 * reach. Do not copy it here — one fact, one home — but do not scope it to the
 * header either without moving it somewhere both organisms can reach.
 *
 * Only the line height is this pattern's. The header's own rule adds a centred
 * alignment and a negative top margin that pull the pill up against a nav
 * baseline; neither belongs on a hero button sitting in a left-aligned column.
 */
.c-hero-flush-split .wp-block-button__link {
  line-height: 1.25;
}

/* Orientation. The axis writes the modifier; this is the whole of what it does.
 *
 * The flip is `row-reverse` on the container rather than `order` on the
 * children, because `order` would also reverse the stacked arrangement once the
 * row turns vertical — and the stacked order is ruled: media above copy, which
 * the DOM already carries, with no `order` override anywhere.
 *
 * SCOPED ABOVE THE COLLAPSE SO IT CANNOT FIGHT IT, AND THE CONDITION IS CORE'S
 * OWN COMPLEMENT RATHER THAN A SECOND DECLARATION OF THE THRESHOLD. Core emits
 * the pattern's per-viewport layout override as `(width <= 480px)` and
 * `(480px < width <= 767px)`. This rule answers with `(width > 767px)`, which
 * is the exact complement of that pair — true wherever core's is false, at any
 * fractional width.
 *
 * IT READ `(min-width: 768px)` UNTIL 4.36.0 AND THAT LEFT A GAP. At a viewport
 * of 767.1875px — measured on the render, and reachable through browser zoom or
 * Windows display scaling — `width <= 767px` is false and `min-width: 768px` is
 * false too, so neither side applied and a `--media-right` hero silently
 * rendered media-LEFT. Nothing errored and the layout did not break; it
 * mirrored. The 4.35.0 comment here claimed the two thresholds "align by
 * construction," which was true only at integer widths.
 *
 * This also retires the coupling that comment asked a future reader to keep in
 * step. There is no longer a second declaration of the threshold to maintain: a
 * change to `settings.viewport` in `theme.json` moves core's condition, and this
 * one follows it by being its complement rather than its twin.
 */
/* `point-split` DECLARES THE SAME AXIS, AND SHARES THIS CONDITION.
 * Admitted as Orientation's second declarer by Copy-to-Site Decision 92's
 * amendment, at 4.47.0. Same meaning, same mechanism, same complement of
 * core's collapse, which this pattern also emits as `(width <= 480px)` and
 * `(480px < width <= 767px)`.
 *
 * ONE DIFFERENCE, AND IT IS THE CHILD STEP. The flush hero's root IS the row.
 * This pattern's root is a constrained section and the row is its second
 * child; the header wrapper carries no layout, so the row is the root's only
 * `is-layout-flex` child. Route recorded on
 * `point-split-refinements.md` in `gravitystack-patterns`.
 */
@media (width > 767px) {
  .c-hero-flush-split--media-right {
    flex-direction: row-reverse;
  }

  .c-point-split--media-right > .is-layout-flex {
    flex-direction: row-reverse;
  }

  /* `text-split` is the third declarer (6.6.0, ahead of the pattern), built
   * as `point-split`'s sibling: the same constrained root with the heading
   * area first and the row as the root's only `is-layout-flex` child, so it
   * takes the same child step. Ruled 2026-10-20 in GravityBrain.
   */
  .c-text-split--media-right > .is-layout-flex {
    flex-direction: row-reverse;
  }
}

/* THE WINDOW. The media region carries its own width and its own shape, and
 * that pair is the whole of the geometry repair at 4.37.0.
 *
 * THE DEFECT IT REPLACES. The region took its width from the viewport — the
 * pattern's `flexSize: "38%"`, which core emits as `flex-basis` — and its
 * height from whatever the row turned out to be. Two dimensions varying without
 * reference to each other, so the window ran from 0.96 to 1.61 across the
 * desktop range: square on one monitor, letterbox on another. No photograph can
 * be composed for that, and an operator replaced the image twice on the
 * assumption that the picture was the problem. 4.36.1 stopped the photograph
 * setting the height; it did not give the window a shape. **Ownership and
 * geometry are separate tests, and this is the second one.**
 *
 * `inline-size` AND `aspect-ratio` TOGETHER, ON THE GROUP, NEVER ON THE IMAGE.
 * The image stays attribute-free and that is not a preference. Spectra Blocks
 * Pro takes any geometry property a `core/image` declares, re-emits it at
 * (0,4,1), and strips the inline style from the rendered output — measured
 * 2026-09-07, recorded in the *Below the collapse* comment. `base.css` cannot
 * outrank that. A block declaring nothing draws no Spectra rule at all, which
 * is exactly why this pattern's `object-fit: cover` above renders unopposed.
 * Spectra keys on `[data-spectra-id]`, which it writes into `core/image` only —
 * every one on the page sits on a `figure.wp-block-image`, none on any
 * `wp-block-group` — so the shape is safe on this element and would not be one
 * declaration further in.
 *
 * `flex: 0 0 auto` IS LOAD-BEARING, AND SO IS THE `1 1 auto` BESIDE IT. Core
 * emits `flex-basis: 38%` on the media region and `62%` on the copy region from
 * the pattern's `flexSize`, at (0,1,0), which these rules outrank at (0,3,0) —
 * confirmed on the render, computed flex reads `0 / 0 / auto`. But those two
 * percentages summed to 100 and were what made the row fill its container. Cap
 * the media at 600px and leave the copy at `flex-basis: 62%` with no grow and
 * the row runs **94.7px short at 1843** — measured: a dead strip of band colour
 * against the right edge. The copy region has to absorb the remainder, and
 * nothing in the pattern makes it.
 *
 * WHY 1:1. At the 1843 reference the old window measured 694.6 x 520, or 1.336
 * against 4:3's 1.333, so 4:3 had an honest claim on the arithmetic. It is
 * refused on purpose: 4:3 is landscape and this pattern exists for a person
 * shown at size. Against a 3:4 portrait source with this file's `top` anchor,
 * 4:3 shows 56% of the frame, which on a head-and-shoulders shot lands below
 * the collar; 1:1 shows 75%, head and chest. No row is added to the approved
 * ratio set — the existing `1:1` row broadens from card grids to include a
 * full-height hero media region.
 *
 * WHY THE 600px CAP, AND IT IS THE ONE TUNABLE NUMBER HERE. It makes the width
 * ceiling equal the height ceiling, so a single value governs both and the
 * largest window is 600 x 600. If the photograph reads too small on a wide
 * monitor this is the lever, and the architecture does not move with it.
 *
 * `min(38vw, 600px)` IS ALSO DECLARED IN THE PATTERN, AND THAT DUPLICATION IS
 * DELIBERATE — theme 4.38.0, plugin 1.29.0. `hero-flush-split.php` in
 * `gravitystack-patterns` carries the same expression as its delivery width
 * declaration, which the delivery role reader turns into the image's `sizes`
 * attribute. This rule renders the region; that declaration reports its width
 * to the browser. There is no channel between a stylesheet and a render filter
 * that would let one read the other, so both copies exist and both are
 * cross-referenced. **If you change this value, change that one in the same
 * commit** — a stale `sizes` is invisible on the page and shows up only as a
 * soft or an over-fetched photograph. The collapse trigger, once a third
 * pattern needs a pattern-local width or either copy moves, is for the reader
 * to emit this as a custom property and for this rule to consume it.
 *
 * `min(38vw, 600px)` RATHER THAN A CLAMP, AND THE MINIMUM WAS MEASURED OUT
 * RATHER THAN REASONED OUT. A 360px lower bound engages from the collapse up to
 * a 947px viewport, and in that band it makes the media WIDER than 38% and
 * takes the difference out of the copy side: at 768 the panel's content width
 * falls from 333px to 264.8px and the ruled 47-character statement goes from
 * three lines to five. Measured at the ruled length — window, then statement
 * lines:
 *
 *   viewport   clamp(360px, 38vw, 600px)   min(38vw, 600px)
 *   768         0.851 / 5 lines             0.821 / 4 lines
 *   800         1.000 / 4 lines             0.968 / 3 lines
 *   850         0.963 / 4 lines             0.991 / 3 lines
 *   947         1.000 / 3 lines             1.000 / 3 lines
 *
 * `min()` holds the statement at three lines from 800px up — the same as every
 * wider viewport — and the window at 0.968 or better, against a clamp that
 * swings 0.851 to 1.000 and breaks the statement onto four and five lines. **The
 * clamp's better ratio at 768 buys a worse render.** Below 767.02px this rule
 * does not apply at all, so there is nothing for a lower bound to protect.
 *
 * THE 52vh FLOOR IS RETIRED, AND DELETED RATHER THAN SCOPED. It read
 * `min-block-size: clamp(420px, 52vh, 600px)` on the row, and its stated job was
 * stopping the media region collapsing. The region now declares its own
 * `inline-size` and derives its height from it, so it cannot collapse and the
 * job is done upstream of the floor. Keeping it costs: measured 2026-09-07 at
 * the ruled length, the floor resolves to 468px at 1200x900 against a 456px
 * media height and drags the window to 0.974, and at 768x1024 it resolves to
 * 532.5px and drags it to **0.548 against 0.821 without it** — a residue of
 * exactly the kind this repair exists to remove, worst in the band nobody had
 * measured.
 *
 * IT WAS ALSO ALREADY INERT BELOW THE COLLAPSE, which is the other half of why
 * it is deleted rather than moved into the mobile block. Below 767px the row is
 * a column: `45vh` of media plus the copy region's own content. At 390x844 that
 * is 379.8 + 291.5 = 671.3px against a 438.9px floor, and the floor would only
 * begin to bind below roughly 338px of viewport height. Read on the render with
 * the floor present and absent — identical to the tenth of a pixel.
 *
 * The copy region still centres rather than stretches: it holds one cluster
 * rather than three, and distributing it against a row taller than its content
 * would strand the eyebrow at the top edge. That rule is below and does not
 * move.
 *
 * THE STATEMENT TAKES THE SAME MEASURE CAP IT TAKES BELOW THE COLLAPSE, and the
 * reason is the mirror image of the mobile one. With the media capped the copy
 * panel absorbs every pixel above the cap's engagement: 1832px of content width
 * at 2560, where the clamp's 19.8-character target wants 1052px. Uncapped, the
 * 47-character statement rendered on TWO lines there and the 32-character
 * placeholder on one, which is not a hero statement. `8.15em` is that target
 * expressed in the statement's own em, so it tracks the clamp rather than
 * restating it, and it is the same declaration the mobile block already
 * carries. Measured: it binds at 1843 (1005.2px cap against 1100px of panel)
 * and is inert at 1440 and below (757.6px against 749.6px). It changes no line
 * count at 1843; at 2560 it takes the ruled statement from two lines to three,
 * which is what every other desktop width renders.
 */
@media (width > 767px) {
  .c-hero-flush-split > .wp-block-group:first-child {
    inline-size: min(38vw, 600px);
    aspect-ratio: 1 / 1;
    flex: 0 0 auto;
  }

  .c-hero-flush-split > .wp-block-group:last-child {
    flex: 1 1 auto;
  }

  .c-hero-flush-split > .wp-block-group:last-child > .has-statement-font-size {
    max-inline-size: 8.15em;
  }
}

.c-hero-flush-split > .wp-block-group:last-child {
  justify-content: center;
  row-gap: 0;
  padding: var(--wp--preset--spacing--70);
}

/* The eyebrow-to-statement bind, proportional to the statement.
 *
 * THE GAP LIVES ON THE STATEMENT, NOT ON THE EYEBROW, AND NOT ON THE PARENT.
 * A gap cannot reference a child's font size, so the parent is out. An `em`
 * margin on the eyebrow resolves against the EYEBROW's own `text-2` — a fixed
 * 15px — so it would reproduce the fixed 8px it replaces. Only an `em` on the
 * statement resolves against the statement.
 *
 * The pattern's own `blockGap` is zeroed above rather than tuned, so this is the
 * single home for the value.
 */
.c-hero-flush-split > .wp-block-group:last-child > .has-statement-font-size {
  margin-block-start: 0.12em;
  margin-block-end: 0;
}

/* An empty caption hides rather than being deleted, because `templateLock:
 * contentOnly` forbids an editor removing the block. A caption suits a portrait
 * and is nonsense over a streetscape, so the slot is conditional on the media
 * rather than on the pattern.
 *
 * `:empty` matches only a genuinely empty element: a space or an `&nbsp;` left
 * behind defeats it and the caption returns. Measured 2026-09-06 from the DOM.
 * What WordPress serializes when an editor clears a locked slot is the open half
 * and is checked in the editor, not from here.
 */
.c-hero-flush-split > .wp-block-group:first-child > p:empty {
  display: none;
}

/* Below the collapse.
 *
 * THE MEDIA TAKES A block-size. With the figure out of flow the region has no
 * intrinsic height at all, so it needs one declared rather than capped — this
 * read `max-block-size` until 4.36.1, which was correct while the image still
 * sized the region.
 *
 * `aspect-ratio` IS AVAILABLE ON THIS REGION, AND THE WINDOW RULE ABOVE NOW
 * USES IT. It was refused here until 2026-09-07 on the grounds that it is one
 * of the four properties Spectra Blocks Pro's per-instance image layer writes —
 * and that reason does not hold for this element, on two counts.
 *
 * Narrowly: Spectra keys on `[data-spectra-id]`, which it writes into
 * `core/image`. This region is a `core/group`. The image layer does not reach
 * it. Measured on the dev site 2026-09-07 — every `[data-spectra-id]` on the
 * page sits on a `figure.wp-block-image`, none on any `wp-block-group`, this
 * region included.
 *
 * More importantly, the premise that authoring a property EXPOSES it to Spectra
 * runs backwards for the properties in question. `testimonial-grid-refinements.md`
 * records Spectra emitting `object-fit` at (0,4,1), beating anything this file
 * can write against that element, with the fit surviving only because the
 * block's `scale` attribute emits an INLINE `object-fit`. For the `core/image`
 * properties that serialize onto the `<img>` — `aspectRatio`, `scale`, `width`,
 * `height` — authoring the block value OUTRANKS Spectra rather than conceding
 * to it. Ordinary CSS specificity, and it applies only to those four: colour,
 * typography, and layout serialize differently and get no inline advantage.
 *
 * So the choice below the collapse is not a precedence question at all. It is a
 * separate design question — how tall should a full-width media band be on a
 * phone — and `45vh` is the current answer to it.
 *
 * WHAT THIS CORRECTION LEFT UNSETTLED, AND 4.37.0 SETTLED. When this comment
 * was written the frame was not thereby conformant: above the collapse the
 * region's shape was still the consequence of a 38% width and an independently
 * clamped height, two dimensions varying without reference to each other, which
 * was the defect the geometry pass existed to fix. Ownership and geometry are
 * separate tests and this correction settled only the first. **The second is
 * settled at 4.37.0 by the window rule above:** above the collapse the region
 * is `min(38vw, 600px)` wide and 1:1, so the window is square at every desktop
 * width from roughly 850px up and degrades to a shallow portrait below that
 * rather than swinging to letterbox. The 52vh row floor was retired in the same
 * pass, and this block is now the only place a hero height is declared.
 *
 * BELOW THE COLLAPSE THE SHAPE IS STILL NOT 1:1, AND THAT IS DELIBERATE. The
 * media goes full width here, so a 1:1 window would be as tall as the viewport
 * is wide — 390px on a common phone, before the copy. `45vh` is the current
 * answer and whether a different mobile ratio is wanted is a design question,
 * not a conformance one: the pattern owns the value either way, which is the
 * test that matters.
 *
 * ALSO MEASURED 2026-09-07, AND THE LAYER IS LIVE RATHER THAN LATENT. Spectra
 * emits `inline#spectra-responsive-styles-inline-css`, one rule per sized image
 * instance at `.wp-block-image.wp-block-image.wp-block-image[data-spectra-id]
 * img` — (0,4,1), not the (0,4,0) the docblock above states — inside its own
 * three breakpoints (max 767.98px / 768–1023.98px / min 1024px), which are not
 * our `settings.viewport` tiers. On this page it emitted `width` and `height`
 * only, and for an image carrying no size value it emitted three EMPTY media
 * blocks: nothing at all. That is why this pattern's `object-fit: cover` in the
 * docblock above is unopposed today. The docblock's own (0,4,0) claim and its
 * "latent rather than live" note are both stale; correcting them is a separate
 * pass, not this comment's.
 *
 * THE STATEMENT TAKES A MEASURE CAP, AND THIS IS THE ONE THAT IS NOT OBVIOUS.
 * The copy region goes full width at the collapse while the statement is already
 * pinned at its floor, so the measure doubles with nothing absorbing it: 18.7
 * characters per line at 768px becomes 38.8 at 767px, one pixel apart. Padding
 * cannot answer it — holding the target at 767px would need 204px of inset per
 * side. 8.15em is 19.8 characters at the measured 0.4114 em/char advance, the
 * same target the clamp serves above the collapse, and being in `em` it tracks
 * the clamp rather than restating it. Inert below roughly 422px of viewport,
 * where the container is already narrower than the cap.
 *
 * The caption returns to `spacing|40` because the media now runs full width and
 * that value is the site's global padding, so the caption lines up with the
 * page's own left edge rather than with the copy side's inset.
 */
@media (width <= 767px) {
  .c-hero-flush-split > .wp-block-group:first-child {
    block-size: 45vh;
  }

  .c-hero-flush-split > .wp-block-group:first-child > p {
    padding: var(--wp--preset--spacing--40);
  }

  .c-hero-flush-split > .wp-block-group:last-child {
    padding: var(--wp--preset--spacing--50);
  }

  .c-hero-flush-split > .wp-block-group:last-child > .has-statement-font-size {
    max-inline-size: 8.15em;
  }
}

/* ── Display preset binding ──────────────────────────────────────────────
 * Added 2026-08-27 alongside gravitystack-design.md v3.9, which raised the
 * display ceiling from 44px to 72px.
 *
 * Why this rule has to exist. `display` is the one type role with no HTML
 * element, so design-to-theme-json emits it as a font-size preset and
 * nothing else — its family, weight, leading, and tracking have no
 * theme.json surface to land on. The design file had recorded a leading
 * since v3.8 and nothing shipped had ever applied it.
 *
 * At the old 44px ceiling that was untidy. At 72px it is a defect: a
 * display heading inheriting prose leading renders as stacked separate
 * lines rather than one block, which is the single most consistent failure
 * mode in large type. Do not remove this rule as cosmetic. If the display
 * ceiling moves again, confirm this rule is still in force first.
 *
 * LEADING RE-DERIVED 1.1 → 1.0 at design file v4.1, 2026-08-30, from a
 * render on this site. 1.1 was pinned at v3.8 against the 44px ceiling and
 * travelled unchanged through the v3.9 raise to 72px, so a ratio derived
 * against one size was shipping against a size nearly twice it. The design
 * file carries the reasoning; this rule carries the value. The rule the
 * pair produced: a ceiling and the ratio derived against it move in the
 * same commit or not at all.
 *
 * Specificity. WordPress emits `.has-display-font-size` for the preset, and
 * theme.json element styles emit at `:root :where(h1)` — a 0,1,0 tie with a
 * bare class, resolved only by source order, which is not something to
 * depend on. `:root` here takes the selector to 0,2,0 so it wins outright
 * whichever element carries the preset. The font-size itself is left alone:
 * WordPress ships that with !important and it is already correct.
 *
 * Tracking. Outfit is a wide geometric and its default fit reads loose
 * above roughly 56px. The design file's tracking scale is a token family
 * for uppercase treatment, not an optical correction at size, so this is a
 * literal rather than a token reference. Revisit if a tracking role is ever
 * admitted for large type.
 */
:root .has-display-font-size {
  font-family: var(--wp--preset--font-family--outfit);
  font-weight: var(--wp--custom--weight--strong);
  line-height: 1;
  letter-spacing: -0.02em;
}

/* ── Statement preset binding ────────────────────────────────────────────
 * Added 2026-09-05 alongside gravitystack-design.md v4.4, which added the
 * `statement` type role, and gravitysite-design-system-standards.md v2.32,
 * which generalised the preset-emitting role exception from a singleton to
 * a class. This is the second member of that class.
 *
 * IT INHERITS NOTHING FROM THE DISPLAY RULE ABOVE. That rule is scoped to
 * `.has-display-font-size` and does not reach this preset. All four
 * properties a font-size preset cannot carry are restated here on purpose:
 * omitting any one of them renders statement type at the correct size in
 * Outfit at the wrong weight, which looks deliberate and is not.
 *
 * WHY A SECOND SIZE RATHER THAN A HIGHER DISPLAY CEILING. `display` is
 * shared, and the narrowest container that takes it governs what it can be.
 * `hero-split` sets its headline in a column around 530px wide, where 112px
 * breaks. The flush pattern's copy region is roughly twice that. Raising the
 * shared ceiling would have broken the contained case to serve the flush one.
 *
 * LEADING AND TRACKING ARE DERIVED VALUES and are NOT transferable from the
 * display rule. Both were derived against the 129.1px ceiling with the exact
 * Fraunces instance this theme ships. Moving this ceiling re-derives both of
 * these in the same commit — see "Derived values move with what they were
 * derived from" in the fleet standard.
 *
 * LEADING RE-DERIVED 1.05 → 0.99 AT 4.36.0, against the new 129.1px ceiling.
 * The design file carries the reasoning; this rule carries the value.
 *
 * The collision floor was re-measured 2026-09-06 by shaping the shipped instance
 * with HarfBuzz across the four committed headlines rather than by simulating a
 * wrap: the tallest glyph reaches +0.737em and the deepest -0.2455em, so ink
 * from consecutive lines meets below 0.9825. Three of the four floor there
 * exactly. 0.92, rejected at 4.34.0, was a collision by this same measure.
 *
 * 1.05 WAS PARITY WITH THE DISPLAY RULE ABOVE, not a margin picked off the
 * collision point — that rule runs 1.0 against Outfit's 0.925 threshold, and
 * 1.05 reproduced the same proportional clearance here. Reproducing a
 * neighbour's proportion is a tidiness argument, and at 4.36.0 that slack is
 * spent deliberately to hold the block together at a taller ceiling.
 *
 * 0.98 WAS CONSIDERED AND REJECTED: it sits 0.0025em BELOW the measured floor.
 * Sub-pixel, about 0.35px at the ceiling, and still a value known to be under
 * the floor. 0.99 clears by 0.0075em, about 1.05px.
 *
 * Known edge, AND ITS CHARACTER CHANGED WITH THIS RULING: the font-wide
 * threshold is 1.151, driven by accented capitals. At 1.05 an accented capital
 * TOUCHED the line above. At 0.99 it OVERLAPS it, by 0.16em — about 23px at the
 * ceiling. Still recorded rather than solved, on an English-language site, but
 * re-read this rather than re-assuming it before any French copy.
 *
 * Weight binds the token rather than a literal: 400 is weight.normal, and the
 * face holds at 400 only because it ships at SOFT 50. See the axis tuple in
 * the design file's fontFamilies entry.
 *
 * Specificity matches the display rule and for the same reason: `:root` takes
 * the selector to 0,2,0 so it beats theme.json element styles at 0,1,0
 * whichever element carries the preset. The font-size is left alone;
 * WordPress ships that with !important and it is already correct.
 */
:root .has-statement-font-size {
  font-family: var(--wp--preset--font-family--fraunces);
  font-weight: var(--wp--custom--weight--normal);
  line-height: 0.99;
  letter-spacing: -0.01em;
}

/* ── Authored line breaks ────────────────────────────────────────────────
 * Added 2026-08-31. An editor places a line break at one viewport width
 * without placing it at the other: `u-br-m` breaks below bp-1 only, `u-br-d`
 * breaks at bp-1 and above. Headline lineation is a craft decision, not an
 * accident of viewport width — a headline that reads correctly at desktop
 * measure breaks badly at mobile measure and the reverse.
 *
 * The span wraps the first word of the intended new line and carries the
 * class. That is the legacy `astra-gravitystack` convention, retained
 * deliberately: a span around a word is the only vehicle the block
 * editor's rich-text layer preserves for a classed inline element, so it
 * is what survives ordinary editing. The theme's two format buttons
 * (assets/js/lineation-formats.js) apply the class; these rules are what
 * give it meaning, and the two ship together. Neither is useful alone.
 *
 * `::before` with `content: "\A"` and `white-space: pre` inserts exactly
 * one break immediately before the wrapped word. Deliberately not
 * `display: block`, which the legacy implementation used: block forces a
 * break on *both* sides of the span, and the unwanted closing break is
 * invisible until a headline has a break in a place nobody authored.
 *
 * There is no `u-br-t`. The responsive strategy admits two breakpoints and
 * explicitly rejected a middle tier. Between bp-1 and bp-2 the headline
 * measure is closer to desktop than to mobile, so `u-br-d` is usually
 * already correct there.
 *
 * TABLET PREVIEW DOES NOT SHOW TABLET BEHAVIOUR, and the original brief
 * for this feature claimed the opposite. WordPress's Tablet canvas is
 * nominally 780px; the scrollbar brings its layout width to 766, two
 * pixels below bp-1, so the editor's Tablet preview renders the MOBILE
 * break. The front end is correct — measured on one.gravitystack.dev at
 * 4.4.0 the threshold flips between 765 and 771, so a physical tablet at
 * 768 or above gets the desktop break. bp-1 does not move for a two-pixel
 * preview artefact. Do not judge tablet lineation in the editor preview;
 * use a real browser at width.
 *
 * These are the first `@media` conditions in this file. The literal is
 * permitted under the raw-value exception in `gravitysite-css-standards.md`
 * — canonical breakpoint values inside a media condition, paired with a
 * token-name comment — which exists because custom properties genuinely do
 * not resolve in media conditions.
 *
 * The break renders in the editor canvas as well as on the front end,
 * because this file is registered with `add_editor_style()`. That is
 * wanted: device preview then shows real lineation rather than a
 * one-width approximation. Do not suppress it.
 *
 * ── PAIRED FILE: assets/css/editor.css ──────────────────────────────────
 *
 * The declarations below are canonical: they decide where breaks fall for
 * visitors. They are NOT the only place the break is declared.
 * `assets/css/editor.css` re-declares it inside its authoring-mark string,
 * because the mark has to render after the newline to lead the new line
 * rather than trail the old one, and a pseudo-element carries exactly one
 * `content` value. In the canvas, editor.css loads last and wins.
 *
 * This is a coupled pair, ruled in gravitysite-css-standards.md v1.4,
 * which also records what was tried first and why it failed. The rule that
 * keeps the pair honest: **a change to the break declarations below is not
 * complete until editor.css changes in the same commit.** If the two
 * diverge, the canvas lies to whoever is authoring, which is worse than
 * showing no mark at all.
 */

/* Both classes break the same way; only the width at which they do differs.
 * `white-space: pre` is set on the pseudo-element rather than the span so
 * the newline survives the parent's own white-space handling.
 */
.u-br-m::before,
.u-br-d::before {
  white-space: pre;
}

/* Mobile break: on at base, off from bp-1 up. */
.u-br-m::before {
  content: "\A";
}

/* Desktop break: off at base, on from bp-1 up. */
.u-br-d::before {
  content: none;
}

@media (min-width: 768px) { /* bp-1 */
  .u-br-m::before {
    content: none;
  }

  .u-br-d::before {
    content: "\A";
  }
}

/* ── Swoosh underline ────────────────────────────────────────────────────
 * Added 2026-10-20 at 6.6.0 (GravityBrain design session for the GravityONE
 * mechanism section; Design Team sitting). A hand-drawn stroke under one word
 * or a short phrase, applied by the "Swoosh underline" rich-text format in
 * assets/js/swoosh-format.js. The span keeps its text live: it wraps, zooms,
 * and reads to a screen reader as the words alone, and the stroke is
 * decoration painted on top. The two files ship together and neither is
 * useful alone.
 *
 * THE SHAPE AND THE COLOUR ARE THE THEME'S, AND A CLIENT THEME REPLACES THEM.
 * Both are read through custom properties with GravityStack's values as the
 * fallbacks, so a client theme sets `--c-swoosh-shape` (a mask image, any
 * stroke drawn in black on transparent) and `--c-swoosh-colour` (a palette
 * role) on `:root` and every swoosh on the site takes them, with no edit here.
 * Ruled by the Design Team the same day: the mark is a design asset the theme
 * ships, never a GravityStack flourish stamped on a client's site
 * (`gravitysite-design-system-standards.md`, *Design Team review checklist*,
 * "An exhibit's imagery is content, and its marks are the theme's").
 *
 * ONE MARK PER SECTION. The editor guide carries the rule and nothing here
 * enforces it, because CSS cannot count across a section and a format cannot
 * see its siblings. A stroke under every heading is emphasis spent.
 *
 * `white-space: nowrap` keeps the marked words on one line, so the stroke is
 * never drawn across a wrap. That is why the format is for a word or a short
 * phrase: a long run that cannot wrap pushes past a phone's measure.
 *
 * The stroke is decoration on text that already carries the meaning, so no
 * non-text contrast bar reaches it. Under forced colours it draws in the
 * system text colour rather than disappearing, so the emphasis survives.
 *
 * The geometry is in `em`, so the stroke scales with whatever size the word
 * is set at. It sits below the baseline inside the descender space and the
 * line gap. The values are carried from the GravityONE canvas mock
 * (2026-10-20), drawn there at 30 and 40px headings, and are not yet measured
 * in this stylesheet: the `text-split` pull request's preview is where they
 * are confirmed against the next line's cap height on a wrapped heading.
 */
.u-swoosh {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}

.u-swoosh::after {
  content: "";
  position: absolute;
  inset-inline: -6%;
  inset-block-end: -0.4em;
  block-size: 0.5em;
  background-color: var(--c-swoosh-colour, var(--wp--preset--color--gravity-brand));
  -webkit-mask: var(--c-swoosh-shape, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 22' preserveAspectRatio='none'%3E%3Cpath d='M3 12 C 32 6, 76 4, 108 6 C 113 6, 117 5.5, 120 4.5' fill='none' stroke='%23000' stroke-width='5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")) center / 100% 100% no-repeat;
  mask: var(--c-swoosh-shape, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 22' preserveAspectRatio='none'%3E%3Cpath d='M3 12 C 32 6, 76 4, 108 6 C 113 6, 117 5.5, 120 4.5' fill='none' stroke='%23000' stroke-width='5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")) center / 100% 100% no-repeat;
  pointer-events: none;
}

@media (forced-colors: active) {
  .u-swoosh::after {
    background-color: CanvasText;
  }
}

/* ── Stretched link ──────────────────────────────────────────────────────
 * One link is made to cover its whole positioned ancestor, so a card or an
 * index row is clickable across its full box without a second anchor around
 * the same destination — which is what a screen reader would otherwise have
 * to read past.
 *
 * Fleet-level rather than per-component: the Gravity Hacks index is the first
 * consumer and a card grid is the obvious second, and the mechanism is
 * identical in both. `gravitysite-css-standards.md`, "Files" — a rule wanted
 * on the next client site belongs here.
 *
 * THE CLASS GOES ON THE ELEMENT WRAPPING THE LINK, NOT ON THE LINK. That is
 * core's constraint rather than a preference: `core/post-title` puts its
 * `className` on the heading and gives the anchor inside it no hook at all.
 *
 * THE HOST MUST CARRY `position: relative`, AND THIS FILE DOES NOT SET IT.
 * The host is whichever box the component means the link to fill — the row,
 * the card — and only the component knows which ancestor that is. Setting it
 * here would have to guess, and guessing wrong fills the wrong box, quietly.
 * If a stretched link covers the page instead of its row, the missing
 * `position: relative` on the intended host is the first thing to check.
 *
 * NO G183 ROLE. Rule 1's heading exception is what leaves these titles
 * unlined, because they are `<h3><a>`; this utility only stretches. That
 * separation is deliberate and was ruled on 2026-09-16 after the reverse was
 * drafted: an accessibility exception keyed on this class would have had no
 * consumer the heading exception did not already cover. Do not add a
 * `text-decoration` declaration here — if a stretched link is underlined and
 * should not be, the question is whether it is a heading, and the answer
 * belongs at rule 1.
 *
 * A sibling link inside the same host needs `position: relative` of its own
 * to stay clickable above the stretched box. Nothing in this estate does that
 * yet; recorded because it is the first thing the next consumer hits.
 */
.u-stretched-link a::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* ── Footer ──────────────────────────────────────────────────────────────
 * The site footer in `parts/footer.html` is one object toned by one band.
 * Ruled 2026-10-25 by Scott on the Design Team's review of the Brand Preview
 * page. Until 0.3.4 the year line and the legal links sat in a separate
 * strip below the dark band, filled with `gravity-surface-subtle` picked off
 * the palette. That bypassed the band system (*Section bands* in the design
 * system standard: bands are the only sanctioned way to tone a section), it
 * ended every page on three surfaces, light ask, dark footer, light strip,
 * so the dark read as a block dropped in rather than the page's ground, and
 * a child switching its footer to a light band would have got a tint under a
 * tint. Now the base row sits inside the footer's band and a child changes
 * the whole footer by changing one style. Its text carries no colour of its
 * own, so the band reaches it; it stays quieter through size (`text-2`)
 * rather than a muted colour, per *A band governs inherited colour, not
 * explicitly-set colour*.
 *
 * THE DIVIDER TRACKS THE BAND, AT 40%, NOT THE ACCORDION'S 20%. Same
 * mechanism as the Ruled accordion edge above (*An edge on the band tracks
 * the band* in the constants file): `currentColor` mixed toward transparent,
 * so one declaration draws a light line on a dark band and a dark line on a
 * light one. The share differs because the jobs differ. The accordion edge
 * is a quiet card-like hairline; this one is a divider, and the Design Team
 * checklist holds dividers to 3:1 (*A solid is not a divider colour*). On the
 * starter's band, white text on `#333`, 40% composites to about `#858585`,
 * 3.4:1, where 20% would give 1.9:1. No palette role exists for an inverse
 * border, so the divider is derived rather than a token.
 *
 * THE MENU HOLDS THE RIGHT EDGE ON WIDE SCREENS ONLY. The column is pushed
 * to the far edge, the list inside stays left-aligned, so the footer has no
 * dead zone in the middle and the business details and the menu frame the
 * width between them. 782px is core's own column-stacking point, below
 * which the column stacks under the business details and sits at the left.
 */
.c-footer__base {
  border-block-start: 1px solid color-mix(in srgb, currentColor 40%, transparent);
}

@media (min-width: 782px) {
  .c-footer__links {
    display: flex;
    justify-content: flex-end;
  }
}

/* ── 2. Header ───────────────────────────────────────────────────────────
 * Fleet component classes for the header template part. The composition,
 * tokens, and craft notes are governed by
 * `gravitysite-header-standards.md`; GravityStack's expression of them is
 * `website-rebuild-header-spec.md`. Nothing token-shaped is authored here —
 * every colour, size, radius, and step below resolves through theme.json.
 *
 * These are in base.css rather than site.css because every rule in the
 * section is wanted on the next fleet site: the two-line button and the
 * sticky stance are both sanctioned entries in the fleet enhancement
 * register, and the wayfinding hover and the phone-width withdrawal are
 * properties of this stack rather than of one client's taste. The number of
 * rules is deliberately not stated here — a stale count in a section header
 * is a fault this file has logged once already.
 */

/* 2a. The header's logo width, as a single source.
 *
 * `--c-header-logo-width` sizes the image. It is a custom property rather
 * than a value on the rule so that anything else needing the rendered logo
 * width reads it here instead of restating it.
 *
 * It fed a second consumer until 4.7.0: the descriptor's optical indent,
 * set at 0.176 of the rendered width, measured off the lockup. The
 * descriptor left the header at 4.7.0 and that rule went with it. The
 * measuring method and the ratio's fragility — a recropped lockup makes it
 * silently wrong — are recorded in `gravitysite-header-standards.md`
 * section 7, for the fleet case of a header that does set a descriptor
 * beside its identity. They are not restated here, because this theme no
 * longer has the rule they govern.
 */
.c-header {
  --c-header-logo-width: 200px;
}

.c-header__logo img {
  width: var(--c-header-logo-width);
  height: auto;
}

/* 2a-ii. The bar's end group holds the right edge, logo or no logo.
 *
 * The bar spreads its two children with `justify-content: space-between`.
 * `core/site-logo` renders nothing at all when no logo is set, so the bar
 * is left with one child, and space-between puts a lone child at the start:
 * the nav and the action slide to the left edge. Seen on the starter site,
 * 2026-10-25, before it had a logo.
 *
 * An auto start margin on the end group pins it right whatever the bar
 * holds, and changes nothing when the logo is present, because the free
 * space it absorbs is the same space space-between was distributing.
 *
 * `.c-header__bar >` rather than the class alone, for (0,2,0): core's flex
 * layout zeroes child margins at (0,1,0) or (0,1,1) depending on version.
 */
.c-header__bar > .c-header__end {
  margin-inline-start: auto;
}

/* 2b. Two-line action label.
 *
 * One of the two fleet enhancements this site draws. The second line sits
 * inside the button rather than beneath it, so the whole label is one
 * click target and one accessible name.
 *
 * `text-1` is the design system's smallest step and the accessibility
 * floor the spec names — the meta line sits ON it, not below it. The
 * distinction between the two lines is carried by weight and size only.
 * Deliberately NOT by opacity: the label is white on `gravity-cta` violet,
 * and dimming it would trade a contrast ratio for a visual nicety on the
 * one element in the header that has to read as clickable.
 */
.c-action--two-line .wp-block-button__link {
  line-height: 1.25;
  text-align: center;
}

.c-action__meta {
  display: block;
  font-size: var(--wp--preset--font-size--text-1);
  font-weight: 400;
}

/* 2c. A two-line button sits low against a single-line row.
 *
 * Flex `verticalAlignment: center` centres the button's box, but the eye
 * reads the FIRST line as the button's line, so a two-line button appears
 * to hang below the nav baseline. The craft note in the header spec is
 * explicit that the fix is to shift the whole button up rather than to add
 * padding, which would grow the button instead of moving it.
 *
 * RAW VALUE, and the one declaration in this block that has not been
 * verified against a rendered page. It is roughly half the meta line's
 * leading. Check it on first deploy and adjust; it is cosmetic and fails
 * visibly rather than silently.
 */
.c-action--two-line {
  margin-block-start: -0.3rem;
}

/* 2d. Wayfinding hover inside the header.
 *
 * RESTORED at 4.7.1. This rule shipped at 4.5.1 and was deleted at 4.7.0
 * alongside the descriptor rules it happened to sit beside in the file. The
 * deletion was collateral, not a decision — the 4.7.0 entry does not mention
 * it, and header nav links had no hover response on the live site until this
 * restore. The reasoning below is 4.5.1's, unchanged.
 *
 * `core/navigation` sets `color: inherit` on its link content with the class
 * written twice — (0,3,0), and deliberately so, to stop exactly this kind of
 * override. theme.json's link hover cannot reach it. Authored here with
 * (0,3,1) so it wins by one pseudo-class rather than by a specificity war,
 * and scoped to `.c-header` so it cannot reach the dark band, whose nav
 * hover is ruled separately in rule 1b and takes the brand hue.
 *
 * `action-11` rather than `gravity-brand`, because brand measures 2.16:1 on
 * white and is unusable here. This is the same hue the rest of the site
 * already uses for a link hover, so the header is not inventing a
 * convention.
 *
 * The selector survives pass two. Under `submenuVisibility: click` the two
 * menu parents render as `<button class="wp-block-navigation-item__content
 * wp-block-navigation-submenu__toggle">` rather than as anchors, and this
 * rule matches on the class rather than on the element, so both forms are
 * covered by one declaration.
 *
 * FOCUS IS NOT AUTHORED HERE, and that is verified rather than assumed.
 * theme.json's `elements.link` focus ring is an `outline`, which core's
 * `color: inherit` never competes with, so it lands on nav anchors today —
 * confirmed on the rendered page as
 * `:root :where(a:where(:not(.wp-element-button)):focus-visible)`. It will
 * NOT reach the two `<button>` parents pass two introduces, which fall back
 * to the generic agent ring. That is a pass-two obligation, recorded here
 * because this is the rule a reader will check first.
 *
 * NOTED, not resolved: this walks contrast DOWN — gravity-text on white is
 * roughly 16:1 at rest, action-11 about 10.4:1 on hover. Both sit far above
 * AA, but the direction is the open fleet question named in rule 1b, and
 * this is a third surface touching it. It is not a new fault; it is the
 * same one, and it should be settled at the fleet level rather than per rule.
 */
.c-header .wp-block-navigation-item__content.wp-block-navigation-item__content:hover {
  color: var(--wp--preset--color--action-11);
}

/* 2e. The header action is withdrawn below the overlay threshold.
 *
 * At phone width the two-line button is the widest object in the header
 * row — wider than the lockup — and the row reads as a button with a logo
 * attached rather than an identity with an action beside it. That inverts
 * job 1 of `gravitysite-header-standards.md`, which is proving this is a
 * real practice in about one second.
 *
 * WITHDRAWN, NOT DELETED, and the distinction is the standard's. Job 3 asks
 * that the primary action be findable, not omnipresent. On mobile the hero
 * carries its own CTA on the first screen, so the header's copy is a
 * duplicate charging header height on every page for a second instance of
 * something already present above the fold. What this does NOT do is give
 * the mobile visitor a route back to the action once she has scrolled past
 * the hero. That gap is real and is the open item below.
 *
 * 600px, NOT bp-1. This is core's `overlayMenu: mobile` threshold — the
 * same number rule 4a is scoped to, under the same deviation note, and for
 * the same reason: it is core's number, not a layout decision of ours. The
 * action leaves the row at exactly the width where the nav collapses into
 * the hamburger, so the header has two arrangements rather than three.
 * Hiding at bp-1 instead would open a 600–767px band carrying an inline
 * four-item nav and no action, which is an arrangement nobody designed.
 *
 * `max-width` rather than the file's mobile-first pattern, deliberately.
 * The mobile-first form would need `display: flex` restored above 600px,
 * which restates a value core sets on `.wp-block-buttons.is-layout-flex` at
 * (0,2,0) — equal specificity to this selector, so which one wins depends
 * on stylesheet order rather than on anything authored. A `max-width` query
 * has nothing to restore and no contest to enter. The `.98` guard keeps a
 * fractional viewport from landing between the two conditions.
 *
 * CSS rather than markup, and that is load-bearing. `parts/header.html` is
 * byte-identical fleet-wide by design — every per-site difference is
 * expressed in `inc/header-config.php` — so an edit here would be a fleet
 * markup change made for one site's phone layout. And per the scope note in
 * `inc/header-runtime.php`, a site whose template part has been edited in
 * the Site Editor is running a database copy no file change reaches. A
 * stylesheet rule reaches the rendered page either way.
 *
 * OPEN: where the mobile action actually lives. The honest answer is inside
 * the overlay menu, which means moving the button inside the navigation
 * block — a fleet markup change, on a surface the header standard's scope
 * note declares deliberately undesigned. Not taken here.
 */
@media (max-width: 599.98px), (max-height: 499.98px) { /* core's overlayMenu threshold, or a short viewport — see 4a and 4g */
  .c-header .wp-block-buttons:not(.wp-block-navigation__overlay-container *) {
    display: none;
  }
}

/* 2e-ii. WHY 2e CARRIES A `:not()`, added when the overlay was built.
 *
 * 2e was authored at 4.23.1, when the only `wp-block-buttons` inside
 * `.c-header` was the desktop action sitting in the header ROW. Its ruling is
 * that the row carries no action below the nav's collapse threshold. Written
 * as a plain descendant selector, it also matched every button that would ever
 * be placed anywhere inside the header subtree.
 *
 * The overlay part puts the action back — as the first object inside the
 * overlay, which is the whole point of relocating it — and that button is a
 * descendant of `.c-header`. So 2e hid it: the action rendered with its fill
 * and its label intact and was never painted. **Found on the render as a
 * bounded empty box, not by reading either rule.** The served HTML showed the
 * `<a>` present with its `href` and both label spans, which is what ruled out
 * a malformed block comment and a lost fill and left only "something is
 * hiding it".
 *
 * The exclusion preserves 2e's ruling exactly — the header row still carries
 * no action on mobile — and states the boundary the original selector left
 * implicit. It is scoped by core's own overlay-container class rather than by
 * any client class, so it holds for any fleet site that relocates its action
 * into a custom overlay.
 */

/* 2f. The sticky stance.
 *
 * The CSS half of `assets/js/header-stance.js`, which implements section 3 of
 * `gravitysite-header-standards.md`. That file carries the reasoning for the
 * mechanism; this comment carries what a CSS reader needs.
 *
 * KEYED ON `has-header-stance`, WHICH ONLY THE MODULE ADDS. Not on the
 * `is-header-stance-*` body class the config layer emits, and the difference is
 * load-bearing. The scroll-margin below is sized from `--c-header-height`,
 * which only the module can measure, so a stance applied without the module
 * running would pin a header over every in-page target it scrolls to. Failing
 * closed to static is the honest degradation; a sticky header with no
 * scroll-margin is an accessibility-floor breach. It also keeps the runtime
 * state off the `<body>` namespace section 10 reserves for configuration.
 *
 * THE STICKY ELEMENT IS THE SHELL, NOT `.c-header`. `.c-header` is confined to
 * the template-part wrapper, whose height is the header's own height, so it has
 * no travel and would silently never stick — measured 64px against 64px on
 * 2026-09-02. `.c-header` also has to stay `position: relative`, because rule
 * 4a makes it the offset parent the full-bleed mega panel resolves against; the
 * panel's geometry was captured with the shell sticky and static and is
 * identical.
 *
 * THE HEADER STAYS IN FLOW, which is how the reserved height that section 6
 * names as a build requirement is satisfied. There is no spacer and no body
 * padding to keep in sync, and `scrollHeight` is unchanged at rest, while
 * stuck, and while withdrawn.
 *
 * NO `transform` IN THE REST STATE, and this is not a tidiness preference. A
 * `translateY(0)` still makes the shell the containing block for a
 * `position: fixed` descendant, and core's overlay menu is exactly that — a
 * fixed, fullscreen descendant of this element. The rest state must resolve to
 * `transform: none`, so the withdrawn state is the only rule that sets one. The
 * module refuses to withdraw while a menu is open for the same reason, which
 * leaves only the transition's own duration as a window where the containing
 * block moves.
 *
 * RAW `z-index`, and it is measured rather than picked. Nothing else in the
 * page creates a stacking context today — every positioned, transformed,
 * filtered, or non-opaque element outside the header was enumerated on
 * 2026-09-02 and the count was zero — so the header would paint correctly with
 * `z-index: auto`. The value exists so that a pattern added later cannot paint
 * over a stuck header, and it is kept low so it does not start a war with a
 * plugin. `z-index` has no token category in the design system; the file's
 * existing `z-index: -1` sets the precedent for stating the value with its
 * reason.
 *
 * RAW DURATION. 180ms, under section 7's 200ms bar, on a transform so the
 * header arrives from behind rather than fading in. Durations are not tokens
 * and resolve to nothing in the design file; `assets/css/organisms/` already states
 * its own.
 *
 * THE ADMIN BAR IS A REAL CASE, not a nicety: a logged-in editor reviewing the
 * site is who checks a header, and `top: 0` puts the stuck header under the
 * bar. `--wp-admin--admin-bar--height` is core's own property, scoped to
 * `.admin-bar` so a site rendering no bar cannot inherit a 32px offset.
 *
 * THE HAIRLINE IS PART OF THIS RULE, and the edge below is why the module owns
 * it rather than the header's design pass. See the raised-edge rule at the end
 * of this section.
 */
:root.has-header-stance .c-header-shell {
  position: sticky;
  top: 0;
  z-index: 10; /* see the measured note above */
  transition: transform 180ms ease-out;
}

:root.has-header-stance .admin-bar .c-header-shell {
  top: var(--wp-admin--admin-bar--height, 0px);
}

:root.has-header-stance.is-header-hidden .c-header-shell {
  transform: translateY(-100%);
}

@media (prefers-reduced-motion: reduce) {
  /*
   * The behaviour is kept and only the interpolation is dropped, so the header
   * changes state in one step. Collapsing `reveal` to always-on sticky was the
   * alternative and it removes more than the motion: the reason for reveal is
   * that a header should not overlay a page while it is being read, and that
   * reason does not weaken for a visitor who has asked for less movement.
   */
  :root.has-header-stance .c-header-shell {
    transition: none;
  }
}

/* The hold offset. One value for anything a pattern holds in view while the
 * page scrolls: the header's height while the header is showing, zero while it
 * is tucked away, and zero on a site with no stance module at all, where the
 * header does not stick. A held element reads it as its `top`, so every one of
 * them steps under the header the same way and a header change is fixed here
 * once. The admin bar is added the way the header shell adds it. First reader:
 * `gravitystack/price-split` (Copy-to-Site Decision 95, 2026-10-13). */
:root {
  --c-hold-top: 0px;
}
:root.has-header-stance:not(.is-header-hidden) {
  --c-hold-top: var(--c-header-height, 0px);
}
.admin-bar {
  --c-hold-admin: var(--wp-admin--admin-bar--height, 0px);
}

/* In-page targets clear the header.
 *
 * Section 5, and it covers `reveal` as well as `sticky` — a returning header
 * lands on a focused target the same way an always-on one does. Sized from the
 * module's live measurement rather than from a number, because the header's
 * height is not one: it is 64px on a phone and 102px at desktop on this site
 * alone, and a client whose header is taller changes it again.
 *
 * FOCUSABLES AS WELL AS `[id]`. The floor names in-page targets, which is the
 * anchor case, but focus-driven scrolling has the identical failure and no
 * anchor. `scroll-margin` is inert until something scrolls to the element, so
 * the breadth costs nothing. `:where()` keeps the whole rule at (0,1,0) so any
 * authored scroll-margin outranks it.
 */
:root.has-header-stance :where([id], a, button, summary, input, select, textarea) {
  scroll-margin-block-start: calc(var(--c-header-height, 0px) + var(--wp--preset--spacing--30));
}

/* The raised edge — no rule at rest, a 3:1 rule when the header sits above
 * scrolled content.
 *
 * This is section 7's second option taken literally, and it is here because
 * SECTION 5 MAKES IT NON-OPTIONAL ONCE A STANCE EXISTS. Section 5 tests a
 * container's outer edge rather than categorising it: an edge that is the sole
 * thing separating a surface from what sits behind it meets 3:1, while an edge
 * refining a separation already carried by fill, shadow, or spacing is
 * decoration and exempt. At rest the header's edge is the exempt case — spacing
 * carries the separation. Stuck over scrolled content it becomes the sole
 * separator between two `gravity-surface` `#ffffff` surfaces, and there the 3:1
 * bar binds.
 *
 * `parts/header.html` carried a `border-bottom` block attribute in
 * `gravity-border` `#d7dad7` — 1.41:1 on white. Solid and present at rest,
 * where section 7 asks for low-alpha or state-dependent, and failing the floor
 * the moment the stance put the header over content. The attribute is deleted
 * fleet-wide at 4.24.1: it was a standing section 7 deviation on every site
 * that has ever run this part, and deleting it is correct whether or not that
 * site runs a stance module. A `static` site now carries no header rule at all,
 * which is what section 7 asks for on a header that never sits above content.
 *
 * WHY THE PRIMITIVE SCALE CHANNEL RATHER THAN A PALETTE TOKEN, because the next
 * person meeting a 3:1 edge with no palette entry will otherwise re-derive the
 * wrong answer. The palette contract carries nothing usable between
 * `gravity-border` at 1.41:1 and `gravity-text-muted` at 5.96:1 —
 * `gravity-brand` fails the bar at 2.28:1 and `brand-11` clears it at 4.81:1
 * but is green and well past a hairline. The palette's audience is an author
 * choosing a colour in the editor's picker; this edge is published by
 * JavaScript and authored in CSS, so no author ever picks it. By the test in
 * `gravitysite-design-system-constants.md` under *The primitive scale channel*
 * — which rejects widening the palette by name, on the ground that the
 * diagnosis is not that the palette is too small but that CSS has been shopping
 * in the picker — this is the channel's case. `neutral-9` `#898e87` is the
 * first ramp step that clears the bar at 3.34:1; `neutral-8` `#b9bcb8` falls
 * short at 1.92:1. Same channel and same shape as rule 1e's `action-3` fill and
 * the menu file's `neutral-6` cell border.
 *
 * The variable name is read back off the rendered global styles rather than
 * predicted, per that section's own emission rule about keys containing digits:
 * confirmed on 2026-09-02 as `--wp--custom--color--neutral-9`, resolving to
 * `#898e87` on the deployed theme.
 *
 * ONE PIXEL IS RESERVED AT REST RATHER THAN ADDED WHEN RAISED. A border
 * appearing on a state change would grow the header by 1px at the moment it
 * flips — a layout shift, and a change to `--c-header-height` that would move
 * every scroll-margin with it, which is exactly the guarantee this section
 * exists to keep. So the box always carries the pixel and only the colour
 * changes. `transparent` is a keyword rather than a token because there is no
 * token for absence, and the alternative is the shift.
 *
 * NOT TRANSITIONED, deliberately. `is-header-raised` flips within a pixel of
 * the header's resting place, so there is nothing to smooth, and a colour
 * transition here would add a second motion surface needing its own
 * reduced-motion path for no gain.
 */
:root.has-header-stance .c-header {
  border-block-end: 1px solid transparent;
}

:root.has-header-stance.is-header-raised .c-header {
  border-block-end-color: var(--wp--custom--color--neutral-9); /* 3.34:1 on gravity-surface, against a 3:1 bar */
}

/* 2g. The navigation toggle's icon-plus-label composition.
 *
 * FLEET, KEYED ON A PUBLISHED CONTRACT. `is-header-toggle-icon-label` is
 * emitted on `<body>` by inc/header-runtime.php from the `toggle` key in
 * inc/header-config.php, following section 10 of
 * gravitysite-header-standards.md. Any fleet site selecting that value wants
 * these rules, so they are here rather than in an organism file or in
 * site.css — the organism is the overlay's contents, and this is the control
 * that opens it.
 *
 * THE SPAN IS INJECTED, and inc/header-toggle-label.php explains why: core's
 * `hasIcon` is one boolean giving icon OR label and cannot express both.
 * That file also carries the WCAG 2.5.3 assertion that keeps the pairing
 * honest.
 *
 * NO WIDTH QUERY, and that is worth stating because every other header rule
 * here has one. Core hides this control itself above 600px —
 * `.wp-block-navigation__responsive-container-open:not(.always-shown) {
 * display: none }` inside its own `min-width: 600px` query — so these
 * declarations only ever paint where the control exists. Scoping them again
 * would have made this the fourth rule in the file at 600px, which the
 * website rebuild build index names as the point where 600px becomes a de
 * facto breakpoint no standard names. It is still the third; see
 * assets/css/organisms/menu-overlay.css.
 *
 * FONT SIZE GOES ON THE SPAN, not on the control. Core sets `font-family`,
 * `font-weight`, and `font-size` to `inherit` on the control at (0,3,0) — a
 * tripled class — so a declaration here would lose and the fix would be
 * specificity escalation to solve what is not a load-order problem. The span
 * is ours and nothing competes for it.
 *
 * The target is enlarged, never the glyph: core's svg is 24px and is sized
 * to the rest of the system. The negative inline-end margin pulls the
 * enlarged box back to the row's optical edge so the control does not appear
 * inset against the identity opposite it.
 */
.is-header-toggle-icon-label .wp-block-navigation__responsive-container-open {
  align-items: center;
  gap: var(--wp--preset--spacing--20);
  min-block-size: 44px;
  min-inline-size: 44px;
  padding-inline: var(--wp--preset--spacing--20);
  margin-inline-end: calc(var(--wp--preset--spacing--20) * -1);
  border-radius: var(--wp--custom--rounded--sm);
  color: var(--wp--preset--color--gravity-text);
}

.c-header__toggle-label {
  font-size: var(--wp--preset--font-size--text-2);
  font-weight: var(--wp--custom--weight--strong);
  line-height: 1;
}

/* ── 3. Buttons inside a navigation block ────────────────────────────────
 * The one fleet-constant rule that came out of the GravityStack mega menu
 * build. It is here and not in site.css because the collision it fixes is
 * in the STACK, not in that menu: any GravitySite putting a button inside a
 * navigation block meets it. The menu was simply the first surface to put
 * one there.
 */

/* Buttons inside a navigation block keep their own hover label.
 *
 * FLEET, NOT THIS MENU. Spectra Blocks emits
 * `:where(.wp-block-navigation a):hover { color: var(--spectra-nav-link-hover) }`.
 * `:where()` contributes no specificity, so that rule is (0,1,0) — exactly
 * theme.json's own `:root :where(.wp-element-button:hover,
 * .wp-block-button__link:hover)`. A tie, decided by load order, and Spectra
 * loads after global styles. On this panel it painted the label `action-9`
 * on the `action-10` hover fill: **1.21:1, an SC 1.4.3 failure on a primary
 * call to action.** White on `action-10` is 8.83:1.
 *
 * theme.json cannot win this, structurally — `:where()` is what makes global
 * styles overridable, so the fix has to be authored CSS. Design Team ruled
 * it fleet on 2026-08-31: every fleet site that puts a button inside a
 * navigation block meets it, and a mega menu with an entry-point anchor puts
 * one there by design. **Scoped to the pattern, not to this client's
 * classes** — an earlier version keyed on `.c-header .c-mega__anchor-btn`,
 * which was a fleet defect wearing a client selector.
 *
 * Found by forcing the hover state in a browser and listing matched rules.
 * It is invisible from either stylesheet alone; each is correct in isolation.
 */
.wp-block-navigation .wp-element-button:hover,
.wp-block-navigation .wp-block-button__link:hover {
  color: var(--wp--preset--color--gravity-cta-label);
  background-color: var(--wp--preset--color--action-10);
}

/* ── 4. Menu organism substrate ──────────────────────────────────────────
 * Ruled 2026-09-01 on the GravityStack website rebuild. Roughly a third of
 * the flagship mega menu's 580 lines was never design: it was the work of
 * beating core, the parent theme, and Spectra. Every mega menu on this stack
 * needs these rules whatever it looks like, so they are fleet and they are
 * here, while the design goes to `assets/css/organisms/[entry].css`.
 *
 * THE TEST IS THE ONE 4.15.0 ALREADY USED. That release moved the menu out of
 * this file and deliberately left the Spectra hover collision fix behind — see
 * section 3 — on the grounds that the defect is in the stack and not in the
 * menu. Same test, four more rules.
 *
 * TEST THE DECLARATION, NOT THE COMMENT. This paragraph read, until 4.16.1:
 * "if the comment explaining a rule names a core, parent-theme, or Spectra
 * selector, it is substrate." That is wrong, and the paragraph below on the
 * band and anchor-card rules is the counter-example sitting ten lines away —
 * their comments name a parent-theme selector and they are not substrate. The
 * correct question is whether the declaration would still be needed if the
 * competing stylesheet vanished: a substrate declaration becomes unnecessary,
 * a design declaration still says what the design says. `gravitysite-css-
 * standards.md` "Files" carries the corrected test; this comment stated the
 * superseded one, which is the habit 4.14.1 and 4.14.3 both logged.
 *
 * SCOPE WITHIN THIS SECTION, because the name says "menu organism" and three
 * of the four rules are narrower than that. 4a, 4b, and 4c key on `.c-mega`
 * and `.c-mega__svc` — the BOUNDED PANEL's contract classes. 4d is generic to
 * any nav running `submenuVisibility: click`. A SECOND ORGANISM ADDS ITS OWN
 * SUBSECTION; it does not reuse 4a, which would hand a small dropdown the
 * full-page width — the failure 4a's own closing note describes. Naming a
 * contract class here also makes that name a fleet reservation: another
 * organism declaring `.c-mega` for something else would collide silently.
 *
 * WHY CLIENT-LOOKING CLASSES APPEAR IN THE FLEET STYLESHEET. `.c-mega` and
 * `.c-mega__svc` are not GravityStack's classes. They are the bounded-panel
 * organism's contract classes, declared by the mega-featured organism
 * (`assets/css/organisms/mega-featured.css`), and any site composing
 * that organism carries them. This section is keyed to the contract, not to a
 * client. A site whose header takes no mega menu simply matches nothing here,
 * which is already true of this file's card, question, and quote sections.
 *
 * WHAT THIS SECTION DOES NOT COVER. The band, strip, and anchor-card rules are
 * written at (0,3,0) and (0,4,0) purely to beat the padding reset in
 * `foundation.css`, `.wp-block-group.is-layout-flow.wp-block-group-is-layout-flow`
 * at (0,3,0), a rule carried over from Spectra One's stylesheet — but what they
 * declare is a shadow, a corner, and a padding value, which is one client's
 * design. The raise is forced by that reset; the declarations are not fleet.
 * They stay in the organism's file, with the selector named at the rules that
 * win it so the next editor knows why the selectors are doubled.
 *
 * This file is the parent's and no child carries a copy, so "authored once" is
 * the mechanism and not only the intent.
 */

/* 4a. A full-width panel needs its containing block moved, and that means
 * neutralising five ancestors rather than one.
 *
 * The panel is full-bleed, and 100vw is deliberately NOT used — it ignores the
 * scrollbar and produces horizontal overflow at exactly the widths a mega menu
 * is widest. Instead the offset parent is moved to `.c-header`, which is
 * already `alignfull`, so the panel resolves `left: 0; right: 0` against the
 * full page width.
 *
 * The real chain, read from the rendered DOM rather than inferred from core's
 * stylesheet:
 *
 *   .c-header
 *     nav.wp-block-navigation                          position: relative
 *       div.wp-block-navigation__responsive-container    relative @ ≥600px
 *         div.wp-block-navigation__responsive-close
 *           div.wp-block-navigation__responsive-dialog   position: relative
 *             ul.wp-block-navigation__container
 *               li.c-mega                                relative, twice over
 *                 ul.…__submenu-container                ← the panel
 *
 * This rule was authored three times before it was measured, and each attempt
 * neutralised the levels a reading of core's stylesheet had turned up rather
 * than the levels the DOM actually had. The panel got wider each time, which
 * reads as progress and was not — it was the same defect resolving against a
 * nearer box. THE CHEAP GUARD: when moving a containing block, enumerate every
 * positioned ancestor from the element up to the intended parent, from the
 * served stylesheet AND the rendered DOM, before overriding any of them.
 *
 * The panel placement rule doubles `.c-mega` to (0,6,0). Core's open-state
 * rule is (0,5,0):
 *   .wp-block-navigation .has-child
 *   .wp-block-navigation-submenu__toggle[aria-expanded="true"]
 *   ~ .wp-block-navigation__submenu-container                        (0,5,0)
 * The first authoring was (0,3,0), lost, and the panel shrink-wrapped to a
 * 200px column.
 *
 * SCOPED TO ≥600px AND TO THE CLOSED STATE, deliberately. Below that the
 * overlay menu takes over, where core positions the responsive container
 * `fixed` and renders submenus static and inline. Forcing these to static
 * unconditionally would win those contests too and break the overlay. The
 * `:not(.is-menu-open)` guards mirror core's own conditions rather than
 * inventing new ones.
 *
 * DEVIATION, STATED: 600px is not one of the two canonical breakpoints, so it
 * falls outside the raw-value exception in `gravitysite-css-standards.md`. It
 * is core's own overlay threshold under `overlayMenu: mobile`, not a layout
 * decision of ours, and pinning it to `bp-1` would put our static override on
 * a different line from the state it is guarding. Left as core's number and
 * flagged here rather than tokenised.
 *
 * CONSEQUENCE WORTH KNOWING, and it is why the About menu is a second organism
 * rather than a variant of this one: a submenu added later as an ordinary
 * dropdown will also resolve against `.c-header` and run full width, which is
 * wrong for a small dropdown. That case needs its containing block restored on
 * its own item.
 */
.c-header {
  position: relative;
}

@media (min-width: 600px) and (min-height: 500px) { /* core's overlayMenu threshold, and tall enough to hold the panel — see the note above and 4g */
  .c-header .wp-block-navigation,
  .c-header .wp-block-navigation__container,
  .c-header .c-mega.c-mega,
  .c-header .wp-block-navigation__responsive-container:not(.hidden-by-default):not(.is-menu-open),
  .c-header .wp-block-navigation__responsive-container:not(.is-menu-open) .wp-block-navigation__responsive-dialog {
    position: static;
  }

  .c-header .c-mega.c-mega .wp-block-navigation-submenu__toggle[aria-expanded="true"] ~ .wp-block-navigation__submenu-container {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
  }
}

/* 4b. A service cell's own content must STACK, and core says otherwise.
 *
 * `core/navigation-link` renders exactly one shape:
 *   <li><a><span class="…__label"></span><span class="…__description"></span></a></li>
 * with both spans INSIDE the anchor, which is what makes a label-plus-
 * description cell native. Core then sets that content box to `display: flex`,
 * which puts the label and the description side by side — the label wraps to
 * two lines in its own column while the description sits beside it, which is
 * what shipped at 4.8.3.
 *
 * Core selector this must beat:
 *   .wp-block-navigation .has-child .wp-block-navigation__submenu-container
 *   > .wp-block-navigation-item > .wp-block-navigation-item__content   (0,4,0)
 * This rule: .c-header .c-mega__svc.c-mega__svc.c-mega__svc
 *            .wp-block-navigation-item__content                        (0,5,0)
 *
 * THE ORGANISM'S FILE CARRIES A RULE WITH THIS EXACT SELECTOR, holding the
 * cell's padding, corner, and transition. Same count, and that file loads
 * after this one, so the two halves cannot disagree and the design half would
 * win if they ever did. Deliberate: this is one rule split by concern, not two
 * rules that drifted apart.
 */
.c-header .c-mega__svc.c-mega__svc.c-mega__svc .wp-block-navigation-item__content {
  display: block;
  flex-grow: 0;
}

/* 4c. The cells stretch, and core centres them.
 *
 * Grid stretches every cell in a row to the tallest, so a cell whose
 * description runs one line centres its content and its label drops below its
 * neighbours' — measured at 11px on the rendered panel, with one cell's label
 * at y=402 against 391 for the two beside it. Not a content problem:
 * descriptions differ in length on every client, so this recurs on every fleet
 * build that uses a grid of cells.
 *
 * `stretch` RATHER THAN `flex-start`, AND THE DIFFERENCE IS THE CELL'S BOX.
 * Both stop core centring, so both fix the label alignment this rule was
 * written for. But the `<li>` is what grid stretches, while the bordered box a
 * visitor sees is the anchor INSIDE it — `.wp-block-navigation-item__content`,
 * set to `display: block` at 4b. Under `flex-start` that anchor takes its
 * content height, so three cells in a row render three boxes of three
 * different heights inside three equal-height list items. Under `stretch` it
 * fills, and the boxes match. Changed 2026-09-02 after the bordered cell
 * shipped at 2026-09-01 and made the mismatch visible; before the border there
 * was no box to be uneven.
 *
 * `flex-grow: 0` at 4b does not fight this. That is the main axis, which is a
 * row here; this is the cross axis.
 *
 * FLEET CHANGE, and correct fleet-wide: any build using a grid of bordered
 * cells wants the boxes to match, and one that does not use a border cannot
 * see the difference.
 *
 * Core selector this must beat:
 *   .wp-block-navigation .wp-block-navigation-item                   (0,2,0)
 * This rule: .c-header .c-mega__svc.c-mega__svc                      (0,3,0)
 */
.c-header .c-mega__svc.c-mega__svc {
  align-items: stretch;
}

/* 4d. Focus reaches the submenu toggle, which theme.json structurally cannot.
 *
 * theme.json's focus ring is authored on `a:where(:not(.wp-element-button))`
 * and cannot reach the `<button>` that `submenuVisibility: click` produces for
 * a menu parent — the element is not an anchor, so no anchor selector will
 * ever match it. Every fleet site with a click-opened submenu has the same
 * hole, and section 2d records it as a pass-two obligation; this is it being
 * paid.
 *
 * There is no selector to beat here. It is in the substrate because the gap is
 * structural in the token layer rather than aesthetic, and because a menu
 * organism arriving without it would ship an accessibility-floor breach —
 * `gravitysite-css-standards.md`, Accessibility floor: a hover state with no
 * matching focus state is a defect.
 */
.c-header .wp-block-navigation-submenu__toggle:focus-visible {
  outline: 2px solid var(--wp--preset--color--gravity-focus);
  outline-offset: 3px;
}

/* 4e. A custom overlay needs a height chain, and core does not supply one.
 *
 * SUBSTRATE, NOT DESIGN. Every one of these declarations exists to make
 * core's wrapper stack pass height and direction down to the overlay part.
 * If core's stylesheet vanished none of them would be needed, which is the
 * test in gravitysite-css-standards.md that puts them here rather than in
 * assets/css/organisms/menu-overlay.css. Any fleet site running a
 * custom overlay meets this, whatever the overlay looks like.
 *
 * WHAT CORE GIVES. Opening the menu makes `.…__responsive-container` a
 * `fixed; inset: 0` flex column. That is where the full-screen box comes
 * from and it is correct.
 *
 * WHAT IT DOES NOT. The four wrappers between that box and the overlay
 * part — `__responsive-close`, `__responsive-dialog`,
 * `__responsive-container-content`, `__overlay-container` — carry no height
 * at all. So a part wanting a pinned bar with a scrolling tree beneath it
 * gets neither: `height: 100%` on its own root resolves against an auto
 * parent and computes to auto, and the whole panel scrolls the container
 * instead, taking the close control off screen with it.
 *
 * `__responsive-container-content` is additionally `display: flex` with its
 * direction read from `--navigation-layout-direction`, which is the DESKTOP
 * nav's layout — `row` on this header. Core rewrites that to `column` for
 * its own overlay and skips the rewrite when `disable-default-overlay` is
 * present, so a custom overlay inherits a row. `min-block-size: 0` on each
 * link is the usual flex-overflow release: without it a flex item refuses to
 * shrink below its content and the scroll region never scrolls.
 *
 * SCOPED BELOW 600px, AND IT HAS TO BE. Above core's `overlayMenu`
 * threshold the same container is `display: block; position: relative` and
 * IS the desktop menu — forcing these wrappers to full-height columns there
 * would break the header this build is not allowed to touch.
 *
 * ** FLAGGED: THIS IS THE FOURTH RULE IN THE THEME SCOPED TO 600px. **
 * Rules 2e and 4a already sit there, and
 * assets/css/organisms/menu-overlay.css is the third. The website
 * rebuild build index records that a third makes 600px a de facto
 * breakpoint no standard names; this is the fourth, and the debt is owed to
 * gravitysite-css-standards.md, whose two canonical breakpoints do not
 * include it. Raised rather than taken quietly. The value is core's own
 * threshold in every instance, not a layout decision of ours, which is the
 * argument for admitting it to the standard rather than removing it.
 */
@media (max-width: 599.98px), (max-height: 499.98px) { /* core's overlayMenu threshold, or a short viewport — see the flag above and 4g */
  .c-header .disable-default-overlay.is-menu-open .wp-block-navigation__responsive-close,
  .c-header .disable-default-overlay.is-menu-open .wp-block-navigation__responsive-dialog,
  .c-header .disable-default-overlay.is-menu-open .wp-block-navigation__responsive-container-content,
  .c-header .disable-default-overlay.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation__overlay-container {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-block-size: 0;
  }

  /* 4f. The nested navigation block must not shrink-wrap its list.
   *
   * A `core/navigation` used inside the overlay as a plain vertical list is
   * still laid out by core's flex layout: `is-vertical` plus
   * `is-content-justification-left` resolves to a column flex container with
   * `align-items: flex-start`, so the `<ul>` sizes to its widest label
   * instead of the panel. Measured at **72px** against a 375px viewport,
   * which is what put the row dividers at roughly 205px in the first
   * screenshot rather than spanning the panel.
   *
   * The fix is to stop the nav imposing a layout, not to force a width onto
   * the list — a `width: 100%` patch would leave the flex container in place
   * and would be sizing the symptom. Rule 4e above fixes the wrapper chain
   * from core's fixed box down to our part; this is the one level below it.
   *
   * Keyed on core's classes only, deliberately. Any fleet site putting a
   * navigation block inside a custom overlay meets this, and scoping it to a
   * client class would be a fleet defect wearing a client selector — the
   * mistake section 3 of this file records and corrects.
   */
  .wp-block-navigation__overlay-container .wp-block-navigation,
  .wp-block-navigation__overlay-container .wp-block-navigation__container {
    display: block;
  }
}

/* 4g. THE SHORT-VIEWPORT SWITCH. Core decides the menu's arrangement on
 * width alone; this makes viewport HEIGHT a second condition. It is the
 * authority for the 500px threshold and for the height axis itself — rules
 * 2e, 4a, 4e/4f and `assets/css/organisms/menu-overlay.css` all carry the
 * condition and none of them restate the reasoning.
 *
 * Added 2026-09-04 at 4.33.0.
 *
 * ── THE DEFECT, MEASURED ─────────────────────────────────────────────────
 * A landscape phone is wide enough for core to serve the desktop nav and
 * nowhere near tall enough for the panel that nav opens. On the live render
 * at 915x412:
 *
 *   viewport ....................... 412px tall
 *   header row ..................... 123.55px  (the two-line action is in it)
 *   slot left below the header ..... 288px
 *   mega panel ..................... 665.8px
 *   below the fold ................. 377px
 *
 * And it cannot be scrolled to. The header is `position: sticky` and the
 * panel is `position: absolute` against it by rule 4a, so scrolling the page
 * carries the panel along with the header and the clipped 377px never
 * arrives. **A user in that orientation has no route to two thirds of the
 * menu.** Reported from a phone, 2026-09-04, and reproduced before anything
 * was changed.
 *
 * ── WHY THE ARRANGEMENT SWITCHES RATHER THAN THE PANEL SHRINKING ─────────
 * The panel could have been given `max-block-size` and `overflow-y: auto`.
 * That is one declaration in one organism file against three media
 * conditions and a rule pair here, so it was the cheaper fix and it was
 * rejected on what it produces: a 288px window onto a 666px panel, 43% of
 * it visible, as a nested scroll region inside a page that also scrolls,
 * under a header still charging 124px because the desktop arrangement stays
 * in place. The overlay organism is ALREADY the arrangement for a short
 * box — rule 4e pins its identity bar and scrolls the tree beneath it, and
 * 4e's own note records that being demonstrated under load. Routing the
 * viewport to the arrangement that handles it beats teaching a second
 * arrangement to survive it.
 *
 * The switch also takes the header down to 76.8px, because rule 2e now
 * fires and the action leaves the row. That is not a side effect to be
 * grateful for; it is 2e's ruling applying to the arrangement 2e is about.
 *
 * ── THE THRESHOLD, AND IT IS A RAW VALUE ─────────────────────────────────
 * 500px, with the file's `.98` guard on the `max-` side, so the pair reads
 * `max-height: 499.98px` / `min-height: 500px` exactly as 599.98/600 does.
 *
 * Landscape phone heights run from 320px (iPhone SE) to roughly 440px on the
 * current large phones. The shortest tablet landscape is 768px. So the whole
 * phone-landscape population sits below 500px and every tablet well above
 * it, with 500-768 reachable only by a deliberately short desktop window —
 * where serving the overlay is defensible rather than wrong, since the mega
 * panel would not fit there either.
 *
 * ** FLAGGED, AND THIS ONE IS A NEW AXIS, NOT A FOURTH INSTANCE. **
 * `gravitysite-css-standards.md` names two canonical breakpoints and both are
 * widths. This introduces a viewport-HEIGHT condition, which the standard
 * does not contemplate at all, and it cannot be tokenised — a media query
 * cannot read a custom property, so the literal is unavoidable in the same
 * way 600px is. Raised for the standard rather than taken quietly. The
 * argument for admitting it: the condition is a fact about the content's
 * height against the viewport's, which no width can express.
 *
 * ── WHAT THIS RULE PAIR ACTUALLY DOES ────────────────────────────────────
 * Core gates the two arrangements from inside its own `min-width: 600px`
 * query in `block-library/style.min.css`, which no condition of ours can
 * reach. Two declarations there have to be reversed for a short viewport:
 *
 *   .wp-block-navigation__responsive-container:not(.hidden-by-default)
 *     :not(.is-menu-open)                 { display: block }   (0,3,0)
 *   .wp-block-navigation__responsive-container-open:not(.always-shown)
 *                                         { display: none }    (0,2,0)
 *
 * The first is the closed desktop list; the second is the hamburger core
 * hides once the list is inline. Both are matched here with `.c-header`
 * prepended, which clears them on specificity rather than on load order.
 *
 * Nothing else is needed, and that is worth stating because it was checked
 * rather than assumed: core's open-state rules — `fixed; inset: 0`,
 * `z-index: 100000`, `overflow: auto`, the flex column — sit OUTSIDE its
 * width query and already apply at any viewport. Verified on the render at
 * 915x412 with the switch in place: the container reported `position: fixed`
 * at 915.2x412, the identity bar held at y=0, and the tree scrolled 358px of
 * content in a 311px region.
 *
 * SCOPED TO `.c-header`, not fleet-wide on core's classes alone. Unlike 4e
 * and 4f, which any site running a custom overlay meets, this pair changes
 * WHICH ARRANGEMENT A HEADER USES — a decision a fleet site is entitled to
 * make differently. It is substrate by the section 4 test (it exists only to
 * reverse core's stylesheet) and a header-scoped decision at the same time.
 *
 * CONSEQUENCE WORTH KNOWING: the overlay's rows run the full viewport width
 * here, so on a 915px-wide phone the panel is a very wide single column and
 * the action button spans it. It reads sparse, not broken, and constraining
 * it is a design decision that has not been taken. Logged as an open item
 * rather than guessed at.
 */
@media (max-height: 499.98px) { /* short viewport — see the threshold note above */
  .c-header .wp-block-navigation__responsive-container:not(.hidden-by-default):not(.is-menu-open) {
    display: none;
  }

  .c-header .wp-block-navigation__responsive-container-open:not(.always-shown) {
    display: flex;
  }
}

/* ── 5. Cluster shell substrate ──────────────────────────────────────────
 * Added 2026-09-02 on the GravityStack website rebuild, at theme 4.29.0, with
 * the cluster sidebar. One rule, and it exists for the same reason section 4
 * does: the declaration below would be unnecessary if core's layout stylesheet
 * vanished, which is the test in `gravitysite-css-standards.md` *Files* and is
 * what makes it substrate rather than design.
 *
 * SCOPE. `.c-cluster-shell` is the contract class of the two-column page shell
 * that `templates/page-cluster.html` declares and the cluster sidebar organism
 * sits in. Naming it here reserves it fleet-wide, as section 4 reserves
 * `.c-mega`: another organism declaring `.c-cluster-shell` for something else
 * would collide silently. A site with no cluster pages matches nothing here.
 *
 * The grid itself — the columns, the gap, the breakpoint — is design and is in
 * `assets/css/organisms/cluster.css`. This is the property-disjoint half of the
 * split that standard prescribes: same subject, two files, no overlap in what
 * they declare.
 */

/* 5a. Core's flow-layout rules reach the shell's grid children.
 *
 * A `core/group` with the default layout puts `margin-block-start` on every
 * child after the first and `margin-block-end: 0` on all of them, and a
 * constrained one additionally caps each child at the global content size and
 * centres it. Neither is wrong for a group laying out in flow. Both are wrong
 * for grid tracks: the margin drops the content column below the sidebar by a
 * block gap, and the cap would squeeze a 770px reading column to 640px and
 * centre it in the space.
 *
 * Selector this must beat, and the count:
 *   :where(body .is-layout-flow) > *                            (0,0,0)
 *   this rule: .c-cluster-shell > *                             (0,1,0)
 *
 * THE TEMPLATE'S GROUP MUST STAY ON THE FLOW LAYOUT, and this is the rule that
 * depends on it. Core's CONSTRAINED layout emits
 * `:where(body .is-layout-constrained) > :not(.alignleft):not(.alignright):not(.alignfull)`
 * at (0,3,0), which outranks this and would reinstate the cap. Raising this
 * rule to match was declined: it would be a guard against an edit nobody has
 * a reason to make, written in the fleet stylesheet, and the honest form of
 * that guard is this sentence. If `page-cluster.html` ever declares
 * `"layout":{"type":"constrained"}` on the shell, the columns will render and
 * the measure will be wrong, and this is the note that says why.
 */
.c-cluster-shell > * {
  margin-block: 0;
  max-width: none;
}
