/*
 * base.css — GravityStack Main
 *
 * Shared composition layer, common to every GravitySite fleet build.
 * Component classes (.c-pill, .c-badge, .c-tile, etc. — see
 * gravitystack-design.md's Components section) and shared .u-* utilities
 * land here in a later, separate pass, once the pattern library exists.
 *
 * As of design-to-theme-json v2.0.0, all token-layer content that used to
 * live here has moved into theme.json:
 * - The `rounded` and `tracking` scales are emitted as
 *   `--wp--custom--rounded--*` / `--wp--custom--tracking--*` custom
 *   properties, and `elevation` is a native `settings.shadow.presets` family.
 * - The Spectra One parent-alias block (spacing, font-size, and colour) is
 *   emitted by the skill directly into theme.json's `styles.css` field at
 *   `:root:root`, which reaches the editor automatically with no enqueue.
 *
 * 2026-08-11: first content added — three interactive-state rules that
 * theme.json cannot express (see the named section below). No component
 * classes exist yet; that pass is still separate and still pending the
 * patterns plugin.
 */

/* ── 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. 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.
 *
 * 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) {
  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):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 takes the brand hue, per "Wayfinding uses the brand hue;
 * in-content action uses the CTA hue." Nav links have no underline to
 * thicken, so the hue shift is the whole cue.
 *
 * DARK BAND ONLY, and the scoping is load-bearing. Measured 2026-08-29:
 * gravity-brand reads 5.34:1 on gravity-surface-dark but only 2.16:1 on
 * gravity-surface-subtle, so an unscoped brand hover would make a light-band
 * nav link harder to read on hover than at rest — the exact fault rule 1b
 * exists to prevent, reintroduced one surface over. On light surfaces the
 * theme.json hover already works: action-11, the global hover since the
 * v4.0 repoint, measures 9.84:1 on gravity-surface-subtle and 8.93:1 on
 * gravity-surface-nested (the amber-era accent-12 figures were 10.89:1 and
 * 9.89:1), so nothing is authored for that case.
 *
 * CORRECTED 2026-08-31: that last clause was true of the links this rule
 * was written against — the footer's nav, which is plain anchors inside
 * lists and paragraphs — and false of `core/navigation`. Core ships
 * `.wp-block-navigation .wp-block-navigation-item__content` with the
 * class DOUBLED, deliberately, which computes to (0,3,0) and beats
 * theme.json's `a:where(:not(.wp-element-button))` at (0,1,1). Core has
 * no hover of its own, so a navigation block's links do not change on
 * hover at all. Rule 2d below authors it.
 *
 * This is the second time the light-surface convention has failed to
 * transfer to the dark band, after the accent-12 fall-through in 1b. The
 * standard's open item on hover direction — that down-ramp walks toward a
 * dark surface rather than away from it — is the general form, and it is
 * still open.
 */
.is-style-band-dark nav a:not(.wp-element-button):hover {
  color: var(--wp--preset--color--gravity-brand);
  text-decoration: none;
}

/* 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);
}

/* ── Card styles ─────────────────────────────────────────────────────────
 * Fleet defaults for the two card-grid style variations, per
 * `gravitysite-design-system-standards.md`, "Card styles". Two roles named
 * for the job the section is doing: `cards-distinct` 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-main` 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: distinct 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 distinct 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.
 *
 * 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.
 */

.c-card-grid--distinct .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);
}

.c-card-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. Headings are targeted directly rather than left to
 * inherit — see the docblock above. Grouped targets the whole grid
 * container's descendants, since the fill now lives there rather than on
 * each card.
 */
.c-card-grid--distinct .wp-block-group-is-layout-grid > .wp-block-group,
.c-card-grid--distinct
  .wp-block-group-is-layout-grid > .wp-block-group :is(h1, h2, h3, h4, h5, h6),
.c-card-grid--grouped .wp-block-group-is-layout-grid,
.c-card-grid--grouped
  .wp-block-group-is-layout-grid :is(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--preset--color--gravity-text);
}

/* Links. Excludes button-styled links, which take the CTA tokens
 * elsewhere and are covered by the focus-ring rule below instead.
 */
.c-card-grid--distinct
  .wp-block-group-is-layout-grid > .wp-block-group a:not(.wp-element-button),
.c-card-grid--grouped
  .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. 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."
 */
.c-card-grid--distinct
  .wp-block-group-is-layout-grid > .wp-block-group .wp-element-button,
.c-card-grid--grouped
  .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);
}

.c-card-grid--distinct
  .wp-block-group-is-layout-grid > .wp-block-group .wp-element-button:hover,
.c-card-grid--grouped
  .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. */
.c-card-grid--distinct
  .wp-block-group-is-layout-grid > .wp-block-group a:focus-visible,
.c-card-grid--distinct .wp-block-group-is-layout-grid
  > .wp-block-group .wp-element-button:focus-visible,
.c-card-grid--grouped .wp-block-group-is-layout-grid a:focus-visible,
.c-card-grid--grouped
  .wp-block-group-is-layout-grid .wp-element-button:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus);
}

/* ── 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 pattern to
 * this axis means adding two selectors here, not a new block of rules.
 *
 * ONE DECLARER IS NOT A PATTERN. `gs-footer-close--divider-top` is the
 * footer's close band in `parts/footer.html` — a template part, not a
 * pattern, and the third declarer the standard reserved Top for. It takes
 * the `gs-` prefix precisely so Decision 60 cannot reach it: 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 it into `c-`. It writes the value unconditionally rather
 * than as a modifier on a picker, because the band has no picker — it
 * ships on every page and is chrome rather than content.
 *
 * 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-split--divider-top,
.c-hero-split--divider-bottom,
.c-hero-stack--divider-top,
.c-hero-stack--divider-bottom,
.gs-footer-close--divider-top {
  --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-split--divider-top::before,
.c-hero-stack--divider-top::before,
.c-cta-stack--divider-bottom::before,
.c-hero-split--divider-bottom::before,
.c-hero-stack--divider-bottom::before,
.gs-footer-close--divider-top::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-split--divider-top::before,
.c-hero-stack--divider-top::before,
.gs-footer-close--divider-top::before {
  top: 0;
}

.c-cta-stack--divider-bottom::before,
.c-hero-split--divider-bottom::before,
.c-hero-stack--divider-bottom::before {
  bottom: 0;
}


/* ── Question styles ─────────────────────────────────────────────────────
 * Fleet defaults for the Question treatment axis, 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 Boxed —
 * 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 Question treatment
 * 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 Boxed modifier is each
 * pattern's own address plus `--boxed`, 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-accent`, 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 nowhere else in this file; that
 * is deliberate, not a local improvisation.
 *
 * 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.
 *
 * Boxed'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. Boxed's padding sits
 * outside that rather than replacing it, which is what makes a boxed panel
 * symmetric top to bottom.
 *
 * Per *A fill obliges a tonal set*, Boxed 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: Boxed on `band-accent` 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 Boxed switches it back,
 * because inside a Boxed panel 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 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 Boxed'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);
}

/* Boxed. */
.c-qa-accordion--boxed .wp-block-accordion-item,
.c-qa-aside-accordion--boxed .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);
}

/* Boxed's tonal set. Headings are targeted directly rather than left to
 * inherit, for the reason given against the card styles above.
 */
.c-qa-accordion--boxed .wp-block-accordion-item,
.c-qa-accordion--boxed .wp-block-accordion-item :is(h1, h2, h3, h4, h5, h6),
.c-qa-aside-accordion--boxed .wp-block-accordion-item,
.c-qa-aside-accordion--boxed
  .wp-block-accordion-item
  :is(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--preset--color--gravity-text);
}

.c-qa-accordion--boxed .wp-block-accordion-item a:not(.wp-element-button),
.c-qa-aside-accordion--boxed
  .wp-block-accordion-item
  a:not(.wp-element-button) {
  color: var(--wp--preset--color--gravity-link);
}

.c-qa-accordion--boxed .wp-block-accordion-item a:focus-visible,
.c-qa-accordion--boxed .wp-block-accordion-item .wp-element-button:focus-visible,
.c-qa-aside-accordion--boxed .wp-block-accordion-item a:focus-visible,
.c-qa-aside-accordion--boxed
  .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--boxed.is-style-band-dark
  .wp-block-accordion-heading__toggle:focus-visible,
.c-qa-aside-accordion--boxed.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. Core ships none. */
.c-qa-accordion .wp-block-accordion-heading__toggle,
.c-qa-aside-accordion .wp-block-accordion-heading__toggle {
  gap: var(--wp--preset--spacing--20);
}

/* 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;
}

/* ── Quote styles ────────────────────────────────────────────────────────
 * Fleet defaults for the Quote treatment axis, 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 Boxed —
 * 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 Boxed has
 * to cancel it.
 *
 * ONE PATTERN DECLARES THIS AXIS TODAY, `c-quote-grid`, and the selector
 * lists below are still written as lists for the reason the Question styles
 * block gives: a second declarer is one selector in each list and nothing
 * else. The unit is addressed as a direct child of the grid container,
 * which is `card-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/quote-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 Boxed'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-boxed` 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 — `card-grid`'s ruling that
 * padding is the inset a surface needs — so Ruled and Boxed sit on the same
 * box and only the paint changes.
 *
 * Per *A fill obliges a tonal set*, Boxed 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-accent`'s ground, so Boxed units are near-invisible on
 * that band. Same reading already recorded for `cards-grouped` and
 * `questions-boxed`, 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 SITS BEHIND THE CONTENT, not over it. A positioned
 * pseudo-element paints above its element's in-flow text, so at 20% it
 * would muddy the first line rather than sit under it. `isolation: isolate`
 * gives the unit a stacking context without touching `z-index` anywhere
 * else, and the mark's `z-index: -1` then paints after the unit's own fill
 * and before its text — the one order that puts a watermark where a
 * watermark goes.
 *
 * Second, ITS CORNER OFFSET NAMES THE PATTERN'S OWN PADDING TOKEN, and that
 * is a coupling worth stating. The contract seats the mark inside the
 * unit's padding, and an absolutely positioned child is laid out against
 * the padding box, so `0` would put the mark at the padding's outer edge
 * rather than at the content corner. `spacing|40` here is the pattern's
 * inline padding restated in this file. If the pattern's unit padding
 * moves, this moves with it, and nothing checks that.
 *
 * 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` is a first value read off the render, not
 * a derivation — the contract says so outright.
 *
 * 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 watermark at 20%: 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.
 */
.c-quote-grid .wp-block-quote {
  border: 0;
  padding-inline-start: 0;
  margin-block-end: 0;
}

/* The unit hosts both pseudo-elements under either treatment. */
.c-quote-grid .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.
 */
.c-quote-grid .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);
}

/* Boxed. Cancels the ruled rule, since the default above is unconditional.
 * `isolation` is here rather than with the mark because it belongs to the
 * unit, not to the pseudo-element it makes room for.
 */
.c-quote-grid--boxed .wp-block-group-is-layout-grid > .wp-block-group {
  isolation: isolate;
  background-color: var(--wp--preset--color--gravity-surface-nested);
  border-radius: var(--wp--custom--rounded--md);
}

.c-quote-grid--boxed .wp-block-group-is-layout-grid > .wp-block-group::before {
  content: none;
}

/* Boxed's tonal set. Headings are targeted directly rather than left to
 * inherit, for the reason given against the card styles above.
 */
.c-quote-grid--boxed .wp-block-group-is-layout-grid > .wp-block-group,
.c-quote-grid--boxed
  .wp-block-group-is-layout-grid
  > .wp-block-group
  :is(h1, h2, h3, h4, h5, h6) {
  color: var(--wp--preset--color--gravity-text);
}

.c-quote-grid--boxed
  .wp-block-group-is-layout-grid
  > .wp-block-group
  a:not(.wp-element-button) {
  color: var(--wp--preset--color--gravity-link);
}

.c-quote-grid--boxed
  .wp-block-group-is-layout-grid
  > .wp-block-group
  a:focus-visible,
.c-quote-grid--boxed
  .wp-block-group-is-layout-grid
  > .wp-block-group
  .wp-element-button:focus-visible {
  outline-color: var(--wp--preset--color--gravity-focus);
}

/* The mark — Boxed 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.
 */
.c-quote-grid--boxed .wp-block-group-is-layout-grid > .wp-block-group::after {
  content: "\201C";
  content: "\201C" / "";
  position: absolute;
  z-index: -1;
  inset-block-start: var(--wp--preset--spacing--40);
  inset-inline-start: var(--wp--preset--spacing--40);
  font-family: Georgia, "Times New Roman", Times, serif;
  font-size: 96px;
  line-height: 1;
  color: color-mix(in srgb, currentColor 20%, 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-quote-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-quote-grid figure.wp-block-image img {
  block-size: 48px;
  inline-size: auto;
  max-inline-size: 96px;
}

/* ── 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;
}

/* ── 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";
  }
}

/* ── 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;
}

/* 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) { /* below core's overlayMenu threshold — see 4a */
  .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;
  }
}

/* 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: a library
 * entry 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 library entry at
 * `theme-builds/library/menus/mega-featured/`, 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 Spectra One's
 * `.wp-block-group.is-layout-flow.wp-block-group-is-layout-flow` padding reset
 * at (0,3,0) — but what they declare is a shadow, a corner, and a padding
 * value, which is one client's design. The raise is forced by the parent; the
 * declarations are not fleet. They stay in the organism's file, and the
 * parent's selector is recorded in that entry's specificity record so the next
 * copier knows why the selectors are doubled.
 *
 * HONEST LIMIT. This file is itself copied per client, so "authored once" is
 * the intent rather than the mechanism until `base.css` becomes a library
 * entry under the same provenance instrument. Trigger: the second GravitySite
 * build. Still better than the same fix made three times in three organism
 * files.
 */

/* 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) { /* core's overlayMenu threshold — see the deviation note above */
  .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) { /* below core's overlayMenu threshold — see the flag above */
  .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;
  }
}

/* ── 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`: a library entry 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;
}
