/**
 * Sitewide interactive colour states -- the shared states sheet.
 * SPEC-interactive-color-states, Story 1 (dark-ground defaults).
 *
 * WHAT THIS FILE IS
 * -----------------
 * The one owner of interaction state for every clickable item on the site.
 * Before it, thirteen `theme/*.css` sheets carried six independently invented
 * idioms because state rules hardcoded colour instead of referencing tokens,
 * so a template that needed a different value had to invent a different rule.
 * This sheet declares the Tier-2 semantic tokens (Family A interactive,
 * Family B current/selected, Family C shape) and the Tier-3 rules bound to
 * them. A new ground supplies VALUES; it never adds RULES.
 *
 * LOAD ORDER
 * ----------
 * This sheet loads FIRST of all child sheets, so every template sheet can
 * override it by the normal cascade. That order is guaranteed by a `$deps`
 * edge naming `joetennis-site-interactive-states` on every other child
 * handle in functions.php -- NEVER by call order. WP_Styles reorders the
 * queue freely (see the note at functions.php:330-331).
 *
 * The corollary that bites: Neve's own `neve-style-css` and the site's
 * `wp-custom-css` load AFTER every child sheet. Confirmed live 2026-09-19 on
 * dev.joetennis.com -- head order is
 *   ... joetennis-site-* ... > neve-style > neve-style-inline > wp-custom-css.
 * So a rule here that merely TIES Neve on specificity loses. Every rule below
 * that has to beat a Neve rule carries its measured specificity in a comment
 * and is deliberately raised (usually by a leading `body`) to clear it. Do
 * not "tidy" those prefixes away.
 *
 * THE DISCIPLINE
 * --------------
 * - A state rule may reference Tier-2 tokens and nothing else. Literal hex
 *   appears ONLY in the `:root` declarations below and in comments.
 * - Neve variables are only ever read as `var(--nv-x, <local fallback>)`.
 *   A bare `var(--nv-x)` drops the whole declaration when unresolved and the
 *   state becomes a silent no-op (simple-stack.css:195-199 documents this).
 * - `:focus-visible` only, never bare `:focus`.
 * - Border WIDTH, font WEIGHT and TEXT-DECORATION are literals in the rules,
 *   not tokens: they are ground-independent. Only colour and radius are
 *   tokenised -- radius because Story 4 had to be a pure value swap,
 *   which it was.
 *
 * WHAT ACTUALLY MOVES ON SCREEN
 * -----------------------------
 * Everything else in this sheet is either inert behind a template sheet or
 * a token nothing consumes yet. Seven things change what a visitor sees:
 *
 *   1. Nav hover        -- was Neve's, now owned here. Same rendered value
 *                          on the dark ground; the change is ownership,
 *                          plus it now survives the Portfolio marker.
 *   2. Nav focus        -- new ring. Nothing painted one before.
 *   3. Footer focus     -- new ring. site-footer.css carried hover and no
 *                          focus at all, the only such sheet.
 *   4. Inline links     -- rest underline removed, rest/focus ink moved to
 *                          #0f8c9d, hover thickens the underline.
 *   5. Button / CTA     -- the ghost/fill inversion, sitewide.
 *   6. Carousel focus   -- new ring on flush-split and single-project,
 *                          which previously moved colour only.
 *   7. Flip-card focus  -- new ring on the card label. The card was
 *                          keyboard-reachable with no indicator at all:
 *                          a live WCAG 2.4.7 failure, not a refinement.
 *
 * WHAT STORY 2 ADDED (the retrofit)
 * ---------------------------------
 * The four dark template sheets gave up their state rules, so rules that
 * were inert above are now the ones painting. Five further visible changes:
 *
 *   8.  Chip rest edge  -- purple -> white, the decided rest cell. The
 *                          archive's funnel icon moves with it.
 *   9.  Chip hover      -- INK ONLY. The decided #000000 fill measures
 *                          1.09:1 on this ground and rendered as nothing;
 *                          Joe ruled the fill role off on dark
 *                          (2026-09-19) rather than retune it. Note the
 *                          decided rest and hover EDGES are both #ffffff,
 *                          so with the fill off hover moves exactly one
 *                          channel: #e2e8f0 -> #00c7fc. Colour-only, and
 *                          ACCEPTED as such -- see the chip block.
 *   10. Carousel weight -- glyphs were taking Neve's 600; the decided cell
 *                          is 500, and nothing had ever declared it.
 *   11. Carousel edge   -- Simple Stack's button had `border: 0`, so the
 *                          -bd role had nothing to paint. Joe ruled it a
 *                          1px edge, making all three consistent on both
 *                          roles -- and giving story 4's 999px something
 *                          to show at rest rather than only on focus.
 *   12. Disabled edge   -- the decided disabled cell is now declared. It
 *                          is NOT a visible change: see the carousel
 *                          block. Counted here as shipped, not as seen.
 *
 * Plus two new owners: breadcrumb link hover/focus, and the archive funnel
 * icon. And one retirement: Simple Stack's `currentColor` focus ring, whose
 * light-page premise was measured away. See the carousel block.
 *
 * WHAT STORY 6 ADDED (the third ground)
 * -------------------------------------
 * Two more visible changes, both in the footer bar, and neither of them a
 * token value:
 *
 *   13. Footer band     -- #666 -> #21232b (site-footer.css). Every control
 *                          on it gains contrast; the bar loses most of its
 *                          separation from the page. That was the trade.
 *                          v1.13.3 moved it again, to #403f3f, and gave
 *                          part of that contrast back -- see the FOOTER
 *                          NAV block for all three columns.
 *   14. Footer marker   -- the footer menu's current item is marked for the
 *                          first time. The selector was always available;
 *                          the ground to show it on was not.
 *
 * SCOPE
 * -----
 * ALL THREE GROUNDS. This sheet declares the WHITE ground as the default
 * (v1.13.0, SPEC-sitewide-white-ground) and owns the Tier-3 rules every
 * ground reads; `site-ground-dark.css` re-binds the dark deltas as VALUES
 * and owns no state rule of its own. That is the contract proven end to
 * end, and proven a second time by the inversion: flipping which ground is
 * the default moved VALUES between two blocks and did not touch a single
 * rule below.
 *
 * IT USED TO BE THE OTHER WAY ROUND, and the reason it changed is the
 * spec's whole thesis. Binding dark here made dark the thing you got by
 * declaring nothing, so every white surface was a delta someone had to
 * remember and a new page landed dark with no error and nothing to notice.
 * White is the site's palette; dark is a deliberate exception for project
 * case studies, and it now has to be asked for by name.
 *
 * THE THIRD GROUND COSTS NOTHING AT ALL, which is the stronger result. The
 * footer band was #666 and carried one failing cell, ruled and deferred
 * twice. v1.11.21 darkened it to #21232b, close enough to the page that
 * every `:root` default is already correct on it -- so the band needed no
 * binding block, and the deferred cell cleared without a token moving. The
 * numbers are in the FOOTER NAV block; do not add a scope for this ground
 * without first checking there is anything for it to re-bind.
 */

/* =====================================================================
   TIER 2 -- token declarations. The ONLY place literal hex may appear.

   THIS BLOCK IS THE WHITE GROUND, and as of v1.13.0 that is the whole
   point of it. It used to be the DARK ground: `site-interactive-states.css`
   bound the dark values here, so any element, page or template that
   declared nothing inherited dark and every white surface was a delta
   someone had to remember. That was backwards from the site being built.
   White is the palette; dark is a deliberate exception for project case
   studies. SPEC-sitewide-white-ground inverted it, and the dark values now
   live in `site-ground-dark.css` under `.joetennis-ground-dark`, which only
   the seven dark consumers load.

   WHAT THAT BUYS, stated plainly because it is the spec's whole thesis: a
   new page added to this site renders white without anyone doing anything
   to make it so. The old failure mode -- a page landing dark with no error,
   no missing stylesheet and nothing to notice until someone looked -- is
   not reachable from here any more.

   Values are the decided white-ground cells, carried over from the
   `.joetennis-ground-white` class this block replaced (v1.11.18 -> v1.13.0)
   together with the reasoning that produced them. `.joetennis-ground-white`
   no longer exists; nothing needs to opt into white.
   ===================================================================== */

:root {
	/* -----------------------------------------------------------------
	   Ground tokens.

	   The page half. Its selection rule was once "only roles whose value
	   is ALSO correct on site-footer.css's band, since these inherit all
	   the way down" -- the constraint flush-split-white.css's block 5b was
	   written under.

	   THAT RULE NO LONGER HOLDS FOR TWO OF THESE LINES, and the band is
	   why. It was #666 when these were chosen; v1.11.21 moved it to
	   #21232b and v1.13.3 to #403f3f, and two bindings stopped clearing it:

	     --ground-default-ring  #000000   3.66:1 on #666  ->  1.34:1 on #21232b  ->  2.00:1 on #403f3f
	     --current-marker       #8100fc   n/a on #666     ->  2.49:1 on #21232b  ->  1.67:1 on #403f3f

	   The marker's literal moved too -- #7a22ce until v1.13.3, #8100fc
	   after it (SPEC-revision01-colour-adoption decision 3). Neither
	   figure above was ever survivable on the band; the counter-binding
	   below is what carries them, and it is what the lighter band makes
	   MORE load-bearing rather than less.

	   Black was picked precisely BECAUSE it cleared both grounds (21:1 on
	   the white page, 3.66:1 on the band). It still clears the page; it no
	   longer clears the band. Rather than re-pick a value that has to
	   satisfy two unrelated grounds at once -- which is what made that
	   constraint fragile in the first place -- v1.11.21 counter-binds both
	   on `.joetennis-site-footer`. See the FOOTER NAV block. THESE LINES
	   ARE THEREFORE CORRECT FOR THE WHITE PAGE ONLY; the footer is handled
	   downstream. Do not "fix" them against the band.

	   THE COUNTER-BINDINGS NOW SHADOW `:root` RATHER THAN A CLASS, which
	   is the one thing the inversion changed about them. They used to
	   counter a body-scoped `.joetennis-ground-white`; they now counter the
	   root default. Specificity is not consulted either way --
	   `.joetennis-site-footer` is a descendant, so inheritance settles it --
	   and checks/05 asserts the shadowing holds.

	   THE FULL FOOTER CHECK, enumerated rather than summarised. The band
	   holds exactly three things: the copyright text,
	   `.joetennis-footer-nav a`, and the "Get in touch" CTA. The footer
	   nav's rest ink is a literal #ffffff in site-footer.css; its hover and
	   focus read `--state-nav-hover-fg` / `--state-nav-focus-ring`. The
	   Tier-4 bare-element rules at the foot of this file (`hr`, `table`,
	   `code`, `blockquote`, `fieldset`, `input`) DO read `--ground-ink` /
	   `--ground-fill` / `--ground-edge` and would paint white-ground values
	   on the band -- but the footer markup contains none of those elements,
	   verified against joetennis_child_site_footer_bar() and
	   site-footer.css. If a form or a table is ever added to the footer,
	   that is the pairing to re-check.

	   THE LESSON IS THE PRECONDITION, NOT THE VALUE. Every "safe" claim
	   here is of the form "no rule down there reads this token". Any new
	   rule scoped to the footer can falsify one. Before adding a footer
	   rule that reads a Tier-2 token, check whether this block binds it.
	   ----------------------------------------------------------------- */
	--ground-ink: #16191d;
	--ground-ink-strong: #000000;
	--ground-fill: #f1f5f9;
	--ground-edge: rgba(0, 0, 0, 0.25);
	--ground-default-ring: #000000;

	/* `--ground-band` IS BOUND HERE AND NOWHERE ELSE, and the asymmetry is
	   deliberate in the same way `--ground-mark-*` below is. Every other
	   role in this family is bound twice -- here and on
	   `.joetennis-ground-dark` -- because a rule reading a token the dark
	   block does not re-bind paints white-ground values on a dark page.
	   That risk is not reachable for this one: both consumers are
	   white-ground (the Portfolio archive's grid layout and the Flush Split
	   White case-study band), `?layout=stacked` is untouched, and no dark
	   equivalent of this grey has been drawn. Inventing one to make the two
	   grounds symmetrical would be inventing design. Whoever adds the first
	   dark consumer decides it, and checks/05 asserts that this is still
	   the ONLY role the two grounds disagree about existing.

	   THE VALUE HAS ONE HOME, which is the whole point of naming it. It was
	   a literal in flush-split-white.css; that sheet now aliases its own
	   `--flush-split-band-bg` to this. Changing this grey moves the archive
	   page area AND every case-study band together -- that consequence is
	   the reason Joe asked for the name (2026-09-22), not a side effect of
	   it. Do not reuse `--ground-fill` for this: #f1f5f9 is a recessed
	   surface fill, a narrower job. */
	--ground-band: #e4e4e4;

	/* `--ground-mark-*` ARE THE ASYMMETRY, and they are deliberate. The
	   states bench decided no white pair, because no <mark> renders on the
	   site; the white ground never bound them and fell through to the dark
	   values instead. After the inversion there is nothing to fall through
	   TO, so they are stated here at the values white already computed --
	   which preserves the rendered result exactly. They are inert: nothing
	   on the site emits a <mark>. Do not read them as a decided white pair,
	   and do not invent one to make the two grounds symmetrical. That would
	   be inventing design. */
	--ground-mark-fg: #111827;
	--ground-mark-bg: #e2e8f0;

	/* -----------------------------------------------------------------
	   FAMILY A -- interactive states. Transient, driven by input.
	   `--state-<widget>-<state>-<role>`; a bare stem never appears in a
	   rule. Role suffixes: -fg ink, -bd edge, -ring focus ring, -bg fill.
	   Ink/edge/ring re-bind when the ground flips; -bg does not.

	   TWENTY-ONE ROLES IN THIS BLOCK ARE CARRIED OVER FROM THE DARK
	   DEFAULT UNCHANGED, and they are marked where they appear. They are
	   the roles `.joetennis-ground-white` never overrode, so on a white
	   page they always resolved to these values -- carrying them forward
	   is what makes the inversion render identically rather than
	   approximately. They are NOT decided white cells. Each is one of:

	     inert   -- nothing on the site consumes it (`--ground-mark-*`,
	                `--state-nav-focus-fg`). NOTE `--state-nav-rest-fg` is
	                NOT in this group, though an earlier draft of this
	                comment put it there: no rule in THIS sheet reads it,
	                but flush-split.css does, for the card's flip cue. "No
	                rule here reads it" and "nothing reads it" are different
	                claims -- grep the whole theme before asserting the
	                second one;
	     shared  -- the value is genuinely ground-neutral (the three
	                `transparent` fills);
	     footer  -- the role's only white-page consumer is the "Get in
	                touch" CTA on the #403f3f band, which needs the dark
	                treatment. The header CTA overrides them below.

	   THE `footer` GROUP IS A KNOWN ODDITY: a dark band's values sitting
	   in the white default because the band has no ground block of its
	   own. That predates this spec -- it was equally true when `:root` was
	   dark -- and moving them onto `.joetennis-site-footer` is logged in
	   deferred-work.md rather than done here, because it is a
	   re-scoping decision, not part of inverting the default.
	   ----------------------------------------------------------------- */

	/* Nav item (primary nav, header + drawer + footer nav).

	   `--state-nav-hover-fg` READS THE MARKER, and it must be declared in
	   every block that declares `--current-marker` -- a `var()` is
	   substituted where it is DECLARED, so a copy in an outer block
	   resolves against the OUTER marker and inherits down as that literal.
	   That was a real shipped defect: white pages hovered nav at #a855f7
	   (3.96:1) instead of the #7a22ce (7.12:1) ruled in -- that marker is
	   #8100fc at 6.29:1 since v1.13.3 -- because the only
	   declaration sat above the block that re-bound the marker. Joe spotted
	   it by eye before any measurement did. The same pattern governs the
	   breadcrumb role below, and `site-ground-dark.css` re-declares both
	   for the same reason. */
	/* Carried over. NOT inert: flush-split.css:1034 binds the card's flip
	   cue to this role, and the cue sits on the card panel
	   (`--color-bg-surface`, #111827 on Flush Split and #3C5158 on Flush
	   Split White), not on the page -- so near-white ink is correct there
	   on both templates and the value is right for a reason unrelated to
	   which ground the PAGE is on. The nav itself does not consume it; rest
	   ink is Neve's own `--color`. */
	--state-nav-rest-fg: #e2e8f0;
	--state-nav-hover-fg: var(--current-marker);
	--state-nav-focus-fg: #e2e8f0;     /* carried over: inert, focus keeps rest ink */
	--state-nav-focus-ring: #00e5ff;   /* carried over: footer; header overrides */

	/* Button / CTA -- the theme's only one is Neve's `.button.button-primary`
	   ("Get in touch", header and footer, both from one theme_mod).
	   NOTE the inversion: ghost at rest, FILLED on hover.

	   EVERY ROLE HERE IS CARRIED OVER, and the reason is that the CTA
	   renders in exactly two places, neither of them the white page body:
	   the header, which overrides the seven roles whose white value differs
	   (below), and the footer's #403f3f band, which needs these. So these
	   thirteen are the FOOTER's values. See the carry-over note above. */
	--state-button-rest-fg: #ffffff;
	--state-button-rest-bg: transparent;
	--state-button-rest-bd: #00e5ff;
	--state-button-hover-fg: #000000;
	--state-button-hover-bg: #00e5ff;
	--state-button-hover-bd: #00e5ff;
	--state-button-focus-fg: #000000;
	--state-button-focus-bg: #00e5ff;
	--state-button-focus-ring: #00e5ff;
	--state-button-active-fg: #000000;
	--state-button-active-bg: #00b8cc;
	--state-button-disabled-fg: #7d7d7d;
	--state-button-disabled-bg: #00b8cc;

	/* Inline body-copy link. Rest is deliberately not the brand accent:
	   the no-underline design is kept and the link HUE moves away from body
	   ink instead (Joe, 2026-09-19). #0b6b78 measures 6.20:1 against the
	   #ffffff page and clears 3:1 against #16191d body ink, which is the
	   comparison WCAG 1.4.1 actually cares about for a link with no
	   underline. It is the same hue and saturation as the dark ground's
	   #0f8c9d, differing only in lightness -- one family, lightened within
	   the hue for this ground, not a second accent.

	   HOVER GOES TO BLACK rather than lighter: on this ground the window
	   that satisfies both floors sits ABOVE the link, and #000000 also
	   thickens the underline, a non-colour carrier.

	   THE RING IS NOT MOVED WITH THE INK. A focus ring is non-text and
	   needs 3:1 against the GROUND rather than against prose; #000000 gets
	   21:1 here. The dark ground keeps the brand accent for the same
	   reason, in the other direction. */
	--state-link-rest-fg: #0b6b78;
	--state-link-hover-fg: #000000;
	--state-link-focus-fg: #0b6b78;
	--state-link-focus-ring: #000000;

	/* Carousel control. Rest edge is the bench's #000000 over the
	   prototype's "no box" (Joe, 2026-09-19). Hover moves INK as well as
	   edge. Disabled drops its edge; `transparent` keeps the 1px box
	   invisible rather than removing it, because the contract has no
	   border-WIDTH role -- a recorded gap, not an oversight.

	   HOVER MOVED FROM THE LINK FAMILY TO THE MARKER HUE, v1.13.6, and it
	   is an alignment rather than a new decision: the Interaction States
	   Bench's white ground has carried #8100FC for this cell since
	   decision 3 moved the marker, and every OTHER white carousel cell --
	   rest, focus, disabled -- already matched the bench exactly. This was
	   the single divergence. 6.29:1 against 6.20:1 for the #0b6b78 it
	   replaces, so nothing moves materially against the floor; it is a hue
	   call, made by Joe 2026-09-22 with both rendered.

	   WHERE IT ACTUALLY RENDERS, which is narrower than "every carousel":
	   `.flush-split__media` re-binds all eight of these for the slate
	   panel, so at desktop no carousel on the site reads this cell. It
	   becomes reachable at the stacked breakpoint, where that re-binding
	   is released and the controls land on the page floor -- see the media
	   query beside that block.

	   The two FILL roles are carried over as `transparent` and no white
	   carousel fill is decided. They are bound rather than absent so that a
	   ground where a carousel fill DOES work supplies a VALUE instead of
	   having to add a rule back -- which is the whole contract.

	   NOTE WHERE THESE DO *NOT* APPLY: `.flush-split__media` re-binds all
	   eight to the panel treatment, because since v1.11.19 the hero carousel
	   sits inside the media column on `--color-bg-surface` (#3C5158), not
	   on the page. See that scope below. */
	--state-carousel-rest-bg: transparent;   /* carried over: shared */
	--state-carousel-rest-fg: #000000;
	--state-carousel-rest-bd: #000000;
	--state-carousel-hover-bg: transparent;  /* carried over: shared */
	--state-carousel-hover-fg: #8100fc;
	--state-carousel-hover-bd: #8100fc;
	--state-carousel-focus-bd: #000000;
	--state-carousel-focus-ring: #000000;
	--state-carousel-disabled-fg: #000000;
	--state-carousel-disabled-bd: transparent;

	/* Breadcrumb link. Moves ink, the same property flush-split moves --
	   a ground must never move a DIFFERENT property than the other one
	   does. Rest ink is deliberately absent: a breadcrumb at rest is ground
	   ink, not a state. No -ring role either: the baseline
	   `a:focus-visible` already paints one from `--ground-default-ring`.

	   HOVER FOLLOWS THE MARKER (Joe, 2026-09-21): "I'd like to change that
	   to be the same purple hover color every other link has." It used to
	   be the LINK family's rest colour, i.e. the breadcrumb's hover was
	   painted with a value that means "not hovered", which read as an
	   unexplained green. #8100fc on #ffffff is 6.29:1 (it was #7a22ce at
	   7.12:1 until v1.13.3; decision 3 of
	   SPEC-revision01-colour-adoption ruled the trade explicitly). */
	--state-breadcrumb-hover-fg: var(--current-marker);
	--state-breadcrumb-focus-fg: #0b6b78;

	/* Archive filter chip. LATENT since v1.11.16 retired the discipline
	   chips -- bound anyway, because that is this sheet's whole claim: a
	   ground supplies VALUES whether or not a component is on screen. Do
	   not prune as unused. Verify by computed custom-property value, never
	   by looking for a rendered chip.

	   Note the fill: hover -bg stays brand cyan while -fg and -bd darken,
	   because cyan as ink or edge on white is 1.54:1 and must move, while
	   cyan as a FILL already passes at 13.65:1 and darkening it would drop
	   that to 3.39:1. No white chip fill at rest is decided, so `-rest-bg`
	   stays on the shared `transparent`.
	   Measured on #ffffff: #16191d 18.1:1, #8100fc 6.29:1 either way. */
	--state-chip-rest-bg: transparent;  /* carried over: shared */
	--state-chip-rest-fg: #16191d;
	--state-chip-rest-bd: #0b6b78;
	--state-chip-hover-fg: #000000;
	--state-chip-hover-bg: #00e5ff;
	--state-chip-hover-bd: #0b6b78;
	--state-chip-focus-bd: #8100fc;
	--state-chip-focus-ring: #000000;

	/* -----------------------------------------------------------------
	   FAMILY B -- current / selected. Persistent. Describes location or
	   selection, NOT input. Never a value of Family A.

	   Naming trap: CSS `:active` means "pointer is currently down".
	   "The active nav item" is `--current-marker`, and
	   archive-project.css's `.is-active` denotes a SELECTED chip, which is
	   Family B. Do not read `.is-active` as `:active`.

	   What carries selection on this ground: purple ink AND a purple edge
	   AND weight 600 AND the x -- three non-colour carriers, which is why
	   the strict "colour is never the sole carrier" rule still binds
	   Family B even though chip HOVER is excepted from it.
	   ----------------------------------------------------------------- */
	--current-marker: #8100fc;
	--current-selected-fg: #ffffff;
	--current-selected-bg: #8100fc;
	--current-selected-bd: #8100fc;

	/* -----------------------------------------------------------------
	   COMPONENT COLOUR. One role, hosted here because two sheets need it
	   and neither can reach the other's root. GROUND-INDEPENDENT in the
	   same sense Family C below is: the value is chosen against a
	   COMPONENT SURFACE, not against the page ground, so it does not
	   change when the ground does.

	   `--color-tab-current` IS THE CURRENT TAB'S INK AND IT MOVED HERE AT
	   STORY 3 OF SPEC-about-page. It was declared on `.flush-split`
	   (flush-split.css:246) from v1.13.15, which was correct while Flush
	   Split and Flush Split White were its only consumers -- a token used
	   by rules in one sheet belongs on that sheet's own root. The About
	   medallion is the third consumer and it is NOT inside
	   `.flush-split`, so the choice was: restate `#8cc201` in about.css,
	   or move the declaration somewhere both roots inherit from.

	   RESTATING IT WOULD HAVE FORKED IT SILENTLY, and the check would not
	   have caught that: `checks/06` counted declarations in
	   flush-split.css ALONE, so a copy in a second sheet left the suite
	   green while the two values drifted apart on the first edit. That is
	   the `--flush-split-band-bg` failure this repo has already had once.
	   The same commit that moved this value re-scoped that assertion to
	   every `theme/*.css`, which is why the move and the check change are
	   one change.

	   THE VALUE AND ITS RULING ARE UNCHANGED BY THE MOVE. #8cc201
	   measures 8.31:1 on Flush Split's #111827 card and 3.92:1 on the
	   #3C5158 slate that Flush Split White and the About medallion both
	   use -- short of 4.5:1 on the slate, and ruled in there twice, by
	   the same reasoning flush-split-white.css:385 records (decision 7):
	   an improvement on the purple's 2.11:1, not a pass. The brighter
	   #d3ff5b (7.24:1) was rendered alongside it and rejected both times
	   as reading like an alert rather than a current tab. DO NOT DARKEN
	   OR BRIGHTEN IT WITHOUT JOE.

	   CONSUMERS, as of story 3: `.flush-split__card-tab[aria-selected]`
	   and `.flush-split__panel-kicker` (flush-split.css), and the About
	   medallion's current arc label (about.css). Move this value and all
	   three move -- which is the whole point, and is Joe's 2026-09-23
	   coupling ruling extended to a third site rather than re-decided.

	   IT IS DELIBERATELY NOT A GROUND ROLE. `checks/05` scans this block
	   for roles that both grounds must bind, and this one is bound at
	   `:root` only -- see that check's ROOT_ONLY list and the note there
	   explaining why binding it in site-ground-dark.css would be the
	   fork, not the fix.
	   ----------------------------------------------------------------- */
	--color-tab-current: #8cc201;

	/* -----------------------------------------------------------------
	   FAMILY C -- shape. Component-level and GROUND-INDEPENDENT: radius
	   never changes between rest and hover, and a value chosen for the
	   dark ground stays correct on white. Its own block, never inside a
	   per-ground override.

	   THESE ARE THE DECIDED RADII. Story 4 flipped four of them on
	   2026-09-19 -- nav 0 -> 999px, button 3px -> 999px, chip 0 -> 999px
	   and carousel 0 -> 999px -- inverting the encoding this codebase
	   shipped with. The rule those values now carry is CAP-7's, and it
	   is one sentence: A ROUNDED BOX ON THIS SITE CAN BE OPERATED; A
	   SQUARED ONE CANNOT. `archive-project.css` used to document the
	   opposite as deliberate; that docblock was rewritten in the same
	   commit, so there is one encoding in this repo, not two.

	   THE CONTRACT HELD. All four moved as pure value swaps -- no rule,
	   no selector and no template file was touched to make them take
	   effect. Measured before and after on dev.joetennis.com: every
	   control computed the new 999px, and not one box metric moved --
	   nav 46x27, footer nav 45x19, CTA 125x39, chip 136x32, overlay chip
	   844x44, funnel icon 47x34, flush-split carousel 66x40, simple-stack
	   carousel 40x40. That was the story's real test: had any of them
	   needed a rule change, it would have meant shape was never
	   swappable here.

	   THE SAME RADIUS IS NOT THE SAME SHAPE. A 999px radius clamps to
	   half the shorter side, so a 40x40 button is a circle and a 66x40
	   one is a stadium. The carousels differ because their boxes differ:
	   simple-stack.css pins `box-sizing: border-box` and `padding: 0`,
	   while flush-split's button takes Neve's `12px 32px` button padding
	   over its own `width: 40px`. Both are correct against this token.
	   Do not "fix" the difference by forcing a width -- that is a
	   geometry decision, not a shape one, and it has not been ruled on.

	   ANY FUTURE FAMILY C MOVE MUST STILL BE A PURE VALUE SWAP. That
	   invariant did not expire when this story proved it: if a radius
	   change ever needs a rule or selector edit -- CAP-2's pending pill
	   move included -- the contract has been broken and that finding
	   outranks the change being attempted.

	   The discipline pill's 999px -> 3px move belongs to
	   spec-flush-split-alignment CAP-2, not here, and has not happened.

	   THE ENCODING IS NOT YET TRUE OF EVERY CONTROL, IN EITHER
	   DIRECTION. Family C covers the four categories the spec
	   enumerated; it does not reach every operable box in the theme, and
	   the rule is bidirectional, so rounded-but-static counts against it
	   too. Known open cases as of 2026-09-19, found in review and left
	   deliberately rather than fixed silently:

	     OPERABLE BUT STILL SQUARE
	     - `.portfolio-archive__chips-close`, the <=768px filter overlay's
	       44x44 close button (archive-project.css). `border: none`, no
	       radius, so its baseline ring paints square directly beside
	       chips whose rings are now 999px.
	     - The flip-card scenes on the THREE remaining card templates
	       (`.editorial-split-wide__card-scene` and siblings). The ring this
	       sheet installs for them follows a radius none of them declares.
	       It was four until v1.13.10, when SPEC-content-module-tabs
	       replaced Flush Split's flip with tabs -- one entry closed by the
	       control ceasing to exist rather than by being given a shape.
	       The tab buttons that replaced it are NOT a fifth case and not a
	       Family C category: they paint no box in any state, so a radius
	       would show only through the focus ring, exactly as nav's does.
	       flush-split.css gives that ring 3px rather than 999px because the
	       bar is 44px tall and flush to the card's top edge -- a geometry
	       constraint, recorded there, not a claim about affordance.
	     - Full-Bleed Float's toggle and single-project's <summary>
	       disclosure marker.

	     STATIC BUT ROUNDED
	     - `.simple-stack__headline-panel` and `.simple-stack__psr-panel`,
	       both `border-radius: 20px` from the prototype. Not operable.
	     - ALL NINE BOXES ON /about/ (`spec-about-redesign`, 2026-09-29):
	       the hero and the four prose panels at
	       `--about-panel-radius: 28px`, and the four facet boxes at
	       `--about-facet-radius: 11px`. Both values are the drawing's,
	       measured at 1:1 off the artboard, and the split is by BOX SIZE.
	       None of the nine is operable.

	       THIS ENTRY IS A RULING RATHER THAN A DISCOVERY, which is what
	       makes it different from the two above it. That pair arrived
	       from a prototype and was kept because nobody had decided
	       otherwise. These nine shipped SQUARE at v1.14.1, on this
	       section's own one-sentence rule, and Joe reversed it on
	       2026-09-29 having seen the squared page: *"Pray don't stand on
	       ceremony about rounded strictly meaning interactive."* So the
	       rule is not being quietly eroded here -- it was applied, the
	       result was reviewed, and the exception was granted on the
	       evidence. about.css's radius token carries the full reasoning
	       and the measurement error that the squaring argument had
	       rested on.

	       Nine entries is also the point at which "exception" starts to
	       strain, and that is worth noticing rather than hiding: eleven
	       static-but-rounded boxes now sit against a rule stated in one
	       sentence. If a third page arrives wanting the same, the honest
	       move is to rewrite CAP-7 rather than extend this list again.

	     - /contact/ IS THAT THIRD PAGE, 2026-09-30, AND THE TRIGGER
	       ABOVE IS HEREBY OWED RATHER THAN PAID. `.contact-page__panel`
	       at `--shape-panel-radius` (28px) is one more static-but-rounded
	       box, taking the list to twelve. The refinement that added it
	       deliberately did NOT rewrite CAP-7, and the reason is scope: a
	       one-sentence sitewide rule is Joe's to restate on seeing the
	       page, not a build's to redefine on the way past. What this
	       entry does instead is make the debt impossible to miss.

	       READ THIS ONE DIFFERENTLY FROM THE ELEVEN ABOVE IT, because
	       the same refinement ALSO rounded the form's fields and its
	       checkbox -- and those are not exceptions at all. They are
	       operable boxes that are now rounded, which is CAP-7 working.
	       The rule's problem was never the rounded statics; it is that
	       the rule reads as though SQUARE were the default, when white
	       ground, twelve static-but-rounded boxes and now a rounded
	       field family all say otherwise. When CAP-7 is rewritten, that
	       is the inversion to make, and this list should get shorter
	       rather than longer as a result.

	   Do not read this list as a to-do that anyone has scheduled. It is
	   the honest scope of what shipped, recorded so the one-sentence rule
	   is not quoted as though it already held everywhere.
	   ----------------------------------------------------------------- */
	/* Nav. THE ONE CONTROL WHOSE SHAPE IS NOT READABLE AT REST, and Joe
	   ruled that acceptable on 2026-09-19 rather than widen the story.
	   A nav item paints no box in any state: the CSSOM of every loaded
	   sheet, Neve's included, was scanned live and NO rule matching a
	   nav selector declares a background or a border. Rest ink is Neve's
	   `--color`, hover is ink-only, and the Family B marker in
	   site-nav.css is colour plus weight. So this 999px is expressed
	   ONLY as the shape of the `:focus-visible` ring, because `outline`
	   follows `border-radius`.

	   The options were put to Joe and this is the one he took: nav's
	   affordance at rest stays carried by position -- it is in the nav
	   bar -- and shape carries it under the keyboard. Giving nav a
	   painted box would be a header redesign and a new RULE rather than
	   a value, which is the line this story does not cross. Do not add
	   one, and do not add horizontal padding to "make the pill read":
	   with nothing painting the box, padding changes nothing visible. */
	--shape-nav-radius: 999px;
	/* Button. THE ONE TOKEN THAT COMPETES WITH NEVE'S OWN CASCADE, so it
	   is the one to re-measure rather than assume. Neve styles the same
	   `.button.button-primary`, and this sheet loads BEFORE it; the rest
	   rule below is deliberately raised with a leading `body` for that
	   reason. Verified live 2026-09-19 on four pages: computed
	   border-radius is 999px on the header and footer CTA, so the raised
	   selector is still winning. The only `input[type="submit"]` on the
	   site belongs to a `display: none` search form and never renders. */
	--shape-button-radius: 999px;
	/* Inline link stays SQUARE, and it is not an exception to "round
	   means clickable" -- it is outside the rule's subject. The rule
	   speaks about boxes; an inline link is text in a paragraph, and
	   rounding the box it does not have would do nothing. Its affordance
	   is carried by ink and by the focus ring. */
	--shape-link-radius: 0;
	--shape-chip-radius: 999px;
	/* Carousel. Family C once recorded this as ALREADY 999px and that
	   was wrong -- flush-split.css:606 and single-project.css:272 both
	   declared `border-radius: 0` and simple-stack.css declared none, so
	   all three shipped square. Story 1 declared the token, story 2
	   retired those literals and wired it here, and Joe ruled the
	   carousel into the flip on 2026-09-19 rather than carve an
	   exception into a one-sentence rule. That correction is what made
	   this story a FOUR-token flip rather than three.

	   BOTH RENDERED CAROUSELS NOW SHOW THEIR SHAPE AT REST, not only on
	   focus -- simple-stack as a circle (40x40), flush-split as a stadium
	   (66x40). `.single-project__carousel-btn` is in the rest rule's
	   selector list but matches NOTHING on the live site, because
	   single-project.php emits `editorial-split__*` markup instead (see
	   KNOWN GAPS item 4); it was not and could not be measured.

	   Rest visibility took a second change: `.simple-stack__carousel-btn`
	   shipped `border: 0` on a transparent background, so nothing painted
	   its box and its radius would have shown through the focus ring
	   alone. Story 2 gave it a 1px edge for exactly this reason -- see
	   simple-stack.css's carousel docblock. If that edge is ever removed,
	   this token goes back to being invisible there. The same dependency
	   holds for flush-split and single-project, which also paint a
	   transparent background behind a 1px edge. */
	--shape-carousel-radius: 999px;
	/* Declared to complete the family. Nothing consumes it here -- the
	   discipline pill is explicitly out of scope, and its move to 3px is
	   spec-flush-split-alignment CAP-2's to apply. */
	--shape-pill-radius: 999px;
}

/* ---------------------------------------------------------------------
   THE ONE PLACE THE WHITE GROUND DOES NOT REACH, added v1.11.20.

   The hero carousel is not on the white ground any more. v1.11.19
   (spec-flush-split-alignment CAP-4) moved `.flush-split__carousel-controls`
   INSIDE `.flush-split__media`, which paints its own
   `var(--color-bg-surface)` -- #3C5158 on Flush Split White. Before that
   the controls sat in a grid row of their own, outside the media column,
   on the page's actual white. So the black ink and edge the block above
   binds were correct for where those buttons used to be, and are wrong for
   where they are now.

   MEASURED, not assumed. Black on #3C5158 is 2.51:1 -- below WCAG AA for
   text (4.5:1) AND for non-text UI (3:1). Joe reported it on sight
   ("those carousel controls will need to be the dark theme"). The values
   below are the `:root` dark treatment verbatim, which measures 8.0:1 on
   that same slate for both the button ink and the counter. Verified live
   on /projects/public-site-strategy/ before shipping.

   WHY A NARROWER SCOPE RATHER THAN EDITING THE BLOCK ABOVE. Those eight
   declarations currently have exactly one consumer on screen -- this
   carousel -- because the Portfolio archive renders no carousel at all in
   either layout (verified live at v1.11.17 against the grid: zero matches
   for all three `*__carousel-btn` selectors; the archive template emits no
   carousel markup at any URL, so the stacked layout cannot either). So
   simply flipping them to the dark values would look equivalent and would
   be a trap: it would leave
   the WHITE GROUND ITSELF claiming that a carousel on white takes
   light-on-light ink, and the first carousel ever added to the archive grid
   would render invisible. `:root` keeps describing the white ground
   correctly; this scope describes the one element that sits on a different
   ground while inside a white page.

   THE SCOPE LOST ITS GROUND-CLASS PREFIX IN v1.13.0 and that is not a
   widening to undo. It read `.joetennis-ground-white .flush-split__media`,
   which existed to keep it off the DARK Flush Split template. The two
   templates share one media column on the same `--color-bg-surface`, and
   these eight values are the dark treatment -- so on dark Flush Split
   `.joetennis-ground-dark` supplies the identical values and this scope is
   a no-op there. Ungating it is what keeps the hero carousel correct on the
   white template now that `:root` no longer hands it dark values.

   (0,2,0) beats the (0,1,0) block above on specificity, not source order,
   so it survives being moved. Only the eight roles whose value actually
   differs are re-bound -- `-rest-bg` and `-hover-bg` are `transparent` in
   both and are deliberately left alone.

   The `:disabled` opacity and the rest EDGE are untouched. That edge
   composites to 1.75:1 here and 2.15:1 on the dark template, i.e. it was
   already below the non-text floor before this change on both grounds;
   the glyph carries the affordance at 8:1 and 17:1 respectively. Raising
   it is a sitewide appearance decision about every carousel on the site,
   not something to slip in under a geometry story -- logged, not taken. */
/* THE SLATE IS NOT THE WHITE PAGE, and the binding that used to sit here
   is retired -- v1.13.10, SPEC-content-module-tabs story 1.

   It read `.flush-split__card-scene { --state-nav-hover-fg: #a855f7 }` and
   it existed for one element: `.flush-split__flip-cue`, the "More >" /
   "< Back" text that was the flip card's only visible signal that it
   turned. The cue bound the sitewide nav roles, and correcting the white
   page's nav hover would have dragged it to 1.17:1 on the card panel --
   fixing the header by breaking the card. This held it where it already
   was, 2.11:1, explicitly as a FLOOR rather than a decision.

   BOTH ELEMENTS ARE GONE. The card is a tab interface now; the scene
   element and the cue went with the mechanism. Nothing on this card reads
   a nav role, so there is no cell here left to hold.

   WHAT DOES NOT GO WITH IT, and is the reason this comment stays rather
   than being deleted whole: #3C5158 has NO purple clearing 4.5:1 --
   #8100fc 1.33:1, #7a22ce 1.17:1, #a855f7 2.11:1. That is a property of
   the ground, not of the cue that happened to expose it, and it applies to
   anything else that lands on the slate. The bench decision it was waiting
   for is still open; it simply has nothing rendering against it today. */

.flush-split__media {
	--state-carousel-rest-fg: #ffffff;
	--state-carousel-rest-bd: #ffffff;
	--state-carousel-hover-fg: #00e5ff;
	--state-carousel-hover-bd: #00e5ff;
	--state-carousel-focus-bd: #00e5ff;
	--state-carousel-focus-ring: #00e5ff;
	--state-carousel-disabled-fg: #f8fafc;
	--state-carousel-disabled-bd: #00e5ff;
}

/* AND RELEASED AGAIN AT THE STACKED BREAKPOINT, v1.13.6 (Joe).

   The block above exists for one reason: since v1.11.19 the controls sit
   INSIDE the media column, which paints `--color-bg-surface` -- so they
   are on the slate panel, not on the page, and they need the panel's
   values. At <=768px that stops being true. The layout stacks to one
   column, the controls come to rest above the text card, and
   flush-split.css drops the media column's fill at the same breakpoint --
   so the controls are back on the page floor and the panel values are
   wrong for them.

   `unset` RATHER THAN A RESTATED VALUE, and that is the whole trick.
   Custom properties are inherited, so `unset` computes to `inherit` and
   each token falls back to whatever the GROUND above it bound. No literal
   is written here, nothing has to be kept in sync, and the two templates
   get different-but-correct answers from one declaration each: Flush Split
   White's controls take `:root`'s white values (#000000 glyph and ring,
   #8100fc hover), and Flush Split's take `site-ground-dark.css`'s
   (#f8fafc glyph, translucent cyan ring) because its floor is #0a0e1a.

   THOSE ARE TWO DIFFERENT SURFACES AND THE BENCH ONLY HAS ONE OF THEM.
   The bench's "dark" ground is the SLATE panel (#3C5158) -- it is the
   column the block above was built from -- so the dark PAGE ground
   (#0a0e1a) that the stacked controls land on has no bench column to
   check against. The dark ground's own decided values are used instead.
   Do not read the bench's dark column as covering this case.

   VALUES, NOT RULES, so this stays in this sheet. The geometry half --
   dropping the fill, the 20px to the card -- is flush-split.css's, at the
   same breakpoint. Change one and check the other. */
@media (max-width: 768px) {
	.flush-split__media {
		--state-carousel-rest-fg: unset;
		--state-carousel-rest-bd: unset;
		--state-carousel-hover-fg: unset;
		--state-carousel-hover-bd: unset;
		--state-carousel-focus-bd: unset;
		--state-carousel-focus-ring: unset;
		--state-carousel-disabled-fg: unset;
		--state-carousel-disabled-bd: unset;
	}
}

/* THE BENCH DEFINITION LANDED HERE, v1.13.4. Decision 8 of
   SPEC-revision01-colour-adoption ruled the carousel controls to the States
   Bench -- "white circle, white chevron, hover #00e5ff" -- and dropped the
   export's lime next-only accent. v1.13.3 did the dropping and left the rest
   at the values this block already had, which was only half the ruling:
   #f8fafc is not white, and `rgba(0, 229, 255, 0.3)` is not a white circle,
   it is a faint cyan one carried over from the dark ground.

   BOTH FLUSH SPLIT TEMPLATES, one block, deliberately. The ruling is about
   the CONTROL, and the control is in the same situation on both -- inside a
   media column painting `--color-bg-surface`. Measured: white ink and white
   edge are 8.37:1 on Flush Split White's #3C5158 and 17.40:1 on Flush
   Split's #111827; hover #00e5ff is 5.44:1 and 11.07:1. The rest edge is
   the visible change on the DARK template -- a faint cyan hairline becomes
   a solid white ring.

   THE DISABLED PAIR IS UNTOUCHED AND UNREACHABLE. flush-split.js wraps at
   both ends (":144 -- no disabled-at-the-ends state"), so nothing on either
   template ever paints them. See the rest/disabled compositing note in the
   CAROUSEL CONTROLS block, which v1.13.4 had to correct rather than move.

   The header half, in three binding scopes.

   Nav and wordmark ink. Neve's builder sets `--color` on each
   `.item--inner` and `.wrapper` re-declares colour, so neither inherits
   from body. Scoped to the header's two rows ONLY: six more `.item--inner`
   live in the footer, and a bare selector blacks them out on the #666 band. */
body .header--row .item--inner,
body .header-menu-sidebar .item--inner { --color: #000000; }

/* The header GROUND, and the rule the whole white chrome turns on.
   Repainting `body` does NOT repaint the header: `#header-grid.hfg_header`
   paints #0A0E1A of its own through `--bgcolor`, as do the mobile drawer
   and its backdrop. Without this, the ink rule above inverts the nav to
   BLACK ON A STILL-DARK BAND -- measured ~1.1:1, strictly worse than
   shipping no header rule at all. Overriding `--bgcolor` rather than
   `background-color` keeps this on Neve's own header-builder mechanism.
   The backdrop is included here (it is part of the ground) but NOT in the
   state block below (it contains no controls). */
/* THE HEADER GROUND RULE WAS DELETED IN v1.13.0 (CAP-5). It read
   `--bgcolor: #ffffff` on the header, the drawer and the backdrop, and it
   existed for exactly one reason: Neve painted #0a0e1a there from the
   active `darkMode` palette and repainting `body` does not reach the
   header. The active palette is a light one now, so Neve paints the header
   from `var(--nv-site-bg)` natively and this rule would only have restated
   it. CAP-5's bar is that no child stylesheet carries a declaration whose
   sole purpose is covering a Neve dark ground -- so it went, rather than
   being kept as a belt-and-braces duplicate. The DARK consumers now carry
   the counter-rule instead; see site-ground-dark.css. */

/* The header's state token values. Custom properties inherit, so binding
   them on the two header containers reaches the nav links and the CTA
   without naming either. Header-scoped for the ink rule's reason: the
   footer's own "Get in touch" renders on #666 and must keep the shared
   white-ink default, which measures 5.74:1 there.

   `--state-nav-rest-fg` is DELIBERATELY ABSENT and this is not a gap: no
   rule in this sheet consumes it, because nav rest ink is left to Neve's
   own `.nav-ul li a { color: var(--color) }`, which the ink rule above
   already re-binds to #000000 (21:1). Binding the token too would give one
   cell two sources that could drift.

   `active` and `disabled` are the only button cells whose white values
   differ from the dark defaults; hover and focus render identically on both
   grounds (#000000 on #00E5FF) and are deliberately not re-stated. That
   identity is CONDITIONAL: the shared hover/focus fills resolve through
   `var(--nv-primary-accent, #00e5ff)` while the two disabled values below
   are literals, so if the Neve Customizer accent moves, hover and focus
   follow it and disabled does not. Pin them here if that ever matters.

   WHY THESE EXIST AT ALL -- carried forward from flush-split-white.css's
   retired block 5, because it is exactly the paragraph that stops a future
   editor deleting `--state-button-rest-fg` as redundant. Story 1 of
   SPEC-interactive-color-states inverted the Button/CTA sitewide: filled
   cyan at rest became GHOST at rest (transparent fill, accent edge, #ffffff
   ink). On every dark template that reads correctly. On a white header band
   the shared dark default paints white ink on white -- measured 1:1 on
   dev.joetennis.com, 2026-09-19, i.e. the "Get in touch" label disappears
   entirely and the CTA renders as an empty outlined box. Shipping the
   inversion without this block would ship that. CAP-8 is the capability
   requiring a white header with dark nav text.

   `--state-nav-hover-fg` WAS BOUND HERE AND IS NOT ANY MORE, v1.11.22. It
   carried #0b6b78 (6.20:1). Nav hover is now the marker hue on every
   ground, expressed once in `:root` as `var(--current-marker)` -- and
   `--current-marker` is #8100fc at `:root`, which inherits in here. So the
   white header
   gets its correct hover (6.29:1) by inheritance, and restating it here
   would give one cell two sources that could drift -- the same reasoning
   the `--state-nav-rest-fg` note above gives for staying absent.

   THE CTA's HOVER PAIR WAS BOUND HERE AT v1.13.3 AND IS GONE AGAIN AT
   v1.13.6. The round trip is worth keeping, because it is the clearest case
   in this spec of a capture beating a decided value and then losing.

   v1.13.3 bound `--state-button-hover-fg: #00e5ff` and
   `--state-button-hover-bg: transparent` here, from
   SPEC-revision01-colour-adoption's page/chrome table. Cyan ink only means
   anything if the fill goes with it -- cyan on cyan is 1:1 -- so both moved,
   and the hovered label landed on the white header band at 1.54:1. It
   shipped as a KNOWN, ruled fail: Joe, 2026-09-22, "deliver it as speced
   and I'll bring it up in review."

   THE REVIEW HAPPENED AND THE BENCH WON. The Interaction States Bench has
   always defined this cell as a black label on a cyan FILL -- read from its
   live database 2026-09-22, white ground, button/hover: fg #000000,
   bg #00e5ff, bd #00e5ff -- which is 13.65:1 and ground-independent. Every
   other white-ground button cell in this block already matched the bench
   exactly; these two were the only divergence, and they were the two lines
   the export had introduced. Joe ruled the bench over decision 6's cyan
   half. Deleting them restores it -- there is nothing to declare here,
   because `:root` already carries the bench's values.

   SO DO NOT RE-ADD THEM FROM decisions.md. That row is marked superseded,
   but a reader working forward from the revision01 spec alone will find a
   ruling for cyan ink and no reason here not to honour it. This is the
   reason.

   IT CLOSED A SECOND CELL FOR FREE. `--state-button-hover-bd` was left
   open at v1.13.3 -- it inherits #00e5ff, which was invisible against its
   own fill and became 1.54:1 against white once the fill went transparent,
   so the hovered button was losing its edge as well as its label. With the
   fill back, the edge sits on its own colour again and the cell is moot.

   Measured on #ffffff: #000000 ink 21:1, #0b6b78 edge 6.20:1, #8100fc nav
   hover 6.29:1, #000000 ring 21:1, #009aad 6.25:1 under black, and the CTA
   hover 13.65:1 (black on cyan, and the same figure on the footer band
   because a fill is ground-independent). */
body .hfg_header,
body .header-menu-sidebar {
	--state-button-rest-fg: #000000;
	--state-button-rest-bd: #0b6b78;
	--state-button-focus-ring: #000000;
	--state-nav-focus-ring: #000000;
	--state-button-active-bg: #009aad;
	--state-button-disabled-fg: #000000;
	--state-button-disabled-bg: #00e5ff;
}

/* =====================================================================
   TIER 3 -- the rules.
   ===================================================================== */

/* ---------------------------------------------------------------------
   THE HEADER'S BOTTOM HAIRLINE, taken over from Neve at v1.13.5.

   WHAT WENT WRONG, because the symptom and the cause are on opposite
   sides of the repo. Neve draws this line from `--nv-c-2`, a starter
   palette slot this project never decided -- CLAUDE.md has carried it as
   UNDECIDED, computed `#e9ecef`, since the palette audit. That was
   survivable while everything under the header was white: #e9ecef on
   #ffffff is 1.19:1, faint but present.

   Then v1.13.3 gave the case-study templates an #e4e4e4 band, and the
   band's top edge is EXACTLY the header's bottom edge -- measured live,
   both at y=113 on /projects/journey-mapping-program/. #e9ecef on #e4e4e4
   is 1.07:1. The line was never removed; it was camouflaged, which is why
   it reads as "we lost the hairline" rather than as a colour bug. Joe
   spotted it by eye.

   WHY AN ALPHA VALUE AND NOT A BETTER HEX. The header sits above two
   different grounds -- white on most of the site, #e4e4e4 wherever
   `--ground-band` is spent -- so no single opaque value is right on both:
   tune it for the band and it shouts on white, tune it for white and the
   band eats it again. This is exactly what `--ground-edge` is for. It
   composites against whatever is beneath it and holds its weight on
   either: 1.81:1 over the band, 1.84:1 over white. Joe ruled the 25%
   value over a fainter 12% after seeing both rendered, 2026-09-22.

   THE SECOND GROUND IS NO LONGER "a case study", v1.13.8. When this was
   written the band existed only on the nine flush-split-white templates.
   SPEC-archive-card-design named the grey `--ground-band` and spent it on
   the Portfolio archive as well, so /projects/ -- the most-visited page
   this hairline crosses -- is now a band ground too. Nothing here needed
   re-tuning, which is the whole return on choosing an alpha: the value was
   picked to survive whichever ground arrived under it, and a second one
   arrived. Confirmed by eye on /projects/ before shipping, not re-derived.
   The reason this paragraph exists at all is that the block's ARGUMENT
   depends on enumerating the grounds correctly, and an enumeration that
   says "a case study" reads as "one template" to the next person.

   SO THIS CLOSES `nv-c-2` AS A SITE COLOUR. The slot still holds Neve's
   starter value and still feeds Neve's own chrome, but nothing this theme
   renders reads it any more -- update CLAUDE.md's palette section, not
   this comment, if that ever stops being true.

   A VALUE, NOT A RULE, and the first attempt at this got it wrong in a way
   worth recording. Neve does not paint the border from a literal: it draws
   `border-bottom: var(--rowbwidth, 0) solid var(--rowbcolor)` from a
   selector shaped like `[class*="row-inner"]:not(.footer--row-inner)`,
   which is (0,2,0) -- an attribute selector plus the `:not()` argument --
   and it enqueues AFTER every child sheet. So `body .header--row-inner
   { border-bottom-color: ... }` at (0,1,1) LOSES, and it loses silently:
   the line simply stays Neve's colour. Measured, not assumed; that
   override was written, injected at this sheet's real cascade position,
   and observed to do nothing.

   Re-binding the custom property instead wins with no specificity contest
   at all. `--rowbcolor` is declared by `neve-style-inline-css` on
   `.header-main`, the PARENT row, and inherits down; a declaration on
   `.header--row-inner` is on the element that actually uses it, so
   proximity settles it -- no `body` prefix, no `!important`, nothing to
   out-specify. This is the same technique as `--color: #000000` on
   `.item--inner` further up, and the same lesson: against Neve, move the
   VALUE it reads rather than the property it paints.

   SCOPED OFF THE FOOTER FOR FREE. `--rowbcolor` also feeds `.footer-main`
   and friends; naming the header's own inner class leaves all three footer
   rows on their own value, verified live.

   BOTH HEADER ROWS, deliberately: Neve renders a desktop row and a
   hide-on-desktop row, and both carry `.header--row-inner`. One selector
   covers them because it names the class rather than either row. */
.header--row-inner {
	--rowbcolor: var(--ground-edge);
}

/* ---------------------------------------------------------------------
   NAV -- `.primary-menu-ul .menu-item a` covers the desktop header and
   the mobile off-canvas drawer, which render identical markup (see
   site-nav.css's header comment).

   REST INK IS DELIBERATELY NOT DECLARED HERE. Neve paints nav ink via
   `.nav-ul li a { color: var(--color) }`, and `--color` is what
   flush-split-white.css's rule 2 re-binds to #000000 to get dark nav text
   on its white header. Declaring `color` at rest from this sheet would beat
   that (0,2,1 vs Neve's 0,1,2) and paint near-white nav text on the white
   header -- measured ~1.0:1. The rest cell's decided value (#e2e8f0) and
   the value Neve already resolves are the same on the dark ground, so
   nothing is lost by leaving rest to Neve's own mechanism, and the white
   ground keeps working. `--state-nav-rest-fg` stays declared for the
   template overrides that will bind it.

   NO `font-weight` HERE, DELIBERATELY -- and this is not a gap against
   `state-values.md`, which does specify nav rest weight 500. That cell is
   satisfied; it is just satisfied by the Customizer rather than by a pin
   in this file.

   Measured 2026-09-19 on dev.joetennis.com: computed `font-weight` on
   `.primary-menu-ul .menu-item a` reads 500, and NO rule anywhere matches
   the anchor. The 500 arrives by INHERITANCE from Neve's
   `.builder-item .item--inner` at (0,2,0), which resolves
   `font-weight: var(--fontweight, var(--bodyfontweight))`, with
   `--fontweight: 500` set on `.builder-item--primary-menu` by
   `neve-style-inline-css` -- i.e. by a theme_mod the site owner edits in
   the Customizer, which is where they would expect to find nav
   typography.

   A `font-weight: 500` here would sit at (0,2,1) and so would not be
   winning a cascade contest -- it would be OVERRIDING INHERITANCE. The
   result: nav links become the one header item that stops responding to
   that Customizer setting. Nothing errors, nothing looks wrong, and the
   breakage surfaces only on the day someone changes the setting and the
   nav refuses to move. Joe's call, 2026-09-19: leave it to the
   Customizer. Do not re-add it by reading `state-values.md` alone.

   `border-radius` below DOES stay: it is a genuine declaration that
   nothing else makes, and story 4's nav flip was a value swap on the
   token it reads. Note what that buys and what it does not -- nothing
   paints a nav box, so the 999px is visible only as the shape of the
   focus ring. See the Family C nav note.
   --------------------------------------------------------------------- */
.primary-menu-ul .menu-item a {
	border-radius: var(--shape-nav-radius);
}

/* GAP FILLED (1 of 3): nav hover. No child sheet owned this -- Neve did,
   via `.nav-ul li:hover > .wrap > a { color: var(--hovercolor, ...) }`,
   which resolves to the primary accent. Same rendered value as the
   decided cell, so this is an ownership change, not an appearance one.

   Specificity: Neve's rule is (0,3,2) and loads later, so the obvious
   `.primary-menu-ul .menu-item a:hover` (0,3,1) would LOSE. The first
   selector below mirrors Neve's own shape at (0,4,1) and wins on class
   count; the second is the direct-hover fallback for any markup that
   lacks the `.wrap` div. */
.primary-menu-ul .menu-item:hover > .wrap > a,
.primary-menu-ul .menu-item a:hover {
	color: var(--state-nav-hover-fg);
}

/* GAP FILLED (2 of 3): nav focus. Nothing painted a ring on these links.
   Ink is NOT re-declared -- the decided focus cell keeps the rest ink, so
   declaring it would re-open the white-header problem described above. */
.primary-menu-ul .menu-item a:focus-visible {
	outline: 2px solid var(--state-nav-focus-ring);
	outline-offset: 2px;
}

/* ---------------------------------------------------------------------
   FOOTER NAV -- `.joetennis-footer-nav a`. Same menu, different class.

   THIS BAND IS THE THIRD GROUND, and this block is the one authoritative
   record of its numbers -- site-footer.css and the KNOWN GAPS note at the
   foot of this file both point here rather than restating them.

   THE BAND HAS MOVED TWICE. #666 from 2026-09-16; #21232b at v1.11.21,
   which discharged the deferred hover cell; #403f3f at v1.13.3, which is
   decision 4 of SPEC-revision01-colour-adoption. All three columns are kept
   because the comparison is the whole point -- and because the third
   column gives BACK some of what the second one bought:

                            on #666      on #21232b    on #403f3f
     white rest ink          5.74:1        15.67:1       10.50:1
     cyan hover ink        ->3.73:1<-      10.19:1        6.83:1
     cyan focus ring         3.73:1        10.19:1        6.83:1
     marker #a855f7          1.45:1         3.96:1     ->2.65:1<-
     CTA ghost edge, cyan    3.73:1        10.19:1        6.83:1
     CTA hover, blk on cyan 13.65:1        13.65:1       13.65:1  (fill, ground-independent)

   EVERY CELL EXCEPT THE FILL LOSES CONTRAST, because #403f3f is roughly
   three times the relative luminance of #21232b. Four of them can afford
   it. THE MARKER CANNOT: it falls back under the floor, to 2.65:1, and
   that is the exact cell v1.11.21 darkened this band to fix. Joe,
   2026-09-22: ship as specced, raise at review. It is a ruled trade
   logged against CAP-5, NOT an oversight, and re-darkening the band is
   not the fix to reach for without him.

   `--state-nav-hover-fg` GOES WITH IT, and no decision row names it. It
   carries the same #a855f7 on the same band, so footer nav hover drops
   3.96:1 -> 2.65:1 alongside the marker. One value, two cells; the ruling
   covered the value. Recorded here so the second cell is not discovered
   later as a surprise.

   NOT ONE TOKEN VALUE MOVED AT v1.11.21, and that is still worth keeping.
   Story 3 deferred the failing hover cell on the explicit grounds that
   "the #666 band itself may change, and tuning the ink against a ground in
   flux is work done twice". That judgement paid twice over: the band has
   now moved again, and any ink tuned to #21232b in 2026-09 would have been
   re-tuned here. The costing kept for that pass -- a paler tint around
   #A5F3FF, 4.61:1 on #666 -- is SUPERSEDED and deliberately not applied.
   Do not resurrect it; it solves a problem that no longer exists.

   AND THE GROUND BINDS ALMOST NOTHING -- TWO DECLARATIONS, AND NEITHER IS
   ABOUT THE DARK GROUND. #403f3f is lighter than #21232b was, but it is
   still a dark surface and every DARK value is still correct on it, so
   against `site-ground-dark.css`'s 52 declarations this ground needs none
   for its own sake. The block below exists for one reason only: the white
   ground -- which since v1.13.0 is `:root` itself -- binds two tokens that
   inherit all the way down into this bar, and both were chosen for the
   white page rather than for this band.

   The first draft of this story shipped with no block at all and that was
   WRONG -- caught in review. Do not delete it again on the reasoning that
   "the dark defaults are already correct here": they are, and that is not
   what these two lines are for.

   The latent exception, recorded not fixed: `--state-link-rest-fg`
   (#0f8c9d) measures 2.63:1 here -- it was 3.92:1 on #21232b -- under the
   text floor either way. No inline link
   renders in the footer -- the rule is scoped to
   `.nv-content-wrap.entry-content a` and the footer is outside it -- so
   this is unreachable today. If prose ever lands in the footer bar (the
   copyright runs through wp_kses_post, which permits anchors), this is the
   cell to bind first.

   WHAT THE BAND TRADED AWAY lives in site-footer.css's own rule, because
   it is a property of the band rather than of the states on it.
   --------------------------------------------------------------------- */
/* THE COUNTER-BINDING. Two tokens, both undoing a WHITE-ground value that
   inherits into this bar, not setting a value of this ground's own.

   The white ground is `:root` since v1.13.0, so the footer is inside it on
   EVERY page that has not opted into dark -- which is now most of the site,
   where before the inversion it was two templates. Custom properties
   inherit, so both of these arrive here with values measured against
   #ffffff. The counter-binding got strictly more load-bearing when the
   default flipped, and strictly less conditional: there is no longer a
   ground class whose absence would make it unnecessary.

   MEASURED on #403f3f, which is what this bar actually paints on those
   pages (site-footer.css is unconditional -- the band does not turn white
   with the page):

     --current-marker        white #8100fc  1.67:1   ->  #a855f7  2.65:1
     --state-nav-hover-fg    white #8100fc  1.67:1   ->  #a855f7  2.65:1
     --ground-default-ring   white #000000  2.00:1   ->  #00e5ff  6.83:1

   The marker is the LIVE one: story 6's footer marker rule reads
   `--current-marker`, so without this line the footer's "you are here"
   renders at 1.67:1 on white-ground pages and 2.65:1 everywhere else --
   the same signal at two legibilities depending on which template you
   arrived from. NEITHER clears the floor on #403f3f any more; the
   counter-binding still earns its place, because it is the difference
   between one failing cell and two different failing cells. The ring is PRE-EMPTIVE: all six focusables in this bar
   are enumerated (five nav links, the CTA) and win the baseline rule on
   specificity, verified live 2026-09-21, so nothing reads it today. It is
   bound anyway because the copyright runs through wp_kses_post and one
   anchor in that theme_mod would make the baseline ring reachable at
   1.34:1, with no code change to review.

   These are values, not rules -- the ground contract holds. */
.joetennis-site-footer {
	--current-marker: #a855f7;
	/* ADDED 2026-09-21, and checks/05 is what demanded it. The same change
	   that gave the white PAGE its own `--state-nav-hover-fg` made that
	   value inherit into this band, where `.joetennis-footer-nav a:hover`
	   consumes it -- so footer hover would have dropped to #8100fc at
	   1.67:1 on #403f3f. It is not enough to re-bind `--current-marker`
	   here: nav hover only follows the marker in blocks that declare the
	   role itself. */
	--state-nav-hover-fg: #a855f7;
	--ground-default-ring: #00e5ff;
}

.joetennis-footer-nav a {
	border-radius: var(--shape-nav-radius);
}

.joetennis-footer-nav a:hover {
	color: var(--state-nav-hover-fg);
}

/* GAP FILLED (3 of 3, first half): footer focus. site-footer.css carried
   hover but NO focus at all -- the one sheet in the inventory with that
   asymmetry. */
.joetennis-footer-nav a:focus-visible {
	outline: 2px solid var(--state-nav-focus-ring);
	outline-offset: 2px;
}

/* ---------------------------------------------------------------------
   BUTTON / CTA -- Neve's `.button.button-primary` plus form submits.

   `.button.button-primary` is the theme's only Button/CTA today: "Get in
   touch", rendered in the header by Neve's builder and in the footer by
   joetennis_child_site_footer_bar(), both from the same `button_base_*`
   theme_mods. Restyling it here changes it sitewide and changes nothing
   else.

   `button[type="submit"]` and `input[type="submit"]` are included so a
   form submit inherits the designed treatment without a later edit to
   the selector contract.

   THAT COVER STOPPED BEING FORWARD COVER AT STORY 2 OF
   SPEC-contact-page. This comment read, from 2026-09-19 until then,
   "Verified: there are NO submits in this theme's own markup -- every
   `<button>` it authors carries an explicit `type="button"`
   (archive-project.php:148,167,170; templates/flush-split.php:252,256;
   templates/original-stacked.php:292,296,300,355,359,363;
   templates/simple-stack.php:335,339), so these two selectors are
   forward cover, not a live change." The enumeration is still accurate
   for those seven files and the claim is now FALSE:
   templates/contact.php renders `<button type="submit">` on /contact/,
   so these rules paint a real control on a real page. The disabled
   block below is the one that matters -- that submit ships `disabled`
   until story 3 wires a handler.

   BARE `button` IS DELIBERATELY NOT STYLED. It would reach the carousel
   controls, the archive funnel icon (`.portfolio-archive__toolbar-icon`)
   and the chips-close control (`.portfolio-archive__chips-close`), all
   of which belong to later stories. The `[type="submit"]` attribute is
   what keeps this row from swallowing them.

   THE INVERSION: filled cyan at rest today, GHOST at rest after this
   (transparent fill, 1px accent edge, white ink) and FILLED on hover.
   Decided in the states bench; state-values.md through-line 1.

   Specificity: Neve's base is `.btn, .button.button-primary, ...` at
   (0,2,0) and its hover at (0,2,1), both in neve-style-css which loads
   after this sheet; wp-custom-css (later still) re-binds
   `--primarybtnhovercolor`/`--primarybtnhoverbg` on
   `.builder-item--button_base`. The leading `body` takes these rules to
   (0,2,1)/(0,2,2) so they win on specificity rather than order, and
   because they set the real properties rather than Neve's
   `--primarybtn*` custom properties, the wp-custom-css re-binds no
   longer reach the rendered result.

   Border width is held at 1px in EVERY state, with `border-color`
   carrying the change, so no state re-flows the button. The decided
   cells that show `bw: 0` are rendered as a transparent edge -- the
   "reserved transparent border" convention state-values.md documents. */
body .button.button-primary,
body button[type="submit"],
body input[type="submit"] {
	background-color: var(--state-button-rest-bg);
	color: var(--state-button-rest-fg);
	border-width: 1px;
	border-style: solid;
	border-color: var(--state-button-rest-bd);
	border-radius: var(--shape-button-radius);
	font-weight: 600;
}

body .button.button-primary:hover,
body button[type="submit"]:hover,
body input[type="submit"]:hover {
	background-color: var(--state-button-hover-bg);
	color: var(--state-button-hover-fg);
	border-color: var(--state-button-hover-bd);
}

body .button.button-primary:focus-visible,
body button[type="submit"]:focus-visible,
body input[type="submit"]:focus-visible {
	background-color: var(--state-button-focus-bg);
	color: var(--state-button-focus-fg);
	border-color: transparent;
	outline: 2px solid var(--state-button-focus-ring);
	outline-offset: 2px;
}

/* `:active` ships on buttons and nowhere else (activeScope=buttons). Not
   a WCAG requirement -- its value is masking latency so a slow response
   does not read as a dead click. */
body .button.button-primary:active,
body button[type="submit"]:active,
body input[type="submit"]:active {
	background-color: var(--state-button-active-bg);
	color: var(--state-button-active-fg);
	border-color: transparent;
}

/* DISABLED, AND THE ACTIVATION GUARD IT SHIPPED WITHOUT.

   `.button.button-primary` is an <a>, which `:disabled` can never match,
   so `[aria-disabled]` is included for the case where one is marked up
   that way. `opacity: 0.3` is this project's existing disabled convention
   and is what exempts the cell from the contrast floor.

   WHAT WAS MISSING, AND WHY IT WAS A DEFECT RATHER THAN A GAP. Until
   story 2 of SPEC-contact-page this rule painted "unavailable" and
   changed no behaviour: an anchor styled at 0.3 opacity still navigated
   on click and still took Tab focus, where it also took a focus ring. A
   sighted keyboard user would land on a control that looks unavailable
   and have it work; a screen-reader user would hear "dimmed" and have it
   work. WCAG 4.1.2 wants role and state to agree with behaviour. The
   deferred-work entry that recorded this was reclassified 2026-09-20 as
   BLOCKED on Contact rather than deferred, because that is the page that
   puts the theme's first submit on screen -- and it is closed here.

   TWO THINGS CHANGE, AND CSS CAN ONLY DO ONE OF THEM.

   1. `pointer-events: none` is added, which is the half CSS owns: a
      click or tap no longer reaches the element at all, so an
      aria-disabled anchor stops navigating.

   2. `tabindex="-1"` IS REQUIRED IN THE MARKUP AND NO RULE HERE CAN
      SUPPLY IT. `pointer-events` does not touch the tab order, so
      without it the element is still focusable, still rings, and is
      still activated by Enter -- keyboard users get the exact failure
      this guard exists to close, while pointer users do not. Any
      element that carries `aria-disabled="true"` in this theme must
      carry `tabindex="-1"` beside it. There is no check for this;
      the pairing is a review item.

   AND `pointer-events: none` DOES NOT STOP IMPLICIT SUBMISSION, which
   is the trap specific to the two selectors this rule has just grown.
   HTML fires a click at a form's default button when Enter is pressed
   in a text field, and it skips that step only when the button is
   natively `disabled` -- a dispatched click is not a pointer event, so
   an `aria-disabled` submit would still submit its form on Enter. That
   is why templates/contact.php ships `disabled` on its submit rather
   than the ARIA form, and why the ARIA form on a SUBMIT is only ever
   correct for a control whose form is also guarded server-side. The
   attribute selectors below exist so that a story 3 which marks the
   submit aria-disabled *while sending* inherits the paint; they are not
   a licence to use the ARIA form in place of the native one.

   `[aria-disabled="true"]` is extended to both submit selectors here.
   It was applied to `.button.button-primary` alone, which meant the two
   submit selectors added alongside it in the same commit got `:disabled`
   and not the ARIA form -- an asymmetry with no reason behind it. */
body .button.button-primary:disabled,
body .button.button-primary[aria-disabled="true"],
body button[type="submit"]:disabled,
body button[type="submit"][aria-disabled="true"],
body input[type="submit"]:disabled,
body input[type="submit"][aria-disabled="true"] {
	background-color: var(--state-button-disabled-bg);
	color: var(--state-button-disabled-fg);
	border-color: transparent;
	opacity: 0.3;
	cursor: default;
	pointer-events: none;
}

/* ---------------------------------------------------------------------
   FORM CONTROLS -- the last widget family this theme took from Neve
   wholesale, closed here. Story 2 of SPEC-contact-page (CAP-3).

   WHY THIS IS A SCOPED BLOCK AND NOT A `:root` FAMILY, decided before a
   line of it was written. The other six widgets in `token-contract.md`
   exist to keep MANY components consistent -- nav, chips, carousels and
   the CTA appear on seven templates between them, and the contract is
   what stops each template inventing its own hover. This family has ONE
   consumer: the /contact/ form. A `:root` binding plus a companion-file
   entry would be machinery serving a single site, and every ground that
   ever ships would then owe it values it has nothing to paint. So the
   tokens are declared on `.contact-page`, the rules are scoped to it,
   and NOTHING enters `:root` -- which is also why no ground contract
   moves, `checks/05`'s `:root` role scan is unaffected, and the
   `'ground' => 'dark'` count of 6 stays still.

   PROMOTING THIS IS A LATER, SMALLER JOB, and the trigger is a SECOND
   form -- Experiments, a search field, a newsletter box. At that point
   the values here are already measured and the move is a cut-and-paste
   into `token-contract.md` / `state-values.md` plus a per-ground table.
   Doing it now would be deciding a white-ground and a dark-ground column
   for controls that do not exist on either. Recorded in deferred-work.

   WHAT THE LIVE PROBE FOUND, because three of the four findings are the
   reason specific declarations below look redundant and are not. Bare
   form elements were injected into live /contact/ at v1.13.24 and every
   matching rule read off `document.styleSheets` -- the "first element of
   type X on this site" check CLAUDE.md mandates after the first bare
   `<button>` (v1.13.11) and the first `<table>` (v1.13.15). This is the
   third time that check has paid, and the second time it caught the
   SAME dormant palette slot:

     1. THE BASELINE FALLBACK AT THE FOOT OF THIS FILE DOES NOT RENDER.
        `input, select, textarea { color/background-color/border-color }`
        is bare-element (0,0,1); Neve's `form input:read-write` is
        (0,1,2) and loads after every child sheet. A live bare input
        computed border `2px #16191d` and background `#ffffff` -- Neve's
        geometry and Neve's colour, not this sheet's. The fallback is
        still correct as a floor for an element NO sheet enumerates; it
        simply never governed a form control on a page that has Neve on
        it. Nothing here relies on it.
     2. THE FOCUS INDICATOR WAS INCONSISTENT BETWEEN INPUT AND TEXTAREA.
        Neve's `input:read-write:focus` is (0,2,1) and beat the sitewide
        `body input:focus-visible`; its `textarea:focus` is (0,1,1) and
        lost. So a textarea rang and an input did not, on the same form.
     3. THE SECONDARY-ACCENT BLUE PAINTED EVERY FIELD'S FOCUS. Both of
        those Neve rules set a `box-shadow: 0 0 3px 0` and re-bind the
        border to the palette's secondary slot, computed `#2f5aae` -- a
        colour nothing in this theme reads or decided. CLAUDE.md's
        palette section records this slot as the one that "renders"
        rather than being inert; it is now three for three. The rules
        below state `box-shadow` and `border-color` in EVERY state that
        Neve paints, at a specificity that clears it outright.
     4. `fieldset` DRAWS A 2px #f1f5f9 BORDER AT 1.1:1 on this panel --
        a box that exists and cannot be seen. That is why the template
        groups the form with a <section> and `aria-labelledby` and emits
        no <fieldset> at all.

   GEOMETRY AND COLOUR ARE TAKEN OVER TOGETHER. deferred-work names the
   half-and-half -- overriding colour onto Neve's padding, border width
   and radius -- as the specific risk, so the control rule below states
   padding, border-width and border-radius beside the colours rather
   than inheriting Neve's 2px box and repainting it.

   FAMILY C, RECORDED RATHER THAN INHERITED. CAP-7's encoding is A
   ROUNDED BOX CAN BE OPERATED; A SQUARED ONE CANNOT. Two exemptions
   were taken here and BOTH WERE LIFTED ON 2026-09-30 -- the paragraphs
   are kept verbatim because the first one's REASONING still decides
   where the line sits, and the second one's still decides the value.
   The radii and the full amendment are at `--shape-input-radius` below.
     - TEXT FIELDS ARE SQUARE. You type into an input; you do not click
       it the way you click a control that does something. Rounding
       every field would spend the site's one operability signal on the
       most numerous boxes on the page and leave the submit -- the box
       that actually acts -- indistinguishable from them.
       LIFTED: fields are 11px. The argument above is why they are not
       999px, which is the number that would actually have spent the
       signal.
     - THE CHECKBOX IS SQUARE, and this one is not a judgement call: a
       round checkbox reads as a radio button, which is a different
       control with different semantics. Shape is already spoken for.
       LIFTED TO 4px ONLY, and this paragraph is the reason it is 4 and
       not 11: a softened square still reads as a square, and at 11px on
       a 22px box it would not.
   The submit keeps `--shape-button-radius` (999px) through the
   `button[type="submit"]` rules above, so the one box that acts is the
   one round box on the panel. `--shape-input-radius` and
   `--shape-check-radius` are declared in the scope below, NOT in the
   Family C block at `:root`, for the same one-consumer reason as the
   rest of this section.

   THE PROTOTYPE IS `https://claude.ai/code/artifact/f8b40415-c37d-4b3e-8079-65ea0d611431`
   ("Joe Tennis Contact Slate"), chosen by Joe 2026-09-25, and every
   value below is re-measured from it rather than re-derived. Three of
   its declarations could not be copied literally and the reasons are
   worth keeping: it read the Neve palette's grey slot directly (written
   here as the literal `#5c646b`, because `checks/05` greps this file for
   palette reads and cannot tell a comment from code); it forced its
   focus ring with `!important` (the ring is a TOKEN here, re-bound on
   the panel scope, so there is no specificity contest to win); and its
   error state keyed off a JS-applied `.is-invalid` class (this ships
   `:user-invalid`, which needs no script -- the class hook is story 3's,
   when there is a handler for it to report).
   --------------------------------------------------------------------- */

/* THE WHITE GROUND -- the page the panel sits on. No control renders
   here today: the form is entirely inside the slate panel, and these
   values exist so the family has a white-ground binding the moment one
   does (a search field, a newsletter box, an Experiments form). Measured
   on #ffffff: rest edge #5c646b 6.02:1, hover/focus edge #16191d
   17.63:1, placeholder #6b7278 4.88:1, error #b3261e 6.54:1, checked
   fill #0b6b78 6.20:1.

   THE REST EDGE IS THE DEFECT THIS FAMILY EXISTS TO FIX. `--ground-edge`
   is `rgba(0, 0, 0, 0.25)`, which composites to 1.84:1 on white -- below
   the 3:1 WCAG 1.4.11 wants for a control boundary. That value is
   correct where it is spent (a header hairline, an <hr>, a blockquote
   rule) and wrong on the edge that tells you where a field is. The
   prototype's notes reached the same number independently, which is why
   it is not re-litigated here. */
.contact-page {
	--state-input-rest-fg: #16191d;
	--state-input-rest-bg: #ffffff;
	--state-input-rest-bd: #5c646b;
	--state-input-hover-bd: #16191d;
	--state-input-focus-bd: #16191d;
	--state-input-focus-ring: #000000;
	--state-input-placeholder-fg: #6b7278;
	--state-input-invalid-bd: #b3261e;
	--state-input-invalid-bg: #fdf3f2;
	/* ERROR TEXT, added by story 3 with the handler that can produce a
	   message. A separate role from the three above because it is INK on the
	   ground rather than paint on a control, so it answers to the 4.5:1 text
	   floor rather than 1.4.11's 3:1 -- #b3261e is 6.54:1 on #ffffff.

	   NOTHING RENDERS THIS VALUE TODAY, and saying so matters here more than
	   elsewhere in this block. Every selector that reads the role is scoped
	   inside `.contact-page__form` or `.contact-page__panel`, which is to say
	   always on slate, where the rebinding below wins -- so the 6.54:1 above
	   is a figure this page never draws. It is bound for the same reason the
	   rest of the white-ground block is: so the family has a white binding
	   the moment an error message renders off the panel. Recorded plainly
	   because CLAUDE.md has a standing section on values documented as though
	   they paint while waiting for their first consumer. */
	--state-input-error-fg: #b3261e;
	--state-input-label-fg: #16191d;
	--state-input-hint-fg: #5c646b;

	--state-check-rest-bg: #ffffff;
	--state-check-rest-bd: #5c646b;
	--state-check-hover-bd: #16191d;
	--state-check-checked-bg: #0b6b78;
	--state-check-checked-bd: #0b6b78;
	--state-check-mark-fg: #ffffff;
	--state-check-label-fg: #16191d;

	/* THE FIELD RADIUS, AND THE FAMILY C EXEMPTION THAT WAS LIFTED TO GET
	   IT. Both were `0` until 2026-09-30. Joe ruled the pair above on
	   seeing the /contact/ refinement drawn against /about/, and the
	   reasoning in the Family C note above is AMENDED rather than
	   contradicted -- read it there, and read this for what survived.

	   WHAT THE ORIGINAL ARGUMENT ACTUALLY PROTECTED was the submit's
	   distinctness: "rounding every field would spend the site's one
	   operability signal on the most numerous boxes on the page and leave
	   the submit -- the box that actually acts -- indistinguishable from
	   them." That holds against a PILL and it does not hold against 11px.
	   The submit is `--shape-button-radius`, 999px, which on a 46px-tall
	   control is a full semicircle at each end; 11px on a 45px field is a
	   softened corner. The two are not confusable, so the signal is not
	   spent. Had the fields gone to 999px the argument would still win.

	   11px IS ABOUT'S FACET RADIUS and 4px is not About's at all. The
	   split About ships is BY BOX SIZE -- 28px for its six large panels,
	   11px for its four small facet boxes -- so a field takes the small
	   one and the panel around them takes the large one. The checkbox
	   goes to 4px rather than 11px because the Family C note's second
	   exemption is NOT a judgement call: at 11px on a 22px box the corner
	   is half the side and starts reading as a radio button. 4px is a
	   softened square that still reads as a square.

	   VALUES HAND-COPIED FROM about.css, WHICH IS THE COST OF THIS AND IS
	   RECORDED RATHER THAN HIDDEN. about.css:141,145 declare
	   `--about-panel-radius: 28px` and `--about-facet-radius: 11px` in its
	   own `:root`, and that sheet loads ONLY on /about/ -- so /contact/
	   cannot read them and these are a second declaration of the same two
	   numbers. `checks/06` pins `#8cc201` and has no opinion about radii,
	   so nothing catches them drifting apart. THE FIX IS TO POINT
	   about.css AT THESE TWO TOKENS when /about/ is next touched; it was
	   not done here because it is an edit to a shipped page outside this
	   refinement's scope, for no visible gain. CLAUDE.md's
	   `--primarybtnpadding` entry is the same shape of problem. */
	--shape-input-radius: 11px;
	--shape-check-radius: 4px;

	/* THE PANEL'S RADIUS, new 2026-09-30, About's large-box value. It is
	   declared here rather than in contact.css because contact.css states
	   no value this sheet could own -- and because the moment a second
	   page wants it, the token is already in the sheet every page loads. */
	--shape-panel-radius: 28px;

	/* The panel's own surface. `#3C5158` is the bench's third ground and
	   CLAUDE.md already records it as a COMPONENT SURFACE deliberately
	   kept out of the palette -- the same kind of object as the Flush
	   Split hero media and the flip cards, which spend it as
	   `--color-bg-surface` on their own roots. It is a panel on a white
	   page, not a site ground, so it takes a scoped rebinding rather than
	   a `ground` key, and `checks/05`'s count of 6 does not move. It is
	   named here rather than in contact.css because that sheet declares
	   no colour value of its own. */
	--color-form-panel-bg: #3C5158;
	--color-form-panel-fg: #ffffff;

	/* THE STATEMENT'S INK, AND IT MOVED UP A SCOPE ON 2026-09-30 BECAUSE
	   THE ELEMENT DID. It was declared on `.contact-page__panel` while the
	   <h1> lived inside the panel. The refinement moved that heading out
	   onto the white band, and the token did not follow -- so
	   `.contact-page__statement h1`'s `color: var(--state-statement-fg)`
	   referenced an undefined property, which is INVALID AT COMPUTED-VALUE
	   TIME and drops the declaration entirely. `color` then inherited
	   `.contact-page`'s `--ground-ink`, and the headline shipped to dev
	   NEAR-BLACK. Joe caught it by eye on the installed v2.0.0: "Looks
	   pretty good, beside the H1 being black not green."

	   THIS IS THE EXACT FAILURE THIS SHEET ALREADY WARNS ABOUT, a few
	   hundred lines down at `--state-input-focus-bd`: "a role bound on one
	   ground and left unbound on the other does not fall back to something
	   sensible, it falls through to a value chosen against a different
	   background." Moving an ELEMENT between grounds is the same hazard as
	   adding one, and it is less obvious, because nothing about the move
	   mentions colour. THE CHECK WORTH RUNNING when a styled element
	   changes parent: list every `var()` its rules read and confirm each
	   still resolves at the new position.

	   IT IS ON `.contact-page` RATHER THAN ON THE STATEMENT so the panel
	   keeps it too -- the panel is a descendant of this element, so a
	   declaration here covers both sides of the move and a future heading
	   put back inside the panel would still resolve.

	   AND THE CONTRAST FIGURE MOVED WITH THE ELEMENT, which is the part
	   that is a decision rather than a bug. #8cc201 measured 3.91:1 inside
	   the slate panel and clears the 3:1 large-text floor there. On the
	   white band it is 2.14:1 and does NOT. That is the ruling Joe made for
	   /about/ on 2026-09-29 with all three numbers in front of him --
	   about.css:225-236 records it verbatim, and the prototype's Layout E
	   cites it for this page. Green on white is a trade he has taken twice
	   now, deliberately. The one-line reversal is pointing this at
	   `--ground-ink`. */
	--state-statement-fg: var(--color-tab-current);
}

/* THE SLATE PANEL -- values only, no rules. Measured on #3C5158: ink
   #ffffff 8.37:1, hints and "(optional)" #d3dde1 6.06:1, ring #00e5ff
   5.44:1 (black would be 2.50:1 here, which is why the ring is a token
   rather than a literal), error edge #ffb4ab 4.93:1.

   THE FIELDS KEEP THEIR WHITE FILL ON THIS GROUND, and that is what
   makes the rest of the block short: a white box on slate is an 8.37:1
   boundary by itself, so the EDGE only has to mark state rather than
   carry the control's presence. That is also why `--state-input-rest-fg`
   and `-bg` are not re-bound -- ink on the fill has not changed ground.

   THE LEFT RULE STAYS `#b3261e` (inherited, deliberately not restated):
   it is drawn INSIDE the field, on the white fill, where it measures
   6.54:1. The error EDGE moves to #ffb4ab because that one is drawn
   against the slate, where the dark red would be 1.32:1. One state, two
   surfaces, two values -- which is the whole reason it is two tokens.

   THE SUBMIT IS NOT RE-BOUND AT ALL. `:root`'s Button/CTA values are
   already the dark-ground ones -- white ink, cyan edge, filling cyan
   with black ink on hover -- so the form's submit inherits the shipped
   treatment unchanged on this panel and needs nothing here. Its DISABLED
   pair is the exception: `:root` disables to a #00b8cc fill, which is a
   filled cyan pill at 0.3 opacity on slate. The ghost form is re-bound
   so the disabled submit reads as the same ghost shape the rest state
   has, dimmed. The 0.3 opacity itself is untouched -- it is this
   project's disabled convention and it is what exempts the cell from the
   contrast floor. */
.contact-page__panel {
	--state-input-rest-bd: #ffffff;
	--state-input-hover-bd: #00e5ff;
	/* FOCUS RE-BINDS TOO, AND IT WAS THE ONE ROLE THIS BLOCK MISSED.
	   Measured on the installed v1.13.25: clicking a field dropped its
	   border from #ffffff at 8.37:1 to the white-ground value #16191d at
	   **2.11:1** on this panel -- the control's boundary all but erased at
	   the moment of interaction. The cyan ring still marked focus, so this
	   was never a focus-INDICATOR failure; it was the edge disappearing
	   underneath a perfectly good ring.

	   This is the failure the ground contract exists to prevent, in
	   miniature: a role bound on one ground and left unbound on the other
	   does not fall back to something sensible, it falls through to a value
	   chosen against a different background. Cyan matches hover, so the
	   edge now reinforces on the way in rather than contradicting. */
	--state-input-focus-bd: #00e5ff;
	--state-input-focus-ring: #00e5ff;
	--state-input-invalid-bd: #ffb4ab;
	--state-input-invalid-bg: #fff5f4;
	--state-input-hint-fg: #d3dde1;
	/* ERROR TEXT ON SLATE. #ffb4ab is 4.93:1 on #3C5158, which clears the
	   4.5:1 text floor; the white-ground #b3261e would be 1.32:1 here and
	   unreadable. Same shape as the error EDGE directly above -- one state,
	   two surfaces, two values -- and the same reason it is a token. */
	--state-input-error-fg: #ffb4ab;

	/* THE FIELD LABELS RESOLVE FROM THE ONE DECLARATION, never a second
	   copy. `--color-tab-current` is declared once at :507 of this file
	   and `checks/06` pins that value to exactly two role names across
	   every theme/*.css -- a third declaration fails the suite, which is
	   the check doing its job rather than an obstacle. Reading the token
	   also means these labels move with the tab kickers and the About
	   medallion's current arc if that value is ever re-ruled, which is
	   Joe's 2026-09-23 coupling ruling extended to a fourth site. */
	--state-input-label-fg: var(--color-tab-current);

	/* THE PAGE'S HEADLINE, ADDED BY STORY 5, AND IT READS THE SAME ONE
	   DECLARATION for the same reason the labels above do -- a third
	   literal `#8cc201` anywhere in theme/*.css fails `checks/06`, and a
	   `var()` reference is not a declaration, so this costs nothing.

	   IT IS A SEPARATE ROLE FROM THE LABEL and not an alias of it, because
	   the two answer to different contrast floors on the same ground.
	   #8cc201 measures 3.92:1 on the slate: the labels clear the 3:1
	   large-text floor only by being 19px/700, which is why that rule
	   carries a "do not tidy it smaller" warning. This headline is
	   clamp(32px, 4.2vw, 56px) at weight 800 and clears the same floor with
	   room to spare at every viewport. Binding them together would invite a
	   future change to one to be reasoned about from the other's margin. */

	--state-check-rest-bg: transparent;
	--state-check-rest-bd: #ffffff;
	--state-check-hover-bd: #00e5ff;
	--state-check-checked-bg: #00e5ff;
	--state-check-checked-bd: #00e5ff;
	--state-check-mark-fg: #05070d;
	--state-check-label-fg: #ffffff;

	--state-button-disabled-bg: transparent;
	--state-button-disabled-fg: #ffffff;
	--state-button-disabled-bd: #00e5ff;

	/* THE BASELINE RING IS COUNTER-BOUND, PRE-EMPTIVELY, and the precedent
	   is `.joetennis-site-footer` doing exactly this at :1183 for exactly
	   this reason. Every control on this panel is enumerated above and wins
	   the baseline rules on specificity, so nothing reads this today. It is
	   bound anyway because `--ground-default-ring` is #000000 at `:root` --
	   chosen against a white page, where it is 21:1 -- and on #3C5158 it
	   measures 2.50:1, under the 3:1 floor a focus indicator has to clear.
	   So any focusable this panel grows that ISN'T enumerated here gets an
	   unreadable ring with no code change to review: story 3's error
	   summary (a `tabindex="-1"` container that takes focus on a failed
	   submit), story 4's Privacy Statement link, a <summary>, a skip
	   target. #00e5ff is 5.44:1 here. */
	--ground-default-ring: #00e5ff;
}

/* THE DISABLED SUBMIT KEEPS ITS EDGE, and this rule exists because the
   rebinding directly above would otherwise erase the control.

   The sitewide disabled rule states `border-color: transparent` as a
   literal, and that is correct where it was written: the CTA's disabled
   cell has a FILL (#00b8cc), so the box is drawn by the fill and an edge
   would be redundant. This panel rebinds that fill to `transparent`, so
   with the shipped literal still in force the submit would have no fill
   AND no edge -- a faint line of text where a button should be. Measured
   in the harness before this rule existed: `background-color rgba(0,0,0,0)`,
   `border-color rgba(0,0,0,0)`.

   THE BUTTON HAS TO STILL READ AS A BUTTON. Its whole job in story 2 is
   to say "this exists and is not connected yet"; a control that dissolves
   into text says "this page is unfinished" instead, which is the reading
   the note beside it is there to prevent.

   THIS IS NOT THE DISABLED-CONVENTION DIVERGENCE THE STORY HOLDS FOR
   JOE. That question is whether `opacity: 0.3` gives way to the
   prototype's explicit greys -- which would change the shipped CTA too,
   and is untouched here. The opacity, the tokens and the rule above all
   stand; this restores a border the panel's own rebinding removed, and it
   reaches nothing outside `.contact-page__form`. Specificity (0,3,1)
   clears the sitewide rule's (0,2,1).

   IT HAS NO LIVE CONSUMER SINCE v1.13.30, and the paragraph above is
   therefore history rather than description: story 3 removed the `disabled`
   attribute from the submit, so nothing on this site renders a disabled
   button today. The rule is KEPT because what it corrects is a real
   consequence of the panel's rebinding, which is still in force -- the next
   thing to disable this submit (a "sending" state, an `aria-disabled` pass)
   would otherwise find a control with no fill and no edge. Forward cover,
   stated as forward cover. */
body .contact-page__form .contact-page__submit:disabled {
	border-color: var(--state-button-disabled-bd);
}

/* The panel's title. Stated here rather than left to inherit because Neve
   paints headings from its own typography theme_mods and this panel's ground
   is not the page's.

   IT WAS TWO ROLES UNTIL v1.13.31. The second was `.contact-page__panel-sub`,
   the line reading "Only your name and email are required", removed along with
   both field hints on Joe's call -- the panel now states its name and asks its
   questions with nothing between them. Deleted rather than kept as forward
   cover, unlike the hint role below: a panel subheading is a thing this page
   had, not a member of the form-control family the story designed. */
body .contact-page__panel-title {
	color: var(--color-form-panel-fg);
}

/* THE FIELD LABEL, AND THE SIZE IS THE ACCESSIBILITY FIX -- DO NOT TIDY
   IT SMALLER OR LIGHTER.

   `--color-tab-current` measures 3.92:1 on the slate. That is under the
   4.5:1 normal-text floor and over the 3:1 LARGE-text floor, and WCAG's
   large-text threshold is 18.66px BOLD (14pt) or 24px regular. 19px at
   weight 700 clears it; 19px at 600, or 18px at 700, does not, and
   neither failure is visible -- the label looks identical and the cell
   silently stops passing. The green was ruled in at this ratio twice
   before (flush-split-white.css:385, decision 7, and again when the
   About medallion adopted it), each time as an improvement on the
   purple's 2.11:1 rather than as a pass; 19px/700 is what turns it into
   a pass on this panel. White at 8.37:1 was rendered alongside it and
   Joe chose the green, 2026-09-25.

   700 IS REQUESTED BY THIS THEME AS OF THIS STORY. functions.php asked
   Google Fonts for `Inter:wght@400;500;600` and this rule is the first
   thing in the theme to set 700 in that family. CSS font matching would
   have supplied 600 with no error and no missing file -- the exact
   failure CLAUDE.md's typography entry records for the archive card's
   ExtraBold -- which would have rendered a label that looks bold, is
   not, and fails the contrast floor it was sized to clear. `700` was
   added to the request in the same commit. */
body .contact-page__form .contact-page__label {
	font-family: var(--font-body);
	font-size: 19px;
	font-weight: 700;
	line-height: 1.3;
	letter-spacing: 0.2px;
	color: var(--state-input-label-fg);
}

/* THE REQUIRED MARK, AND IT REPLACED `.contact-page__optional` OUTRIGHT AT
   STORY 5. The convention inverted: the drawing marks the two REQUIRED
   fields with a green asterisk and marks nothing optional, where this form
   previously marked the optional ones in words and asserted "there are no
   asterisks anywhere on this form".

   THAT IS THE THIRD ROUND ON THIS ONE QUESTION and the rounds are not a
   contradiction, so they are worth naming once: story 2 said it in a panel
   sub-line plus two field marks, v1.13.31 removed the sub-line and one mark
   on Joe's call, and the drawing replaces the MECHANISM the other two were
   reaching for. All three were after the same thing.

   IT INHERITS THE LABEL'S 700 AND ITS COLOUR, which is the whole of the
   rule. The mark is part of the label's own name in the drawing -- same
   weight, same green, three pixels out -- so it states only the offset.
   The `.contact-page__required-note` beside the Send button is the same
   class and gets the same treatment, which is the point: the legend has to
   look like the thing it is a legend for.

   IT IS `aria-hidden` IN THE MARKUP AND THAT IS NOT THIS FILE'S BUSINESS
   TO ENFORCE, but it is the reason a purely visual mark is acceptable at
   all: the input's own `required` attribute is what announces the field,
   and an asterisk read aloud as "star" is noise. */
body .contact-page__form .contact-page__required {
	margin-left: 3px;
	color: var(--state-input-label-fg);
}

/* THE HINT ROLE HAS NO CONSUMER AS OF v1.13.31, and it is kept anyway.
   Both hints this form carried -- "Used only to reply to you" under the email
   field and "If you would rather talk" under phone -- were removed on Joe's
   call, so nothing on this page renders this class today.

   IT STAYS because a hint is a member of the form-control family CAP-3
   designed, not a one-off line of this page's copy: the role is bound on both
   grounds (#5c646b at 4.88:1 on white, #d3dde1 at 6.06:1 on slate), the
   "(optional)" mark above resolves its colour from the same token, and the
   next field anywhere on this site that needs a hint should not have to
   redecide what one looks like. Compare the panel sub-line above, which was
   deleted: that was this page's, this is the family's.

   Stated plainly rather than left for a reader to discover, because an
   unannotated rule matching nothing is indistinguishable from a rule whose
   selector has drifted. */
body .contact-page__form .contact-page__hint {
	font-family: var(--font-body);
	font-size: 13px;
	line-height: 1.45;
	color: var(--state-input-hint-fg);
}

/* THE TEXT CONTROLS -- text, email, tel and textarea in one rule,
   reached through a class rather than through element or attribute
   selectors.

   SPECIFICITY IS THE WHOLE REASON FOR THE CLASS. Neve's base is
   `form input:read-write` at (0,1,2) and its focus at (0,2,1), both in
   neve-style-css, which loads AFTER every child sheet -- so a rule that
   merely TIES loses. `body .contact-page__form .contact-page__control`
   is (0,2,1), which beats the base on class count; the state rules below
   add a pseudo-class and land at (0,3,1), which beats the focus rule the
   same way. An element-selector group would have been (0,1,1) for
   `textarea` and would have lost to Neve's base silently, painting
   nothing and looking like a cascade bug.

   BORDER WIDTH IS 1px IN EVERY STATE INCLUDING ERROR, and the prototype
   is deliberately not followed here: it bumps the slate error edge to
   2px. This sheet's standing convention is that width is held and
   `border-color` carries the change, so no state re-flows the control --
   the same reason the Button/CTA block above holds 1px through four
   states. The error state has two other carriers (the fill and the left
   rule), so it loses nothing by holding the width. */
body .contact-page__form .contact-page__control {
	box-sizing: border-box;
	font-family: var(--font-body);
	/* 18px, About's body size, up from 17 on 2026-09-30. 17 was measured
	   to hold a full email address in the narrower column story 5 drew
	   against; the page has since taken the site band, so the field is
	   WIDER at the same breakpoint and the extra pixel costs nothing it
	   was protecting. */
	font-size: 18px;
	font-weight: 400;
	line-height: 1.5;
	letter-spacing: 0.2px;
	color: var(--state-input-rest-fg);
	background-color: var(--state-input-rest-bg);
	border: 1px solid var(--state-input-rest-bd);
	border-radius: var(--shape-input-radius);
	padding: 12px 14px;
	box-shadow: none;
}

/* THE TEXTAREA RESIZES VERTICALLY ONLY, and the `max-width` is a second
   belt on the same failure. A textarea's UA default is `resize: both`, so
   the drag handle lets a visitor pull it WIDER than its grid track -- out
   through the slate panel's padding and past its edge, on a panel whose
   whole job is to contain the form. `resize: vertical` is what the
   prototype declared and it was lost in transcription; `max-width: 100%`
   holds every control inside its track for the other routes to the same
   place (a long `value`, a wide `placeholder`, a future `size` attribute),
   because a grid track sized `minmax(0, 1fr)` stops the track blowing out
   but does not stop a child overflowing it. */
body .contact-page__form .contact-page__control {
	max-width: 100%;
}

body .contact-page__form textarea.contact-page__control {
	resize: vertical;
}

body .contact-page__form .contact-page__control:hover {
	border-color: var(--state-input-hover-bd);
}

/* A BARE `:focus` RULE, AND IT IS THE ONE EXCEPTION THIS SHEET ALLOWS
   ITSELF -- read the next sentence before deleting it. It declares no
   state paint of its own that `:focus-visible` does not: what it does is
   REMOVE Neve's, which is applied on bare `:focus` and therefore paints
   on a mouse click, where `:focus-visible` does not match. Without it, a
   visitor who clicks into a field gets a 3px #2f5aae glow and a #2f5aae
   border from a palette slot nothing in this theme reads, and the
   keyboard ring below never fires to cover it. `box-shadow` and
   `border-color` are both stated because Neve sets both. */
body .contact-page__form .contact-page__control:focus {
	border-color: var(--state-input-focus-bd);
	box-shadow: none;
}

/* AUTOFILL, AND IT IS NOT HYPOTHETICAL ON THIS FORM. The name and email
   fields carry `autocomplete="name"` and `autocomplete="email"`, which is
   an invitation -- so the browser filling them is the expected path, not
   an edge. Chrome then paints its OWN background and ink over the control
   (a pale yellow historically, the UA's accent now) and neither obeys a
   `background-color` declaration, which is why the mask below is an inset
   `box-shadow` 100px deep rather than a colour: the shadow is the one
   property the UA's autofill layer does not replace.
   `-webkit-text-fill-color` is likewise the only thing that moves the ink,
   since `color` is overridden too.

   WHAT IT COSTS IF LEFT: every measured figure on the slate panel is void
   for the two fields most likely to be filled -- the ink is the UA's, the
   fill is the UA's, and the white edge that carries the control's presence
   against #3C5158 sits around a colour nobody chose.

   TWO RULES, NOT ONE COMMA-JOINED LIST, AND THAT IS LOAD-BEARING. A
   selector a browser does not recognise invalidates the WHOLE rule it sits
   in, so pairing the standard `:autofill` with `:-webkit-autofill` in one
   list would silently drop the declarations in every engine that knows
   only one of them. Kept apart, each engine takes the rule it understands
   and ignores the other. */
body .contact-page__form .contact-page__control:-webkit-autofill,
body .contact-page__form .contact-page__control:-webkit-autofill:hover,
body .contact-page__form .contact-page__control:-webkit-autofill:focus {
	-webkit-text-fill-color: var(--state-input-rest-fg);
	caret-color: var(--state-input-rest-fg);
	border-color: var(--state-input-rest-bd);
	box-shadow: inset 0 0 0 100px var(--state-input-rest-bg);
}

body .contact-page__form .contact-page__control:autofill,
body .contact-page__form .contact-page__control:autofill:hover,
body .contact-page__form .contact-page__control:autofill:focus {
	-webkit-text-fill-color: var(--state-input-rest-fg);
	caret-color: var(--state-input-rest-fg);
	border-color: var(--state-input-rest-bd);
	box-shadow: inset 0 0 0 100px var(--state-input-rest-bg);
}

body .contact-page__form .contact-page__control:focus-visible {
	outline: 2px solid var(--state-input-focus-ring);
	outline-offset: 2px;
}

/* `opacity: 1` because Firefox still applies a UA opacity to
   ::placeholder, which would composite the decided value into a lighter
   one and quietly undo the ratio it was chosen for. No field on this
   page carries a `placeholder` attribute -- a hint that vanishes on the
   first keystroke is not a label, and every field here has a real one --
   so this rule is the FAMILY being decided rather than this page being
   painted. It is bound anyway, on the same principle the chip roles are:
   a ground supplies values whether or not a component is on screen. The
   default it replaces composites to #8a8c8e, 3.37:1, under the text
   floor. */
body .contact-page__form .contact-page__control::placeholder {
	color: var(--state-input-placeholder-fg);
	opacity: 1;
}

/* `:user-invalid`, NEVER `:invalid`, and the difference is the whole
   behaviour. A required field is `:invalid` from first paint, so
   `:invalid` would paint an error on a form nobody has touched --
   scolding the visitor for not yet having done the thing. `:user-invalid`
   matches only after the field has been interacted with and blurred, or
   after a submit attempt, which is exactly the I/O matrix's first two
   rows.

   COLOUR IS NEVER THE ONLY SIGNAL, AND THE CARRIER CHANGED ON
   2026-09-30. It was `box-shadow: inset 4px 0 0` -- a rule down the
   field's left edge, drawn on the field's own fill rather than against
   the ground, which is why it kept ONE value on both grounds. The
   non-colour carrier is now BORDER WIDTH: 1px at rest, 2px when
   invalid, which survives a greyscale render and a colour-vision
   deficiency the same way. The error MESSAGE is the other carrier and
   it is story 3's: a message has to be honest about what failed, which
   needs the handler that decides it.

   WHY IT CHANGED, because "we rounded the corners" is not a good enough
   reason on its own and this one is mechanical. `--shape-input-radius`
   went from 0 to 11px in the same change, and an `inset` box-shadow
   follows the border-box's CORNER CURVE -- so a 4px left rule on an
   11px radius is pinched to nothing at both ends of its own run and
   reads as a smear rather than a rule. There is no way to draw a
   straight-edged inset rule inside a rounded box. The choice was
   therefore the radius or the rule, and Joe took the radius knowing the
   cost; the 2px edge is what replaces the signal rather than dropping
   it. `--state-input-invalid-rule` was deleted with it -- it had no
   other consumer.

   THE ONE THING THE SWAP GIVES UP is ground-independence. The left rule
   sat on the field's own near-white fill, so `#b3261e` worked on the
   white page and on the slate panel alike. A BORDER is seen against
   whatever is behind the field, so it has to take
   `--state-input-invalid-bd`, which is bound per ground -- `#b3261e` on
   white, `#ffb4ab` on the panel. That is the ground contract working as
   designed rather than a regression, but it is one more value that has
   to be right on two surfaces instead of one.

   IT IS LAST IN THE STATE ORDER ON PURPOSE. This rule and the `:focus`
   pair share specificity (0,3,1), so source order settles a field that
   is both focused and invalid -- and the error edge is the one that
   should win, with the keyboard ring still painting over it from the
   `outline` property, which nothing here touches.

   KEEP IT IN ITS OWN RULE. An unsupported selector invalidates the whole
   selector LIST it appears in, so grouping `:user-invalid` with anything
   else would take that rule down with it on any engine that does not
   know the pseudo-class. */
body .contact-page__form .contact-page__control:user-invalid {
	border-color: var(--state-input-invalid-bd);
	background-color: var(--state-input-invalid-bg);
	border-width: 2px;
}

/* `.is-invalid` -- the SERVER's error paint, and story 2 left the hook here
   deliberately for story 3 to fill.

   WHY THE PSEUDO-CLASS ABOVE IS NOT ENOUGH. `:user-invalid` matches only
   after the visitor has interacted with a field IN THIS DOCUMENT. A
   re-rendered page after a rejected POST is a NEW document nobody has
   touched yet, so every field would paint clean while the summary at the top
   said the form was not sent. The handler adds this class to the fields it
   rejected, which is the only way server-decided state can reach the paint.
   It also covers what the pseudo-class cannot know: an address that is
   syntactically fine and still not an address.

   IT IS ITS OWN RULE AND MUST STAY ONE. The declarations are identical to
   the rule above, and grouping them into one selector list would be the
   obvious tidy -- and would take BOTH down on any engine that does not know
   `:user-invalid`, because an unsupported selector invalidates the whole
   list it appears in. That is the note directly above this one, and this is
   the case it was written for. Duplicated on purpose. */
body .contact-page__form .contact-page__control.is-invalid {
	border-color: var(--state-input-invalid-bd);
	background-color: var(--state-input-invalid-bg);
	border-width: 2px;
}

/* ---------------------------------------------------------------------
   THE HANDLER'S OWN TEXT -- per-field errors, the whole-form notice, the
   error summary and the confirmation. Story 3 (CAP-4).

   COLOUR ONLY. Every one of these elements takes its layout from
   contact.css, which declares no colour value of its own; the ink is here
   because this sheet is the single owner of state, and an error message is
   state made readable. Each resolves from the role above, so all four move
   together on the white page and on the slate panel without restating a
   value.

   THE SUMMARY'S LINKS ARE NOT STYLED HERE. They are anchors, and anchors on
   this page are enumerated in contact.css against the WHITE ground -- teal
   ink that would measure badly on slate. Overriding them from this sheet
   would mean out-specifying rules in a sheet that loads after it, so the
   correction lives beside the rules it corrects instead. See contact.css.
   --------------------------------------------------------------------- */
body .contact-page__form .contact-page__error,
body .contact-page__panel .contact-page__notice,
body .contact-page__panel .contact-page__summary-title {
	color: var(--state-input-error-fg);
}

/* The summary's box, drawn so the block reads as one thing rather than as a
   loose heading above a list. The rule is the same non-colour carrier the
   fields use -- an edge on the leading side, not a fill -- which is why
   there is no background here to measure against.

   ITS WIDTH AND THE PADDING THAT CLEARS IT LIVE IN DIFFERENT FILES, which is
   a seam worth naming since neither file's banner would lead you to expect
   it: this sheet owns colour and that is why the border is here, while
   `contact.css` owns layout and carries the `padding-left: 16px` that was
   sized against these 4px. Change one and the other no longer matches.
   contact.css says the same thing at its end of the seam. */
body .contact-page__panel .contact-page__summary {
	border-left: 4px solid var(--state-input-error-fg);
}

/* The confirmation takes the panel's own ink. Stated rather than left to
   inherit for the reason the panel's title and subheading are: Neve paints
   this panel's descendants from typography theme_mods chosen against the
   page, and this ground is not the page. */
body .contact-page__panel .contact-page__sent {
	color: var(--color-form-panel-fg);
}

/* THE CHECKBOX -- the site's first, and the one control here drawn from
   scratch. `appearance: none` is what makes the box paintable at all; a
   native checkbox takes its colour from the platform and ignores
   everything below. The tick is a rotated two-sided box on ::after
   rather than an SVG or a character, so it needs no font, no asset and
   no additional element, and it scales with the border width.

   `scale(0)` AT REST RATHER THAN `display: none`, so the mark has a
   geometry to grow from; nothing animates it today, which is why there
   is no reduced-motion counterpart in this file. */
body .contact-page__form .contact-page__check-input {
	appearance: none;
	-webkit-appearance: none;
	flex: none;
	width: 22px;
	height: 22px;
	margin: 1px 0 0;
	display: grid;
	place-items: center;
	background-color: var(--state-check-rest-bg);
	border: 2px solid var(--state-check-rest-bd);
	border-radius: var(--shape-check-radius);
	cursor: pointer;
}

body .contact-page__form .contact-page__check-input::after {
	content: "";
	width: 6px;
	height: 11px;
	margin-top: -2px;
	border: solid var(--state-check-mark-fg);
	border-width: 0 2.5px 2.5px 0;
	transform: rotate(45deg) scale(0);
}

body .contact-page__form .contact-page__check-input:hover {
	border-color: var(--state-check-hover-bd);
}

body .contact-page__form .contact-page__check-input:checked {
	background-color: var(--state-check-checked-bg);
	border-color: var(--state-check-checked-bd);
}

body .contact-page__form .contact-page__check-input:checked::after {
	transform: rotate(45deg) scale(1);
}

body .contact-page__form .contact-page__check-input:focus-visible {
	outline: 2px solid var(--state-input-focus-ring);
	outline-offset: 2px;
}

/* THE CHECKBOX'S TEXT IS AN ANSWER, NOT A FIELD LABEL, which is why it
   is the one label on this panel that does not take the green label ink.
   It reads as a sentence the visitor is agreeing with, so it takes the
   panel's body ink at 8.37:1 and a normal reading size; the green marks
   the NAME of a field you are about to fill in, and this is not one.
   `cursor: pointer` because the whole label is a click target -- which
   is the <label for> doing its job, not a rule here. */
body .contact-page__form .contact-page__check-label {
	font-family: var(--font-body);
	font-size: 16px;
	font-weight: 500;
	line-height: 1.45;
	letter-spacing: 0.2px;
	color: var(--state-check-label-fg);
	cursor: pointer;
}

/* THE NOTE BESIDE THE SUBMIT IS GONE, and so is this rule. Story 2's own
   comment here ended "It goes when the handler lands", and story 3 is the
   handler landing: the sentence it styled said sending was not connected
   yet, which stopped being true. Recorded rather than silently deleted
   because a reader comparing this file against story 2's diff will look for
   it. */

/* ---------------------------------------------------------------------
   INLINE BODY-COPY LINKS -- the third gap, and the only one with no
   child-theme owner at all: a grep for a bare `a {` / `a:` selector
   across theme/*.css returned nothing.

   Two selector groups:
   1. The four `single-project` prose regions, which emit their own
      markup.
   2. Neve's content wrapper. CONFIRMED LIVE 2026-09-19 against
      dev.joetennis.com: `.nv-content-wrap.entry-content`, exactly one per
      page. About / Blog / `experiment` render their prose through Neve
      markup, because the four modern `project` templates `esc_html()`
      theirs (templates/simple-stack.php:397-403) -- so without this group
      those pages have no owned link treatment at all. If a future Neve
      release renames that wrapper, these links fall back to Neve's own
      styling: degraded, not broken.

   Specificity: Neve's `.entry-content a:not([class])` (0,2,1) sets
   `--linkdeco: underline`, and its `a:hover, a:focus` (0,1,1) tints every
   anchor to the SECONDARY accent. The `body` prefix plus `:not(.button)`
   clears both. `:not(.button)` also keeps a CTA dropped into prose on the
   button rules above.

   TWO THINGS TO KNOW ABOUT THESE VALUES:
   (a) The decided cells REMOVE the rest underline on both grounds. That
       is a real change here -- Neve underlines unclassed content links
       today.
   (b) With no rest underline, WCAG 1.4.1 falls back to colour alone, and
       the comparison it makes is against the SURROUNDING BODY TEXT, not
       the page. The dark-ground rest ink was re-derived for exactly this
       reason and is now #0f8c9d at 3.24:1 against body ink -- see the
       long note on `--state-link-rest-fg` in the token block above for
       the derivation and the luminance window. The white ground already
       passed at 3.39:1 and is unchanged. */
.single-project__intro-summary a,
.single-project__psr-body a,
.single-project__details-body a,
.single-project__toggle-body a,
body .nv-content-wrap.entry-content a:not(.button):not(.wp-block-button__link) {
	color: var(--state-link-rest-fg);
	border-radius: var(--shape-link-radius);
	font-weight: 400;
	text-decoration: none;
	--linkdeco: none; /* Neve renders `text-decoration: var(--linkdeco)`. */
}

.single-project__intro-summary a:hover,
.single-project__psr-body a:hover,
.single-project__details-body a:hover,
.single-project__toggle-body a:hover,
body .nv-content-wrap.entry-content a:not(.button):not(.wp-block-button__link):hover {
	color: var(--state-link-hover-fg);
	text-decoration: underline;
	text-decoration-thickness: 2px;
	text-underline-offset: 2px;
	--linkdeco: underline;
}

.single-project__intro-summary a:focus-visible,
.single-project__psr-body a:focus-visible,
.single-project__details-body a:focus-visible,
.single-project__toggle-body a:focus-visible,
body .nv-content-wrap.entry-content a:not(.button):not(.wp-block-button__link):focus-visible {
	color: var(--state-link-focus-fg);
	text-decoration: underline;
	--linkdeco: underline;
	outline: 2px solid var(--state-link-focus-ring);
	outline-offset: 2px;
}

/* ---------------------------------------------------------------------
   BREADCRUMB LINKS -- the second control that hovered more than one way,
   and the one story 1 had no tokens for. flush-split.css moved `color`
   through a BARE `var(--nv-primary-accent)` -- no local fallback, so an
   unresolved Neve variable dropped the declaration and left the link
   with no hover affordance at all. simple-stack.css used the mandatory
   fallback pattern. Same design, two idioms, one of them a latent
   silent no-op; both now resolve from the tokens above.

   Rest ink stays in the template sheets (`color: inherit`): a breadcrumb
   at rest is ground ink, not a state. No ring is declared -- the
   baseline `a:focus-visible` already paints one, measured against the
   ground rather than against this ink.

   RECONCILED IN STORY 3. flush-split-white.css used to override these
   at (0,3,1) with a text-decoration hover instead of a colour one -- the
   one cell on the site where a ground changed the PROPERTY rather than
   the value, which is what CAP-2 forbids. That override is retired; the
   white ground binds these and moves `color`, exactly as the dark ground
   does. (Both grounds bound BOTH roles to #0b6b78 / cyan when that was
   written; as of 2026-09-21 hover follows `--current-marker` per ground
   -- #8100fc here at 6.29:1, #a855f7 on dark at 4.87:1 -- while focus
   keeps #0b6b78 at 6.20:1. See the role declarations for why hover
   moved and why focus did not.)
   It also closes the failure mode story 2 opened here: with the white
   override in place and no colour binding, these rules resolved to cyan
   on white at 1.54:1, carried only by that underline. */
.flush-split__breadcrumb-item a:hover,
.simple-stack__breadcrumb-item a:hover {
	color: var(--state-breadcrumb-hover-fg);
}

.flush-split__breadcrumb-item a:focus-visible,
.simple-stack__breadcrumb-item a:focus-visible {
	color: var(--state-breadcrumb-focus-fg);
}

/* ---------------------------------------------------------------------
   CAROUSEL CONTROLS -- the one control that hovered three different ways
   in three templates, which is the clearest evidence of the drift this
   spec exists to remove. As of story 2 it hovers ONE way: the three
   template sheets keep only geometry, and every colour, the radius and
   the glyph weight resolve from here.

   THREE THINGS STORY 2 ADDED, all visible:
     - `font-weight`. No `__carousel-btn` rule in any sheet declared it,
       so the glyphs took Neve's button weight (600, measured live). The
       decided cell is 500.
     - `border-color` on `:disabled`, the decided disabled cell. This
       used to RENDER AS NOTHING and the arithmetic for that is worth
       keeping, because v1.13.4 broke it: the rest edge was
       `rgba(0, 229, 255, 0.3)` at element opacity 1 and disabled is a
       full-alpha accent at element opacity 0.3, so both landed at an
       effective alpha of 0.3 and the edge did not move. The ink did not
       move either -- `--state-carousel-disabled-fg` was the same `#f8fafc`
       as rest -- so `opacity: 0.3` alone was what made a disabled button
       read as disabled.

       SINCE v1.13.4 THE REST EDGE IS SOLID `#ffffff` AND THAT SYMMETRY IS
       GONE. A disabled button would now show a faded CYAN edge against a
       solid white one, i.e. it would read as "hovered and faded" rather
       than as disabled. NOBODY CAN SEE IT: flush-split.js wraps at both
       ends and never disables either button, and the one carousel any JS
       does disable (`single-project.js`) emits `editorial-split__*` markup,
       so the selector matches nothing on the site. Recorded rather than
       fixed, because fixing it means picking a disabled edge value that no
       decision row covers and that nothing would render. If a flush-split
       carousel ever gains a disabled state, THIS is the cell to decide
       first -- do not read the paragraph above as saying it is harmless.

       Worse, nothing can exercise it. `.single-project__carousel-btn` is
       the only carousel any JS ever disables (single-project.js:121 and
       :124) and `single-project.php` emits `editorial-split__*` markup
       instead, so that selector matches nothing on the site. flush-split.js
       and simple-stack.js both state in-file that they have no
       disabled-at-the-ends state. See KNOWN GAPS item 4.
     - `border-radius`, from `--shape-carousel-radius`. Story 4 took it
       to 999px from the token block alone -- no change here. Both
       buttons that actually render show the shape at rest, since story
       2 had already given simple-stack the edge it needed; they are not
       the same shape, because their boxes are not the same size. See
       the Family C carousel note.

   HAZARD 1 IS RETIRED, DELIBERATELY AND WITH MEASUREMENT.
   `simple-stack.css` used to draw `outline: 2px solid currentColor` and
   split `:hover` off into its own selector so that `currentColor` could
   never be the accent when the ring was painted. That defence was chosen
   against a LIGHT page (its comments quoted 1.54:1 and ~1.3:1) and the
   template has not rendered on one for some time. Measured live on the
   real ground 2026-09-19, on `/projects/auto-loan-future/`: the
   `currentColor` ring scores 15.62:1 and this sheet's cyan ring 12.52:1,
   against a 3:1 non-text floor. Joe ruled the local ring out the same
   day, so all three carousels now share one rule.

   What replaces the defence is the token, not an accident of selectors:
   a ground where a cyan ring would fail re-binds
   `--state-carousel-focus-ring`, exactly as flush-split.css does for
   white. `:hover` and `:focus-visible` still get separate selectors
   below -- not to protect `currentColor` any more, but because the
   decided focus cell keeps the rest ink while hover moves it.

   THE FILL IS DECLARED IN STORY 5, AND NO `body` PREFIX IS NEEDED.
   Measured live 2026-09-19: exactly two Neve rules reach a carousel
   button's background -- `button` at (0,0,1), which the template sheets'
   own `background: transparent` at (0,1,0) already outranks, and
   `button:hover` at (0,1,1), which nothing outranked because no rule here
   declared the property at all. These selectors are (0,2,0) and beat it.
   Do not raise them to `body ...`; that would be unearned specificity of
   exactly the kind the baseline block below warns against. */
.flush-split__carousel-btn,
.simple-stack__carousel-btn,
.single-project__carousel-btn {
	background-color: var(--state-carousel-rest-bg);
	color: var(--state-carousel-rest-fg);
	border-color: var(--state-carousel-rest-bd);
	border-radius: var(--shape-carousel-radius);
	font-weight: 500;
}

.flush-split__carousel-btn:hover,
.simple-stack__carousel-btn:hover,
.single-project__carousel-btn:hover {
	background-color: var(--state-carousel-hover-bg);
	color: var(--state-carousel-hover-fg);
	border-color: var(--state-carousel-hover-bd);
}

.flush-split__carousel-btn:focus-visible,
.simple-stack__carousel-btn:focus-visible,
.single-project__carousel-btn:focus-visible {
	border-color: var(--state-carousel-focus-bd);
	outline: 2px solid var(--state-carousel-focus-ring);
	outline-offset: 2px;
}

.flush-split__carousel-btn:disabled,
.simple-stack__carousel-btn:disabled,
.single-project__carousel-btn:disabled {
	color: var(--state-carousel-disabled-fg);
	border-color: var(--state-carousel-disabled-bd);
	opacity: 0.3;
	cursor: default;
}

/* ---------------------------------------------------------------------
   ARCHIVE FILTER CHIP -- Family A cells only, and live as of story 2:
   archive-project.css now keeps geometry alone.

   THE FUNNEL ICON RIDES ALONG. `.portfolio-archive__toolbar-icon` opens
   the filter panel below 768px and had its own hover/focus rule moving
   ink and edge to the accent -- a seventh idiom, and the last ad hoc
   state rule in that file. It has no cell in state-values.md, so Joe
   ruled it onto the CHIP family (2026-09-19): a filter control, in the
   filter toolbar, that acts in place -- the same affordance tier as the
   chips it opens. Its rest edge moves purple -> white with them, and it
   newly takes weight 500, which archive-project.css never declared for it.

   SCOPING ASYMMETRY, DELIBERATE BUT WORTH KNOWING: these rules are
   unconditional, while the icon's GEOMETRY (width, height, border width
   and style) lives inside archive-project.css's `max-width: 768px` block,
   because the icon is `display: none` above that. Nothing breaks today.
   But a future decision to show the funnel at wide viewports would get a
   coloured edge with no width until that geometry is unscoped. The shared
   sheet does not carry template breakpoints, so the fix belongs there, not
   here.

   HAZARD 2 IS NOW A RULE, NOT AN ACCIDENT. `archive-project.css:263-265`
   used to keep a hovered, already-SELECTED chip looking selected purely
   because its `:hover` rule sat before `.is-active` in one file. With
   hover moved here that ordering would still work -- this sheet loads
   first -- but it would be banking the same accident across two files
   instead of one. The `:not(.is-active)` below states the contract's
   precedence rule directly: Family A never overrides Family B, whatever
   the file order.

   CHIP HOVER IS COLOUR-ONLY, AND THAT IS A RULING, NOT AN OVERSIGHT.
   `--state-chip-rest-bd` and `--state-chip-hover-bd` are both #ffffff, so
   once the fill was ruled off, hover moves ink alone. That sits against
   SPEC.md's "colour is never the sole carrier of a state", and against the
   inline-link block above, which earns its hover by thickening an
   underline. Joe was shown the tension and offered a non-colour carrier (a
   thickened edge via inset box-shadow, no layout shift) and chose
   colour-only, 2026-09-19: the chip already reads as operable from its
   border and cursor, so nothing here is conveyed by colour ALONE that is
   not conveyed otherwise, and hover is pointer-only and supplementary.
   Do not "fix" this by adding a carrier. The strict rule still binds
   Family B, where weight 600 is load-bearing. The constraint's exception
   is handed to bmad-spec at
   docs/handover/2026-09-19-bmad-spec-story2-contract-amendments.md.

   THE CHIP'S FAMILY B CELLS LANDED IN STORY 5, in the Family B block
   below rather than here -- they are not a chip state, they are the
   selected state that happens to appear on a chip. What archive-project.
   css used to ship (near-white ink on an #a855f7 fill) measured 3.85:1
   and failed the 4.5:1 text floor; the decided cell, with its fill ruled
   off on this ground, measures 4.87:1 and passes.
   --------------------------------------------------------------------- */
.portfolio-archive__chip,
.portfolio-archive__toolbar-icon {
	background-color: var(--state-chip-rest-bg);
	color: var(--state-chip-rest-fg);
	border-color: var(--state-chip-rest-bd);
	border-radius: var(--shape-chip-radius);
	font-weight: 500;
}

.portfolio-archive__chip:not(.is-active):hover,
.portfolio-archive__toolbar-icon:hover {
	color: var(--state-chip-hover-fg);
	background-color: var(--state-chip-hover-bg);
	border-color: var(--state-chip-hover-bd);
}

/* `:not(.is-active)` on the EDGE for the same reason it is on hover. Without
   it, `.portfolio-archive__chip.is-active` in archive-project.css ties this
   rule at (0,2,0) and wins on order, so a focused SELECTED chip would take
   its edge from the local Family B rule rather than from the token -- the
   second source-order dependency of exactly the kind the hover rule was
   written to remove. It renders identically today only because
   `--color-accent-secondary` and `--state-chip-focus-bd` both resolve to
   #a855f7, and that equality holds only while Neve leaves
   `--nv-secondary-accent` undefined. The RING is deliberately outside the
   guard: a selected chip must still show a focus indicator. */
.portfolio-archive__chip:not(.is-active):focus-visible,
.portfolio-archive__toolbar-icon:focus-visible {
	border-color: var(--state-chip-focus-bd);
}

.portfolio-archive__chip:focus-visible,
.portfolio-archive__toolbar-icon:focus-visible {
	outline: 2px solid var(--state-chip-focus-ring);
	outline-offset: 2px;
}

/* ---------------------------------------------------------------------
   THE <=768px FILTER OVERLAY'S CLOSE BUTTON -- adopted into the chip
   family 2026-09-20, v1.11.7. It sits inside the overlay directly beside
   the chips and was the last control in archive-project.css owning
   neither their colour behaviour nor their shape.

   WHY IT NEEDS A `body` PREFIX AND THE CHIPS DO NOT. This control is a
   bare <button> with `background: transparent` declared at (0,1,0) in
   archive-project.css. Neve ships `button:hover { background-color:
   var(--primarybtnhoverbg) }` at (0,1,1) in neve-style-css, which loads
   AFTER every sheet in this theme -- so it outranks that transparent on
   specificity AND on order, and the control flashes Neve's #a855f7 when
   touched. A bare `.portfolio-archive__chips-close:hover` here would be
   (0,1,1) too: a tie, decided by order, which Neve wins. The leading
   `body` takes these to (0,2,1) so they win on specificity instead. Same
   mechanism, and the same reason, as the button rules further up.

   Note this sheet deliberately does NOT style bare `button` -- see the
   Button block's own note. That is exactly why this control was never
   covered and had to be enumerated. Enumerating is the convention here;
   widening to `button` would reach the carousels and the funnel icon too.

   SHAPE: it takes `--shape-chip-radius` for the same reason it takes the
   colours -- it is a 44x44 control standing beside chips that went round
   in v1.11.4, and its square focus ring next to their round ones was the
   visible tell. It was named in the Family C exception list; this closes
   that one entry. The rest of that list still stands. */
body .portfolio-archive__chips-close {
	background-color: var(--state-chip-rest-bg);
	border-radius: var(--shape-chip-radius);
}

body .portfolio-archive__chips-close:hover {
	color: var(--state-chip-hover-fg);
	background-color: var(--state-chip-hover-bg);
}

body .portfolio-archive__chips-close:focus-visible {
	outline: 2px solid var(--state-chip-focus-ring);
	outline-offset: 2px;
}

/* =====================================================================
   FAMILY B -- current / selected, with its precedence rule stated rather
   than inherited from source order.

   A Family B item under the pointer KEEPS its Family B treatment.
   Family A never overrides Family B.

   Scoped to `[aria-current]`, and THE CLAIM THAT NOTHING EMITS IT WAS
   WRONG. WordPress core's nav-menu walker has put `aria-current="page"`
   on the current item's anchor since 5.3; measured live 2026-09-19 on
   `/about/` and `/projects/`, in the primary AND footer menus. These
   rules were never additive -- they were firing on every page all along.

   WHICH IS WHY THEY NEEDED THE `body` PREFIX, ADDED IN STORY 5. Joe
   reported the marker appearing on Portfolio and nowhere else. It was
   not a missing hook; it was a lost cascade. Neve paints the current item
   with `.nav-ul li.nv-active > .wrap > a { color: var(--activecolor) }`
   at (0,3,2) -- the PRIMARY accent, i.e. the same cyan as hover, so the
   "you are here" marker was rendering in the "you can click this" colour.

   ONLY ONE OF THESE SIX SELECTORS EVER MATCHES ANYTHING. Core puts
   `aria-current` on the ANCHOR, never on the `<li>` -- verified live
   2026-09-19 -- so the three `.menu-item[aria-current]` forms match
   nothing on any page. They are kept because a future menu walker could
   move the attribute to the list item; treat them as latent, and never
   cite their specificity as evidence about this rule's behaviour.

   THE PREFIX IS `html body` AND IT HAS TO BE. The live selector is
   `.primary-menu-ul .menu-item a[aria-current]`: the attribute counts in
   the CLASS column, so it is (0,3,1), and it loses outright to Neve's
   (0,3,2). A leading `body` takes it only to (0,3,2) -- a TIE -- and a
   tie is a loss here, because `$deps` puts this sheet at stylesheet
   index 13 against neve-style at 16. `html body` is (0,3,3) and wins.

   THAT DISTINCTION WAS SHIPPED WRONG ONCE, in this story, so the method
   matters as much as the value: the `body` version was "verified" with a
   `<style>` appended to the end of `<head>`, which sits AFTER Neve and
   therefore wins for a reason the real sheet does not have. Measured
   properly -- injecting immediately after this file's own <link>, i.e.
   at its true cascade position -- `body` gives #00e5ff and `html body`
   gives #a855f7. When you test anything in this file live, insert the
   probe at this sheet's position, never at the end of the head.

   Portfolio looked correct only by accident of having a SECOND rule:
   `body.is-portfolio-section .primary-menu-ul .menu-item-31 a` in
   site-nav.css, already raised for the same reason. That rule stays and
   is not redundant -- it also marks single project pages, where no menu
   item is the current URL and core emits no `aria-current` at all.

   THE FOOTER MENU WAS A KNOWN GAP AND IS NOW MARKED, v1.11.21. Core
   emits `aria-current` there too (verified live 2026-09-19), so the hook
   was always present; what was missing was a ground the marker could be
   seen on. On #666 `--current-marker` measured 1.45:1 -- invisible, and
   shipping the selector then would have been worse than shipping none.
   The band moved to #21232b on 2026-09-21 and the same value measured
   3.96:1 there. v1.13.3 lightened it again to #403f3f and it is back
   under the floor at 2.65:1 -- ruled knowingly (decision 4 of
   SPEC-revision01-colour-adoption), not re-opened by accident. See the
   footer rule below, and the FOOTER NAV block for the full column.
   ===================================================================== */
html body .primary-menu-ul .menu-item[aria-current] > .wrap > a,
html body .primary-menu-ul .menu-item a[aria-current],
html body .primary-menu-ul .menu-item[aria-current]:hover > .wrap > a,
html body .primary-menu-ul .menu-item a[aria-current]:hover,
html body .primary-menu-ul .menu-item[aria-current] > .wrap > a:focus-visible,
html body .primary-menu-ul .menu-item a[aria-current]:focus-visible {
	color: var(--current-marker);
	font-weight: 600;
}

/* THE FOOTER MENU'S MARKER -- the same Family B treatment, a different
   menu class and a much smaller specificity problem.

   WHY THIS IS NOT `html body` LIKE THE BLOCK ABOVE. That prefix exists to
   beat Neve's `.nav-ul li.nv-active > .wrap > a` at (0,3,2). The footer
   menu is rendered by wp_nav_menu() with `menu_class =>
   'joetennis-footer-nav'` (functions.php:506), NOT `.nav-ul`, so that
   Neve rule cannot match it at all. The only thing to beat here is
   site-footer.css's own `.joetennis-footer-nav a` at (0,1,1), which loads
   AFTER this sheet. `.joetennis-footer-nav a[aria-current]` is (0,2,1)
   and clears it on specificity rather than order. Do not copy `html body`
   across by analogy -- measure what actually competes.

   THE :hover AND :focus-visible VARIANTS ARE LOAD-BEARING, not padding.
   The Family A footer hover rule is `.joetennis-footer-nav a:hover`, also
   (0,2,1) -- a TIE with the bare marker selector, decided by source
   order. Family B must never lose to Family A (token-contract.md's
   precedence rule), and relying on "this rule is further down the file"
   makes that guarantee an accident of ordering. Adding the pseudo-class
   takes these to (0,3,1) and wins outright wherever they sit.

   THE VALUE IS 3.96:1 AGAINST A 4.5:1 TEXT FLOOR, AND THAT WAS RULED IN
   rather than overlooked (Joe, 2026-09-21). Three reasons, in order of
   weight. (1) The alternative was a per-ground re-binding to a paler
   purple -- #c084fc clears at 5.93:1 -- which would give "you are here"
   two different appearances on one page, since the header marker on the
   dark ground is #a855f7. One signal, one hue. (2) `font-weight: 600`
   against the footer's 500 is a genuine non-colour carrier, the same one
   token-contract.md calls load-bearing for every Family B cell. (3) The
   shortfall has precedent already on the record: `dark pill.current`
   sits at 3.96:1 as an accepted open item in state-values.md. Against
   the 1.45:1 this replaces, it is the unblock, not the compromise.

   If it is ever revisited, the fix is a `--current-marker` binding scoped
   to `.joetennis-site-footer`, not an edit here. */
.joetennis-footer-nav a[aria-current],
.joetennis-footer-nav a[aria-current]:hover,
.joetennis-footer-nav a[aria-current]:focus-visible {
	color: var(--current-marker);
	font-weight: 600;
}

/* PORTFOLIO IS THE ONE ITEM `[aria-current]` CANNOT REACH, in either menu.
   Added v1.11.22 after Joe reported the footer not marking project pages.

   "Portfolio" is a CUSTOM LINK (`menu-item-type-custom`, `menu-item-31` in
   both menus), so WordPress never emits `aria-current` on it -- not on
   /projects/, and not on a single project page, where no menu item is the
   current URL at all. The rule above is therefore structurally unable to
   mark it, which is not a bug in that rule: it is why site-nav.css has
   carried `body.is-portfolio-section .primary-menu-ul .menu-item-31 a`
   since the header gained its marker. The footer simply never got the
   counterpart, so a project page marked Portfolio in the header and left
   it at rest ink in the footer. Measured on
   /projects/public-site-strategy/ before the fix: header #7a22ce/600,
   footer #ffffff/500.

   `body.is-portfolio-section` is emitted by functions.php for the whole
   section, which is what makes this work where `aria-current` cannot.

   SPECIFICITY: (0,3,1) against site-footer.css's `.joetennis-footer-nav a`
   at (0,1,1), which loads later -- clears it on specificity, not order.
   The `:hover` form is listed for the same reason as the block above:
   Family A must never take a Family B item, and a tie decided by source
   order is not a guarantee. Mirrors site-nav.css's rule rather than
   inventing a second idiom; the two are deliberately the same shape. */
body.is-portfolio-section .joetennis-footer-nav .menu-item-31 a,
body.is-portfolio-section .joetennis-footer-nav .menu-item-31 a:hover,
body.is-portfolio-section .joetennis-footer-nav .menu-item-31 a:focus-visible {
	color: var(--current-marker);
	font-weight: 600;
}

/* THE SELECTED ARCHIVE FILTER CHIP -- the other Family B consumer, and
   the one CAP-6 is actually about.

   `.is-active` is a SELECTION THE VIEWER MADE, which is why it needs more
   than the nav marker does. WCAG 1.4.1 and 4.1.2 both bite here, and the
   spec discharges them as ONE build item rather than a colour plus an
   attribute: `archive-project.php` emits `aria-pressed` (the exposure)
   and an x glyph (the affordance), and the chip button itself is the
   reset -- clicking it again clears the filter, which archive-project.js
   has always done. Do not split the x into its own control: a nested
   <button> is invalid, and it would double the tab stops on the one chip
   a keyboard user most needs to move past.

   NO `:hover` OR `:focus-visible` VARIANT IS NEEDED HERE, and that is the
   precedence rule paying off. The Family A chip rules above carry
   `:not(.is-active)`, so nothing in Family A ever reaches a selected
   chip; this single unconditional rule is all that keeps it reading as
   selected under the pointer. The focus RING is deliberately still
   Family A's -- it sits outside that guard, so a selected chip keeps its
   indicator.

   A SELECTED CHIP GROWS BY ~17.3px, measured live on the widest chip
   2026-09-19 -- the 10px glyph, the base rule's 6px gap, AND about a
   pixel of reflow from `font-weight: 600` against the rest rule's 500,
   which this rule newly introduces (the retired archive-project.css rule
   declared no weight). Do not quote "one glyph" as the budget: the 992px
   re-wrap question is decided by all three. This rule owns no padding,
   which is what keeps the delta to those three and nothing more; Joe ruled the growth in on 2026-09-19 and that file's comments
   were corrected in the same commit. See them before re-deriving a
   no-reflow requirement from anything else in this repo. */
.portfolio-archive__chip.is-active {
	background-color: var(--current-selected-bg);
	color: var(--current-selected-fg);
	border-color: var(--current-selected-bd);
	font-weight: 600;
}

/* =====================================================================
   BASELINE FALLBACK -- so an element nobody enumerated cannot render at
   an unintended value.

   Two halves, and they are NOT symmetric. The universal focus ring is
   pure gain: every focusable element gets an indicator whether or not
   anyone thought about it. Blanket *colour* on anchors is the opposite
   -- it is exactly the blast radius that enumerating exists to avoid --
   so there is deliberately NO `a { color }` rule anywhere in this sheet.
   An anchor nobody enumerated keeps Neve's colour and gains only a ring.

   Two consequences of that boundary, both known and neither fixed here:
   Neve's own `a:focus, a:hover` tints every unenumerated anchor to the
   SECONDARY accent, which is the same hue as the "you are here" marker;
   and since the dark-ground inline-link rest ink moved to #0f8c9d, an
   unenumerated anchor now sits at Neve's #00e5ff while an enumerated one
   in prose beside it sits at #0f8c9d. The divergence is the price of
   enumerating, and it shrinks as containers are added to the list above
   -- but it is a real inconsistency, not an illusion.
   ===================================================================== */

/* The ring, at DELIBERATELY LOW specificity (0,1,1), so any enumerated
   rule above and any template sheet below outranks it -- that is the
   whole point of a baseline.

   Until story 2 this was also load-bearing for hazard 1: `button:focus-
   visible` here tied `.simple-stack__carousel-btn:focus-visible` and
   lost on order, leaving that sheet's `currentColor` ring intact. That
   ring is now retired (measured, see the carousel block), so raising
   this selector would no longer repaint anything hidden -- but do not
   raise it anyway. A baseline that outranks an enumerated rule stops
   being a baseline, and the next template sheet to declare a ring would
   silently lose to it. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
	outline: 2px solid var(--ground-default-ring);
	outline-offset: 2px;
}

/* Form controls need the `body` prefix and only the `body` prefix: Neve
   zeroes their outline outright with
   `[tabindex="-1"]:focus, input:read-write:focus, select:focus,
   textarea:focus { outline: 0 }` at (0,1,1), later in the cascade. (0,1,2)
   clears it. No template sheet styles a raw form control, so nothing
   below is being overridden. */
body input:focus-visible,
body select:focus-visible,
body textarea:focus-visible {
	outline: 2px solid var(--ground-default-ring);
	outline-offset: 2px;
}

/* THE FLIP CARDS -- a live WCAG 2.4.7 failure this baseline has to close,
   and the one case where the obvious universal rule would look right and
   fix nothing.

   THREE `project` templates build their card flip out of a keyboard-
   focusable `<input type="checkbox">` immediately followed by the
   `<label>` that IS the visible card:

     theme/templates/editorial-split-wide.php:166
     theme/templates/full-bleed-float.php:156
     theme/single-project.php:175               (emits `editorial-split__*`)

   THREE, NOT FOUR, SINCE v1.13.10. Flush Split headed this list -- and
   Flush Split White with it, through the shim -- until
   SPEC-content-module-tabs replaced its flip with a tab bar. There is no
   checkbox to focus there and no label to ring. The rule below is
   unchanged and is still required by the three above: do not read the
   removal as the pattern being retired.

   A grep for `flip-toggle` across theme/*.css returns ONLY `:checked`
   rules -- not one focus indicator anywhere. The card is reachable by Tab
   and gives the keyboard user nothing.

   THE TRAP: the input carries `.visually-hidden`, which is
   `position: absolute; width: 1px; height: 1px; clip: rect(0,0,0,0)`
   (flush-split.css:223-233 and three more). The universal
   `body input:focus-visible` ring above DOES fire on it -- and paints a
   2px outline on a clipped one-pixel box, where nobody can see it. The
   net would read as covered and the failure would survive.

   So the ring goes on the VISIBLE label instead, via the adjacent-sibling
   combinator. Verified 2026-09-19: the input is immediately followed by
   the label in every template that carries the pattern, so `+` holds. The
   selector names no template class, which keeps the one-way-coupling
   constraint, and covers them all in a single rule -- which is why Flush
   Split leaving the list needed no edit to the rule itself.

   Specificity (0,2,1) clears every `*__card-scene` rule in the template
   sheets (0,1,0), so it survives their loading after this one.

   The class itself is declared just below. Until this story, four
   template sheets each declared `.visually-hidden` and no sitewide sheet
   did; a shared rule that keys off a class it does not own would break
   the moment a template that clips content differently adopts the
   pattern. The declaration below is byte-identical to the four local
   ones (flush-split.css:223-233 and its three siblings), so it changes
   nothing today -- it just gives the class an owner at the sitewide
   layer, where the rule that consumes it now lives. The local copies are
   left in place; retiring them is a template-sheet edit. */
.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

.visually-hidden:focus-visible + label {
	outline: 2px solid var(--ground-default-ring);
	outline-offset: 2px;
}

/* Baseline ink / fill / edge for elements no sheet enumerates, so they
   resolve from ground tokens rather than from browser defaults (a yellow
   <mark>, a black-on-white <table>, a light-grey <hr>). Bare element
   selectors, specificity (0,0,1): these beat the UA stylesheet and lose
   to literally everything else, which is the intent -- they fill gaps,
   they do not compete. */
mark {
	color: var(--ground-mark-fg);
	background-color: var(--ground-mark-bg);
}

code,
kbd,
pre {
	color: var(--ground-ink);
	background-color: var(--ground-fill);
}

hr {
	border-color: var(--ground-edge);
}

blockquote {
	color: var(--ground-ink);
	border-color: var(--ground-edge);
}

table,
th,
td {
	color: var(--ground-ink);
	border-color: var(--ground-edge);
}

fieldset,
legend {
	color: var(--ground-ink);
	border-color: var(--ground-edge);
}

input,
select,
textarea {
	color: var(--ground-ink);
	background-color: var(--ground-fill);
	border-color: var(--ground-edge);
}

/* =====================================================================
   KNOWN GAPS -- carried forward, not overlooked.

   1. WHITE GROUND, NOW FULLY BOUND -- CLOSED IN STORY 3.
      flush-split-white.css no longer carries a single state rule of its
      own: its hardcoded #000000 literals for the carousel and breadcrumb
      rings are retired, and the chip roles, the Family B selected-chip
      roles, the carousel rest/hover/focus/disabled roles and both
      `--state-breadcrumb-*` roles are bound as values in its blocks 5a
      and 5b. Measured on white: #000000 21:1, #0b6b78 6.20:1, #7a22ce
      7.12:1 either direction -- that last is #8100fc at 6.29:1 since
      v1.13.3.

      That also closed the one live regression story 2 left behind. With
      a text-decoration hover and no colour binding, breadcrumb hover AND
      focus ink on white both resolved to cyan at 1.54:1, carried only by
      the underline -- and adding the `var(--nv-primary-accent, #00e5ff)`
      fallback had changed the unresolved-Neve-variable case from
      "declaration drops, black ink kept" to "paints cyan", i.e. a new
      failure mode on white rather than just an idiom mismatch.

      TWO ROLES REMAIN UNBOUND ON WHITE, both deliberately:
      `--ground-mark-fg` / `--ground-mark-bg`. The bench decided no white
      values, token-contract.md records them as unbound, and no <mark>
      renders anywhere on the site. Do not invent a pair to close the
      table. `--state-nav-rest-fg` is likewise unbound on white, but the
      reason changed on 2026-09-21 and the old one no longer holds: it used
      to be "NO RULE READS IT" -- nav rest ink is Neve's `--color`, which
      that sheet's rule 2 already re-binds. Flush Split's flip cue now reads
      it (`spec-flush-split-card-affordance`), so the role has a consumer.
      It still needs no white binding, for a different reason: that consumer
      sits on the dark card, not on the white ground, so the `:root` value
      is the correct one for it and a white binding would be the wrong one.
      See the note under block 5a.

      Worth knowing WHY it needs no white binding, because the reason is not
      the obvious one and it caught a build: `--state-nav-hover-fg` is
      declared ONCE, at `:root`, as `var(--current-marker)`. Custom-property
      substitution happens where the property is DECLARED, not where it is
      used -- so it resolves against `:root`'s marker and inherits downward
      as a literal. Re-binding `--current-marker` on a ground class below
      `:root` -- as `.joetennis-ground-dark` does -- does NOT reach back and
      re-resolve it, which is why every ground block declares the role too.
      Verified live 2026-09-21 on /projects/public-site-strategy/: on that
      white page `--current-marker` computes to #7a22ce on <body> while
      `--state-nav-hover-fg` computes to #a855f7 on the same element. See the
      finding logged in deferred-work.md -- the white header's nav hover is
      therefore NOT the #7a22ce this sheet's block 5a says it inherits.

   2. SIMPLE STACK'S SHEET DESCRIBED A GROUND IT NO LONGER HAD. Closed
      in story 2: the "this light page" comments and the 1.54:1 / ~1.3:1
      figures are gone, re-measured on the dark ground the template
      actually renders on (`/projects/auto-loan-future/`, body #0a0e1a).
      Recorded because the same trap recurs -- a figure in a comment
      outlives the ground it was measured against.

   3. THE FOOTER BAND IS THE THIRD GROUND -- CLOSED IN STORY 6, v1.11.21.
      Both halves of it. The band moved #666 -> #21232b, which took the
      failing hover cell from 3.73:1 to 10.19:1 WITHOUT moving a token
      value, and took the "you are here" marker from an unshippable
      1.45:1 to 3.96:1, which is what let the footer menu be marked at
      all. The ground turned out to need no binding block: at #21232b
      every `:root` default is already correct on it. Measurements and
      the full reasoning live in one place -- the FOOTER NAV block above.
      Do not restate them here.

      Two things survive the closure. The marker ships at 3.96:1, under
      the 4.5:1 text floor, ruled in by Joe on 2026-09-21 with weight 600
      as the carrier -- the footer marker rule above records why. And
      `--state-link-rest-fg` measures 3.92:1 on this band; no inline link
      renders in the footer, so it is unreachable rather than failing.

   4. SINGLE PROJECT'S CAROUSEL HAS NO LIVE TARGET. `single-project.php`
      emits `editorial-split__*` markup, and the only project on the
      default template renders no carousel at all, so
      `.single-project__carousel-btn` matches nothing on the site today.
      Its rules here and in single-project.css are latent -- correct by
      construction and by injected fixture, unverifiable on a real page
      until a project renders that markup.
   ===================================================================== */
