/*
 * PROVENANCE — this file is AUTHORED HERE, not copied. It is the only file in
 * this folder that is.
 * ─────────────────────────────────────────────────────────────────────────
 * Entry            cluster
 * Entry version    not yet extracted
 * Destination      theme-builds/library/sidebars/cluster/
 * SHA-256          none — nothing has been pulled, so there is nothing to hash
 * Authored         2026-09-02, gravitystack-main 4.29.0
 * Substrate        base.css, "Cluster shell substrate". Required. Without it
 *                  core's flow-layout margins push the content column out of
 *                  line with the sidebar, which renders and is wrong
 * Specificity      no separate record yet — the collisions are named inline
 *                  below, in the shape mega-featured-specificity.md uses, and
 *                  they move to that file at extraction
 *
 * WHY IT IS AUTHORED HERE RATHER THAN PULLED. `mega-featured` was extracted at
 * instance one, and its own README records that version 1.0.0 meant *the first
 * thing that was pulled* rather than *ready*. Working Together and About are
 * both in this build, so waiting for the second instance lets the
 * constant-versus-variable split be read from two real cases at no extra cost.
 * Extraction is owed once About is built — see library/sidebars/README.md.
 *
 * Until then this file is the source, and it is the one file in this folder
 * that may be edited in place. The moment it is extracted that stops being
 * true and this header gains a version, a hash, and a pull date like every
 * other one.
 * ─────────────────────────────────────────────────────────────────────────
 */

/*
 * cluster.css — the cluster sidebar, design layer.
 *
 * One organism, one file. In-page navigation for a set of child pages under a
 * real parent page: a panel beside the content at desktop, a sticky disclosure
 * control and a full-height overlay below the shell breakpoint, and one list
 * serving both. Specified in `website-rebuild-cluster-sidebar-spec.md`.
 *
 * THIS FILE IS THE DESIGN HALF ONLY, per gravitysite-css-standards.md, *Files*.
 * The rules that exist to beat core's block layout are not here — they are the
 * substrate, in base.css under "Cluster shell substrate". This file assumes
 * they are present. It will render without them and the columns will not line
 * up, and nothing will error.
 *
 * THE SHELL IS IN THIS FILE AND IT IS A JUDGEMENT CALL, recorded rather than
 * hidden. `.c-cluster-shell` belongs to `templates/page-cluster.html`, which is
 * per-site, while everything else here is fleet-constant. It is here because
 * the organism's desktop sticky is meaningless without the column it sits in —
 * the offset, the cap, and the grid are one design decision — and because
 * splitting an eight-line grid across two files costs a reader more than the
 * purity buys. Extraction has to decide whether the shell travels with the
 * entry; it is marked below so that decision is made rather than inherited.
 *
 * EVERY COLOUR RESOLVES THROUGH theme.json, by one of two channels. The role
 * tokens and `action-11` are palette entries. The three `warning` steps are
 * editor-only and come from the primitive scale channel as
 * `--wp--custom--color--warning-N`, the route theme 4.24.1 used for the header
 * edge — so they are present wherever the receiving client's design file
 * defines those steps, and absent where it does not. The check is the design
 * file, not theme.json.
 *
 * CONTRAST, MEASURED 2026-09-02 on this palette, against
 * `gravity-surface-nested` #ebf0e2 unless stated. These are PROVENANCE, not a
 * guarantee — re-measure on any client palette.
 *
 *   gravity-text  #1d211c on the panel          14.06:1
 *   action-11     #482799 on the panel           8.93:1
 *   gravity-focus #5d3fb8 on the panel            6.31:1
 *
 * No state takes a fill as of 4.29.5, so every figure above is on the panel and
 * the white-fill measurements this list used to carry are gone with it.
 *   warning-11    #a35200 on warning-3 #ffeccc    4.81:1
 *   warning-9     #d97706 on the panel            2.75:1  (exempt — see l.)
 *
 * NO `container-type` MAY BE DECLARED IN THIS FILE, on `.c-cluster-shell`, on
 * the panel, or on anything above either. Both the desktop panel and the mobile
 * control are sticky, and constraint 3 in gravitysite-css-standards.md is that
 * a containment context creates a new containing block, so a sticky descendant
 * anchors to the container instead of the viewport. It renders. It is quietly
 * wrong. This is the note that stops someone adding one for a card query.
 */

/* ── a. The two-column shell ─────────────────────────────────────────────
 *
 * A grid authored here, not `core/columns`. Core's columns block stacks below
 * 782px, which is neither `bp-1` nor `bp-2`, so reaching for it would put a
 * third breakpoint on the page that nobody owns and nothing documents.
 *
 * `bp-2`, NOT `bp-1`, and the arithmetic is the argument. At `bp-1` the content
 * area is 768 − 48 of gutters = 720px, which after the reading measure leaves a
 * sidebar too narrow to be a sidebar. Tablets get the mobile disclosure, which
 * the mobile design supports on its own terms rather than by default.
 *
 * THE SIDEBAR HAS NO WIDTH. This is the part to read before changing anything
 * here. Until 4.30.0 the first track was `20rem`, and 320px was a number no
 * file in the estate defended — not this one, not `theme.json`, not
 * `gravitystack-design.md`, not the CHANGELOG. Measured, it happens to be the
 * smallest round value that fits the longest Working Together label with 14px
 * to spare, which is instance-tuning even if nobody intended it as such. This
 * component serves Working Together, About, and Capabilities on this site and
 * an unknown set on every client site, so a width read off one cluster's
 * labels is a width that is wrong everywhere else.
 *
 * SO THE TRACKS ARE DERIVED AND ONLY ONE IS NAMED. The content track IS the
 * site's reading measure, straight from `layout.contentSize`, which
 * `gravitystack-design.md` sets at 40rem to hold roughly 75 characters and
 * keeps in `rem` so that count survives a reader enlarging text. The sidebar
 * takes what the canvas has left. The gap is the declared gap and nothing
 * else. Read as one sentence: measure first, sidebar gets the remainder.
 *
 * WHAT THAT BUYS, MEASURED ACROSS FOUR PALETTES ON 2026-09-03. The gutter is
 * always exactly `spacing.60`; trailing air is always zero; both shell edges
 * always align with the header and the footer; and the measure is always the
 * receiving site's own `contentSize`. On this palette the sidebar resolves to
 * 452px. On a 65rem canvas it is 352px, on a 75rem canvas 512px.
 *
 * AND IT ENDS A FIGHT RATHER THAN WINNING ONE. Core centres a constrained
 * group's children with `margin-left: auto !important; margin-right: auto`,
 * also `!important` — so any rule that left-aligns the measure has to carry
 * `!important` too. With the track equal to the measure there is nothing left
 * to centre and those autos resolve to zero on their own. Do not reintroduce a
 * `1fr` content track here and then override the centring; that is the version
 * of this that needs `!important` and it was rejected.
 *
 * THE `20rem` FLOOR IS A FLOOR AND NOT A WIDTH, and its value is the one
 * unsettled number left in this file. It should come from what makes a nav row
 * usable rather than from any one cluster's labels, and it has not been derived
 * from anything yet. It engages only on a canvas narrower than about 1010px,
 * which no GravityStack site has. Revisit it when a client palette reaches it,
 * not before.
 *
 * `minmax(0, ...)` ON THE CONTENT TRACK, NOT A BARE `var()`. Measured on a
 * squeezed palette — 65rem canvas with a 45rem measure — the bare form
 * overflows the shell by 48px and the `minmax` form shrinks the measure
 * instead. A layout that gives up 48px of measure is recoverable; one that
 * hangs its content past its own container is not.
 *
 * THE TOP SPACE LIVES HERE, NOT ON `main`. Until 4.30.0 `page-cluster.html`
 * carried `padding-top: spacing.60` inline on `main`, which is unconditional
 * and so put 48px above a control that is the first thing on a phone screen.
 * Inline template padding cannot be made breakpoint-aware without `!important`,
 * so the value moved here where it can be. `padding`, not `margin`: the shell
 * is `main`'s only child, so a margin collapses straight out through it and
 * the space silently does not appear.
 */
.c-cluster-shell {
  padding-block-start: var(--wp--preset--spacing--40);
}

/* Below `bp-2` the shell is flow and the sidebar renders above the content —
 * that is the mobile design. But `base.css` section 5 zeroes `margin-block` on
 * every shell child so core's flow margins cannot break the desktop grid, and
 * below `bp-2` that same rule removes the only thing separating the control
 * from the page title. Measured before this rule: 0px. Nothing else put it
 * back.
 *
 * Collision, and the count:
 *   base.css 5a       .c-cluster-shell > *            (0,1,0)
 *   this rule         .c-cluster-shell > .c-cluster-nav (0,2,0)
 * Won on specificity rather than on file order, so it survives a change to the
 * enqueue chain in `functions.php`.
 */
.c-cluster-shell > .c-cluster-nav {
  margin-block-end: var(--wp--preset--spacing--40);
}

@media (min-width: 1200px) { /* bp-2 */
  .c-cluster-shell {
    display: grid;
    grid-template-columns:
      minmax(20rem, 1fr)
      minmax(0, var(--wp--style--global--content-size));
    gap: var(--wp--preset--spacing--60);
    align-items: start;
    padding-block-start: var(--wp--preset--spacing--60);
  }

  .c-cluster-shell > .c-cluster-nav {
    margin-block-end: 0;
  }

  /* Core hands a nested constrained group the ROOT padding, 24px a side here,
   * on top of the root padding the page already carries. Inside a track that is
   * exactly the measure it has nowhere to come from but the measure, which it
   * would cut from 640 to 592. Stripped at `bp-2` only: below it the article is
   * full width and that padding is the page gutter, which is correct.
   *
   * Collision, and the count:
   *   core     :root:root .wp-block-group.has-global-padding   (0,4,0)
   *   this rule :root:root .c-cluster-shell > article.has-global-padding (0,5,1)
   * The `:root:root` is core's, not an affectation — matching it is the price
   * of the override.
   */
  :root:root .c-cluster-shell > article.has-global-padding {
    padding-inline: 0;
  }
}

/* ── b. The nav, and the lists inside it ─────────────────────────────────
 *
 * The nav is the mobile cap. Below `bp-2` it is a flex column holding the
 * control and the panel: the control takes its own height, whatever that turns
 * out to be, and the panel takes the rest and scrolls inside it. The whole
 * thing is capped at the viewport less the header.
 *
 * THIS IS WHAT REPLACED `--c-cluster-control-reserve`, and the property is gone
 * rather than retuned. It existed because the open panel was `position: fixed`
 * against the viewport while the control sat in normal flow, so the panel had
 * to reserve a strip of its own padding for a control it could not measure. The
 * value was authored at 6.5rem, raised to 7.5rem at 4.29.0 when the harness
 * found it cleared a two-line control by 1.2px, and was still wrong on the live
 * page by 118px — because it was measured from the viewport top and the control
 * actually sits below a sticky header and 48px of page padding. A number that
 * has been wrong twice for two different reasons is a number that should not
 * exist. Flex removes the need to know the control's height at all.
 *
 * `100dvh`, NOT `100vh`. On a phone `vh` is the tall viewport, so a panel sized
 * from it hides its last rows behind the browser chrome until the user scrolls.
 * `dvh` tracks the chrome. The desktop cap in k. is a different case and keeps
 * `vh` deliberately.
 */
.c-cluster-nav {
  display: flex;
  flex-direction: column;
  max-height: calc(100dvh - var(--c-header-height, 0px));
}

.c-cluster-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* ── c. The panel ────────────────────────────────────────────────────────
 *
 * Two elements and both are load-bearing. `__panel` is the positioning box —
 * a plain block at desktop, a fixed overlay when the mobile disclosure is open.
 * `__panel-inner` is the card, and it is what carries the fill at every size,
 * so the switch above never has to move a colour.
 *
 * Padding is asymmetric on purpose. `spacing.40` inline, because the card wants
 * its horizontal gutter. `spacing.30` block, because the rows already carry
 * 44px of their own and 24px on top of that reads as a gap before the list
 * starts rather than as the card breathing.
 */
.c-cluster-nav__panel-inner {
  background-color: var(--wp--preset--color--gravity-surface-nested);
  border-radius: var(--wp--custom--rounded--lg);
  padding: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40);
}

/* ── d. The panel's own heading ──────────────────────────────────────────
 *
 * HIDDEN AT EVERY SIZE AS OF 4.30.0, where it used to be hidden at `bp-2` only.
 *
 * It earned its place when the mobile panel was a full-height overlay: nothing
 * else on that screen said what the panel was, because the control was painting
 * over the list rather than sitting above it. With the panel in flow the
 * control is directly above the heading saying the same words — the set name is
 * its eyebrow — so the h2 renders the label twice, 40px apart. See the
 * before/after renders in `docs/verification/`.
 *
 * NOTHING IS OWED TO ASSISTIVE TECH BY HIDING IT. The accessible name of the
 * navigation landmark comes from the `aria-label` the renderer puts on the
 * `<nav>`, not from this heading, and that is unchanged.
 *
 * THE ELEMENT STAYS IN THE MARKUP AND MUST. `e.` keys its first-heading rule on
 * `.c-cluster-nav__label + .c-cluster-nav__group`, and `display: none` does not
 * affect an adjacent-sibling match. Removing the h2 from `inc/cluster-sidebar.php`
 * would take that rule with it silently — which is the trap `e.` already fell
 * into once and documents.
 */
.c-cluster-nav__label.c-cluster-nav__label {
  display: none;
}

/* ── e. Group headings ───────────────────────────────────────────────────
 *
 * Body font, not the heading font, and SMALLER than the links it labels. Weight
 * and colour carry the emphasis; size would make the label compete with the
 * items it introduces. The live sidebar does the same at 16px against 17px and
 * reads correctly.
 *
 * WEIGHT 600, NOT 700. Figtree ships Regular and SemiBold only. A 700 request
 * either synthesises a fake bold or falls back silently, and neither is a
 * decision anyone made.
 *
 * Doubled class for the same collision as d.
 */
.c-cluster-nav__heading.c-cluster-nav__heading {
  font-family: var(--wp--preset--font-family--figtree);
  font-size: var(--wp--preset--font-size--text-2);
  font-weight: var(--wp--custom--weight--strong);
  line-height: 1.4;
  letter-spacing: var(--wp--custom--tracking--normal);
  color: var(--wp--preset--color--gravity-text);
  margin: var(--wp--preset--spacing--30) 0 var(--wp--preset--spacing--20);
}

/* The first heading in the panel needs no space above it — the panel's own
 * heading and the card's padding are that space.
 *
 * KEYED TO THE LABEL, not to `:first-child`, and the reason is a trap this rule
 * has already fallen into once. `.c-cluster-nav__group:first-child` matches
 * nothing: `.c-cluster-nav__label` is always the panel's first child, so the
 * first group is its second. Until 4.29.4 the rule worked only through its
 * second selector, `.c-cluster-nav__lead + .c-cluster-nav__group` — and when
 * the lead moved inside a group, that selector went with it and would have
 * taken the rule silently. `display: none` on the label at desktop does not
 * affect the adjacent-sibling match, so one selector serves both breakpoints. */
.c-cluster-nav__label + .c-cluster-nav__group .c-cluster-nav__heading.c-cluster-nav__heading {
  margin-top: 0;
}

/* ── f. Rows, at rest ────────────────────────────────────────────────────
 *
 * Charcoal, unadorned, and flush against the row above. This is the header's
 * own convention — base.css rule 2d sets nav links to `gravity-text` at rest
 * and `action-11` on hover — and the sidebar follows it rather than inventing a
 * second one. The alternative, every row in the link colour, was rendered first
 * and refused on sight: fourteen violet rows all compete, and the panel reads
 * as unsettled. Charcoal at rest leaves the one row that is *on* as the only
 * accent in the panel.
 *
 * `min-height: 2.75rem` IS NOT A LOCAL TRADE. `ops-accessibility-standards.md`
 * adopts SC 2.5.5 at 44px as a fleet constant on interactive controls, unscoped
 * by breakpoint, and records it as an exceedance rather than folding it into
 * the AA floor precisely so that nobody later reduces a compliant control to
 * 24px on the grounds that AA permits it. A 17px row at 1.5 leading measures
 * 29.5px, which clears AA's 24px and fails ours — and the first draft of this
 * component shipped exactly that, which is what the standard predicted when it
 * listed target size as Unenforced. If the height becomes genuinely unworkable
 * the question belongs to that file, not to a build.
 *
 * ZERO GAP BETWEEN ROWS, and it is the height that pays for it. Rows separated
 * by a gap at this height read as slack rather than as rows, and fourteen items
 * at 44px plus 8px gaps puts the panel past 1000px.
 *
 * A ROW FILL WAS THE FIRST ANSWER TO THE HEIGHT AND IT IS GONE — see g. The
 * looseness it was meant to fix turned out not to be visible on the render,
 * which is the honest reason it went rather than any accessibility cost.
 *
 * THE TRANSPARENT BORDER IS WHAT STOPS THE ROW SHIFTING when the current marker
 * appears. Reserve the width at rest; do not remove it as dead weight.
 *
 * Collision, and the count:
 *   theme.json elements.link  :root :where(a:where(:not(.wp-element-button)))  (0,1,0)
 *   this rule                 .c-cluster-nav a                                 (0,1,1)
 * One class and one type beats it without a doubled class, so there is none.
 */
.c-cluster-nav a {
  display: flex;
  align-items: center;
  min-height: 2.75rem;
  font-size: var(--wp--preset--font-size--text-3);
  line-height: 1.5;
  color: var(--wp--preset--color--gravity-text);
  text-decoration: none;
  padding-inline: var(--wp--preset--spacing--30) var(--wp--preset--spacing--20);
  border-inline-start: 3px solid transparent;
  border-radius: 0 var(--wp--custom--rounded--sm) var(--wp--custom--rounded--sm) 0;
  background-color: transparent;
  transition:
    color 0.15s ease-in-out,
    border-inline-start-color 0.15s ease-in-out;
}

/* ── g. Hover, focus, and the current item ───────────────────────────────
 *
 * HOVER AND CURRENT SHARE `action-11` DELIBERATELY. Hover then previews the
 * state rather than introducing a third colour, and the weight and the left bar
 * are what separate them. Hovering the current item changes nothing, which is
 * correct — it is not a destination.
 *
 * NO STATE TAKES A FILL. WITHDRAWN ACROSS TWO PASSES ON THE RENDER, 4.29.4 from
 * the current item and 4.29.5 from hover and focus, and the sequence is the
 * useful part of the record.
 *
 * WHAT IT WAS FOR. 44px is a lot of height for 17px text, and a white row fill
 * was offered as the answer to rows reading as slack. It did not survive being
 * looked at. Permanent on the current item it read as a floating white card
 * sitting inside the panel rather than a row within it; transient on hover it
 * read as a card following the pointer. **And the looseness it was meant to fix
 * was not visible on the render in the first place** — a problem in a spec,
 * answered in a spec. Taking it off the current item first and hover second was
 * two looks at the same page, which is why it took two releases rather than
 * one.
 *
 * EVERY STATE STILL CARRIES ENOUGH. Hover is `action-11` and an underline;
 * focus adds the ring; current adds weight 600 and the left bar. 1.4.1 is
 * satisfied on hover by the underline alone and on the current item four ways
 * over. Text contrast is 8.93:1 on the panel against a 4.5:1 requirement, and
 * the focus ring is 6.31:1 against 3:1 — both measured on the render, not
 * carried from the spec.
 *
 * The 1.4.11 exemption this section used to argue for the fill is retired with
 * it. There is no longer a non-text indicator here that needs excusing.
 *
 * NOTED, NOT RESOLVED: this walks contrast DOWN. Charcoal at rest is 14.06:1
 * and the violet hover is 8.93:1 on the panel. Both clear AAA, and WCAG
 * requires each state to meet its threshold independently and nowhere requires
 * interaction to increase contrast. base.css rule 2d records the same direction
 * on the header nav and names it an open fleet question to be settled
 * fleet-wide rather than per rule. This is the fourth surface to touch it and
 * it inherits the position rather than reopening it.
 */
.c-cluster-nav a:hover {
  color: var(--wp--preset--color--action-11);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* THE RING IS ADDITIVE TO THE HOVER TREATMENT, NEVER INSTEAD OF IT. The live
 * implementation carries `outline: none` and substitutes a left border, which
 * is a keyboard accessibility defect and not a style choice — the border says
 * which page you are on, the ring says where the keyboard is, and they are
 * independent. Do not carry that over.
 *
 * INSET OFFSET, and it outlives the fill it arrived with. theme.json's ring is
 * outward by 2px; the rows here are flush at 44px with no gap between them, so
 * an outset ring overlaps its neighbours. That reason is independent of the
 * fill and is why `outline-offset: -2px` stayed when the fill went.
 *
 * Collision, and the count:
 *   theme.json elements.link  :root :where(a…:focus-visible)  (0,1,0)
 *   this rule                 .c-cluster-nav a:focus-visible  (0,2,1)
 */
.c-cluster-nav a:focus-visible {
  outline: 2px solid var(--wp--preset--color--gravity-focus);
  outline-offset: -2px;
  color: var(--wp--preset--color--action-11);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* Hooked on the ATTRIBUTE, not a class. The class would carry the visual and
 * the attribute the meaning, and the two would be free to drift; keying the CSS
 * to `aria-current` means a row cannot look current without being announced as
 * current.
 *
 * THE BAR IS `action-11`, NOT `brand-9`, AND THAT IS MEASURED RATHER THAN
 * JUDGED. `brand-9` #8eb957 — the green the live sidebar uses — is 1.96:1 on
 * `gravity-surface-nested` against a 3:1 floor for a non-text indicator. It was
 * washing out on the old amber sidebar too and nothing had measured it. If a
 * client build wants the brand cue back, `brand-11` #5b7c2e clears it at
 * 4.14:1. `brand-9` is not an option at either light fill.
 */
.c-cluster-nav a[aria-current="page"] {
  color: var(--wp--preset--color--action-11);
  font-weight: var(--wp--custom--weight--strong);
  text-decoration: underline;
  text-underline-offset: 0.15em;
  border-inline-start-color: var(--wp--preset--color--action-11);
}

/* NO `[aria-current="page"]:hover` RULE, AND ITS ABSENCE IS THE POINT.
 * 4.29.4 carried one holding the background transparent, because the hover fill
 * would otherwise have returned on the one row that is not a destination. With
 * the fill withdrawn from hover at 4.29.5 there is no background to hold: hover
 * sets `action-11` and an underline, and the current item already has both. So
 * hovering it changes nothing without a rule saying so, and a rule that only
 * restates what the cascade already does is a guard whose reason has gone. It
 * was deleted rather than left in place looking load-bearing.
 *
 * A `[aria-current="page"]:focus-visible` rule went the same way. It existed to
 * keep the fill on a focused current row against the (0,3,1) hover guard above
 * it; both halves of that problem are gone.
 */

/* ── h. The switch ───────────────────────────────────────────────────────
 *
 * ONE SIBLING COMBINATOR AND ONE MEDIA QUERY, and this is the whole reason the
 * markup is shaped the way it is.
 *
 * THE PANEL IS A SIBLING OF `<details>`, NEVER ITS CHILD. A closed `<details>`
 * hides its children through the user-agent stylesheet rather than through an
 * authored rule, so a list inside it cannot be forced visible at desktop
 * without engine-fragile overrides — `::details-content` would do it and is not
 * Baseline. A list beside it is governed only by the rules below. That is what
 * lets ONE DOM serve both breakpoints with no JavaScript in the base: the
 * summary still toggles `open` natively, still exposes its expanded state
 * natively, still takes Enter and Space, and the combinator does the rest.
 *
 * A `<details>` whose only child is a `<summary>` is valid — the content model
 * is one summary followed by flow content, and flow content may be empty.
 *
 * IF THE RENDERER EVER PUTS THE LIST BACK INSIDE `<details>`, this rule stops
 * matching and the sidebar disappears at desktop. See the structure note in
 * `inc/cluster-sidebar.php`.
 */
.c-cluster-nav__disclosure:not([open]) + .c-cluster-nav__panel {
  display: none;
}

/* ── i. The mobile control ───────────────────────────────────────────────
 *
 * Sticky, and load-bearing rather than a refinement. With no prev/next anywhere
 * in the set, a reader deep inside a long agreement section has no exit but the
 * back button once the control scrolls away.
 *
 * THAT ARGUMENT WAS SOUND AND THE IMPLEMENTATION NEVER DELIVERED IT. Until
 * 4.30.0 a sticky element cannot travel further than its containing block, and
 * with the panel `fixed` the nav was exactly the control's own height — 77.7px,
 * measured — so the travel was zero. Measured on the live page at 4.29.5: with
 * the panel open, scrolling 500px put the control at −339.8 and out of reach of
 * a hit test. The exit did not scroll away; it never stuck. Putting the panel
 * in flow is what gives this element something to travel in, and that is the
 * larger half of why j. changed.
 *
 * TWO LINES: the set above the current item's name. The 2026-09-01 ruling says
 * it shows the current item's name and does not say what identifies the set, so
 * a reader arriving from search would learn what she is reading and not what it
 * belongs to. The eyebrow is quiet enough that it costs nothing.
 *
 * OFFSET TO `--c-header-height`, NOT `0`. At `0` the control pins to the top of
 * the viewport and the header returns underneath it on a scroll up, since the
 * header stance is reveal. Measured at three widths: offset to the header, the
 * control clears the returning header by 0.8px and both stay tappable.
 *
 * `z-index: 5`, BELOW THE HEADER'S 10 AND NOT ABOVE IT. The old value was 70,
 * from when the panel had to paint over everything. It is not optional and it
 * is not zero: the article beside it is `position: relative`, so a sticky
 * control at `auto` is painted over by content later in the DOM.
 *
 * `list-style: none` and the WebKit marker rule are both needed. `display: flex`
 * removes the disclosure triangle in most engines and does not in all of them.
 */
.c-cluster-nav__disclosure {
  flex: none;
  position: sticky;
  top: var(--c-header-height, 0px);
  z-index: 5;
}

.c-cluster-nav__control {
  display: flex;
  align-items: center;
  gap: var(--wp--preset--spacing--20);
  padding: var(--wp--preset--spacing--30);
  background-color: var(--wp--preset--color--gravity-surface-nested);
  border: 1px solid var(--wp--preset--color--gravity-border);
  border-radius: var(--wp--custom--rounded--md);
  color: var(--wp--preset--color--gravity-text);
  font-size: var(--wp--preset--font-size--text-3);
  font-weight: var(--wp--custom--weight--strong);
  line-height: 1.5;
  cursor: pointer;
  list-style: none;
}

.c-cluster-nav__control::-webkit-details-marker {
  display: none;
}

.c-cluster-nav__control:focus-visible {
  outline: 2px solid var(--wp--preset--color--gravity-focus);
  outline-offset: 3px;
}

.c-cluster-nav__control-eyebrow {
  display: block;
  font-size: var(--wp--preset--font-size--text-1);
  font-weight: var(--wp--custom--weight--normal);
  line-height: 1.4;
  color: var(--wp--preset--color--gravity-text-muted);
}

.c-cluster-nav__chevron {
  margin-inline-start: auto;
  flex: none;
  transition: transform 0.2s ease-in-out;
}

.c-cluster-nav__disclosure[open] .c-cluster-nav__chevron {
  transform: rotate(180deg);
}

/* ── j. The open mobile panel ────────────────────────────────────────────
 *
 * IN FLOW, DIRECTLY BELOW THE CONTROL. Until 4.30.0 this was a fixed full-height
 * overlay entering from the inline start, and three separate defects came out of
 * that one decision. Recorded together because they are one decision, not three.
 *
 * The overlay painted over the header. `inset-block: 0` anchors to the viewport
 * top and `z-index: 60` beat the header's 10, so the site header was covered
 * whenever the panel was open — confirmed by hit test, not by eye.
 *
 * The control landed on the list. The panel started at the viewport top and
 * reserved 120px for a control that actually sat 160.2px down the page, below
 * the header and `main`'s padding. Two measurements from two different origins.
 *
 * And there was no way out. The control is the close affordance, and it was
 * buried in the list it was supposed to close; Escape is keyboard-only; there
 * was no scrim and no tap-outside handler, because dismissal is not one of the
 * two behaviours in this organism's budget.
 *
 * In flow, all three stop existing rather than get fixed. The panel opens under
 * the control because it is under the control. The header is never covered
 * because nothing is fixed. The control is the close affordance and it is the
 * thing directly above the open list, which needs no explaining to anyone.
 *
 * THE CAP IS ON THE NAV, IN b., NOT HERE. This element only has to agree to
 * shrink — `min-height: 0`, because a flex item's default `min-height: auto`
 * refuses to go below its content and the cap would do nothing.
 *
 * `overscroll-behavior: contain` stops a flick at the end of the list scrolling
 * the page underneath, which on a phone reads as the panel having lost the
 * gesture.
 *
 * NO ENTRY ANIMATION. `@keyframes c-cluster-nav-enter` translated the overlay in
 * from the inline start and is deleted with it: a panel that opens in place has
 * nowhere to travel from, and animating its height is a different decision that
 * nobody has asked for.
 *
 * The card keeps its fill and gives up its top corners, so it reads as
 * continuous with the control rather than as a second card below it.
 */
.c-cluster-nav__disclosure[open] + .c-cluster-nav__panel {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.c-cluster-nav__disclosure[open] + .c-cluster-nav__panel .c-cluster-nav__panel-inner {
  border-start-start-radius: 0;
  border-start-end-radius: 0;
  padding-block-start: var(--wp--preset--spacing--30);
}

.c-cluster-nav__disclosure[open] .c-cluster-nav__control {
  border-end-start-radius: 0;
  border-end-end-radius: 0;
}

/* ── k. Desktop ──────────────────────────────────────────────────────────
 *
 * The disclosure goes, the panel is always shown, and the sidebar becomes
 * sticky in its column — capped at the viewport, scrolling internally.
 *
 * STICKY WITHOUT A CAP WOULD BE WRONG RATHER THAN MERELY GENEROUS. The Working
 * Together panel is 828px after the target-size fix, against a laptop viewport
 * of roughly 660px, so the bottom of the list would be unreachable. The cap
 * plus the inner scroll is what makes the sticky column honest.
 *
 * THE OFFSET REUSES `--c-header-height`, which assets/js/header-stance.js
 * publishes live on `:root` and which base.css already consumes for
 * scroll-margin. The 0px fallback is correct rather than a degradation: with
 * scripts off the variable is absent, and with scripts off the header is not
 * sticky either, because the stance module is what makes it so. Nothing is owed
 * to the offset in that state.
 *
 * THE CAP IS ON THE CARD HERE AND ON THE NAV AT MOBILE, and the asymmetry is
 * deliberate rather than an oversight. At desktop the card's own edges should
 * stay put while the list scrolls inside them; capping the nav instead would
 * scroll the card's top corners out of view, which reads as the panel being cut
 * off. At mobile the card has no top corners to protect, because it is
 * continuous with the control above it. NOTE FOR THE JS: this is why
 * `cluster-sidebar.js` cannot assume one scroll container — the scroller is the
 * inner card here and the panel at mobile.
 *
 * `100vh`, NOT `100dvh`, and this is the opposite call from b. Desktop browser
 * chrome does not retract, so the two resolve the same; `vh` is kept because it
 * is what the rule has always been measured against.
 *
 * THE NAV RETURNS TO A BLOCK. b. makes it a flex column to cap the mobile
 * panel, and that has no job here — the panel is not a scrolling child at this
 * size, the card is.
 *
 * The panel rules below repeat the selectors from h. and j. verbatim rather
 * than adding a class. Media queries add no specificity, so an equal selector
 * later in the file is what wins — and an equal selector is also what makes the
 * pair greppable as a pair.
 */
@media (min-width: 1200px) { /* bp-2 */
  .c-cluster-nav {
    display: block;
    max-height: none;
    position: sticky;
    top: calc(var(--c-header-height, 0px) + var(--wp--preset--spacing--30));
  }

  .c-cluster-nav__disclosure {
    display: none;
  }

  .c-cluster-nav__disclosure:not([open]) + .c-cluster-nav__panel,
  .c-cluster-nav__disclosure[open] + .c-cluster-nav__panel {
    display: block;
    min-height: 0;
    overflow: visible;
    overscroll-behavior: auto;
  }

  .c-cluster-nav__disclosure[open] + .c-cluster-nav__panel .c-cluster-nav__panel-inner {
    border-start-start-radius: var(--wp--custom--rounded--lg);
    border-start-end-radius: var(--wp--custom--rounded--lg);
    padding-block-start: var(--wp--preset--spacing--30);
  }

  .c-cluster-nav__panel-inner {
    max-height: calc(100vh - var(--c-header-height, 0px) - var(--wp--preset--spacing--60));
    overflow-y: auto;
  }
}

/* ── l. Editor-only completeness flags ───────────────────────────────────
 *
 * Rendered for `current_user_can( 'edit_pages' )` and for nobody else — gated
 * on the capability, never on WP_DEBUG, which is off in production, which is
 * where the mistake gets made. A visitor's page never carries these rules'
 * markup, so nothing here is ever paid for by Linda.
 *
 * COLOURS DERIVED 2026-09-02 FROM THE PRIMITIVE SCALE CHANNEL, not invented and
 * not a palette change. Text `warning-11` on `warning-3` measures 4.81:1, which
 * clears AA on 13px text. The mockup's original #7a4405 was not a token and is
 * withdrawn.
 *
 * The left bar is `warning-9` at 2.75:1 on the panel, below the 3:1 a non-text
 * indicator would owe. It is exempt under the ruled edge test in
 * `gravitysite-header-standards.md` section 5: the flag is already separated
 * from the panel by its own fill, and the bar refines that separation rather
 * than carrying it. Recorded here so it is not re-derived as a defect.
 */
.c-cluster-nav__flag {
  display: block;
  font-size: var(--wp--preset--font-size--text-1);
  line-height: 1.4;
  color: var(--wp--custom--color--warning-11);
  background-color: var(--wp--custom--color--warning-3);
  border-inline-start: 3px solid var(--wp--custom--color--warning-9);
  border-radius: 0 var(--wp--custom--rounded--sm) var(--wp--custom--rounded--sm) 0;
  padding: var(--wp--preset--spacing--20) var(--wp--preset--spacing--30);
}

.c-cluster-nav__flag strong {
  font-weight: var(--wp--custom--weight--strong);
}

.c-cluster-nav__flag code {
  font-size: 0.9em;
}

/* The foot group — direction two of the completeness check, and the half the
 * whole design turns on. Separated by a dashed rule because it is not part of
 * the index: these pages are not in the sidebar, they are the report that they
 * are missing from it. */
.c-cluster-nav__flag-group {
  margin-top: var(--wp--preset--spacing--40);
  padding-top: var(--wp--preset--spacing--30);
  border-top: 1px dashed var(--wp--custom--color--warning-9);
}

.c-cluster-nav__flag-heading {
  font-size: var(--wp--preset--font-size--text-1);
  font-weight: var(--wp--custom--weight--strong);
  line-height: 1.4;
  color: var(--wp--custom--color--warning-11);
  margin: 0 0 var(--wp--preset--spacing--20);
}

/* A flagged row has no anchor, so the row height and the row states above do
 * not reach it. Give it the same left inset the links have, so the flag lines
 * up with the items it sits among rather than reading as a different list. */
.c-cluster-nav__flag-group ul li + li,
.c-cluster-nav li > .c-cluster-nav__flag {
  margin-top: var(--wp--preset--spacing--20);
}

/* ── m. Reduced motion ───────────────────────────────────────────────────
 *
 * The chevron turns without turning, and the row states change without easing.
 * The animation half of this rule went with the overlay at 4.30.0 — there is no
 * `animation` in this file any more, so suppressing one would be a guard against
 * nothing. Scoped to this component rather than global: a fleet-wide
 * reduced-motion rule is a base.css decision and this file is not the place to
 * make it.
 */
@media (prefers-reduced-motion: reduce) {
  .c-cluster-nav * {
    transition-duration: 0.01ms;
  }
}
