/* base.css V1.13 */
/* V1.13 - 2026-09-19, Jackie. `SYS-09` - THE GAP BELOW A SECTION HEADING, which
   the block comment above `.h-sec-label` has promised since 08-17 while the
   declaration read `margin: 0` on both edges. `SYS-26`(a) paid the generous-
   above half; this is the tight-below half. Shape B on the Orchestrator's
   ruling and Meryl's measurement - 136 emission sites, re-counted off the tree
   here and 136 again - at `--sp-2`, the value `S2-02` already shipped for the
   same visual relationship.
   ⚠️ AND ONE THING THE RULING'S SAFETY ARGUMENT IS NOT TRUE OF, now written
   down rather than discovered later: a margin collapses in normal flow and
   DOES NOT inside a flex or grid parent, where it ADDS to the container's gap.
   That case was already answered before this row existed - `.h-wsec
   .h-sec-label { margin: 0 }` - and `* + .h-sec-label`'s 22px has been
   shipping on the identical dependency since `SYS-26`(a) with that same one
   exemption. `tools/seclabel-gap.mjs` (NEW) asserts the collapse itself, the
   exemption, and that no label on any static page sits in a flex parent
   carrying this margin. Five mutations, including shape A and a padding-bottom
   of the same size, which every distance measurement in that tool passes.
   `--sub` is IN, named rather than silent, with its own assertion. */
/* V1.12 - 2026-09-19, Jackie. `S2-14`, and `SYS-07` did not regress - a SECOND
   mechanism was never covered. Safari zooms on DOUBLE TAP as well as on a small
   field, and it waits ~300ms after every tap to find out. Pressing + twice to
   ask for two goalies IS a double tap, on the one control where repeated
   tapping is the intended interaction. `touch-action: manipulation` on the
   interactive elements removes that gesture and keeps pinch, panning and
   browser zoom. Measured first: `touch-action` computed `auto` on every
   tappable in the app, because nothing anywhere had ever set it. */
/* base.css V1.11 */
/* V1.11 - 2026-09-22, Vera. `SYS-49`, the design half. `.h-sec-label--sub`'s
   weight goes back to a token lookup now that `--head-weight-sub` actually
   exists in all five blocks, and the 700 fallback is deleted rather than kept:
   a missing slot should fail visibly, not resolve to the chapter's own weight,
   which is how this hid for a full release. openice and street are unchanged. */
/* V1.10 - 2026-09-21, Orchestrator. ONE DECLARATION, PIXEL-IDENTICAL, AND IT IS
   A LEDGER REPAIR RATHER THAN A DESIGN CHANGE. `.h-sec-label--sub`'s
   `font-weight` was a token lookup with a 700 fallback on a name declared
   nowhere, so it always computed to 700; `css-balance` reds on exactly that
   shape ("a literal fallback does not excuse a token that does not exist").
   Written flat. Nothing about the rendered weight changes on any style.
   ⚠️ THE DESIGN QUESTION THE LINT EXPOSED IS `SYS-49` AND IT IS VERA'S: on gm,
   pond and the default the chapter weight and this sub-level are BOTH 700, so
   the contrast this modifier exists to create is absent on three styles out of
   four. Not repaired here - see the note at the rule. */
/* V1.9 - 2026-09-21, Vera. `SET-01` reopened, defect 2 of 3. `.h-sec-label--sub`
   — the QUIET heading level, which did not exist. Settings renders ten headings
   with two computed signatures between them, differing only by a margin, so a
   choice inside a section looks exactly like a section. Purely additive: the
   base class does not move, so none of its 23 callers move either. Ink and the
   rule carry the demotion; no font-size change, so no box changes. */
/* V1.8 - 2026-09-19, Vera. The ruled heading build, both halves. (A) font-size
   11px -> 12.5px, so the section heading finally out-ranks the row copy beneath
   it; the cost is +2.25px on the label's own box and zero sideways, measured in
   every shape the app uses. (B) a rule under the label, drawn from the new
   `--head-rule` slot as a BACKGROUND IMAGE so it costs no layout at all.
   Suppressed in two contexts where rendering showed it wrong — a label inside
   `.h-wsec`, and a `<caption>` — both explained at the rule. */
/* V1.7 - 2026-09-15, Vera. `SET-01` half two. `.h-sec-label` paints --ink, was
   --muted. The section heading was 11px/--muted while the prose under it is
   12.5px/--muted (hub.css:898), so the heading lost to the paragraph and the
   only boundary marker on the screen read as body copy. Colour only: the line
   box does not change, so nothing moves anywhere. The container half is
   `settings.js` and is not mine. Reasoning at the rule. */
/* V1.6 - 2026-09-15, Vera. `SYS-04`: `.h-sec-label` now reads --head-font,
   --head-case, --head-track, --head-weight and --head-shadow instead of
   hard-coding them. Defaults equal the old literals, so nothing moves and
   nothing changes until a style sets a slot. font-size deliberately NOT
   tokenised - it is the one type property that resizes the line box. */
/* V1.5 - 2026-09-06, Meryl. `SYS-26`(a) - the section gap. The rule the header
   comment promised ("generous above, tight below") did not exist: `.h-sec-label`
   read `margin: 0`, so a section break was the same height as a row break, which
   is Karl's "not enough separation between sections" measured on Settings.
   `* + .h-sec-label { margin-top: 22px }` - Vera's ratified mock 1 value, on the
   SIBLING per Karl's correction that the gap is a separator. Reasoning at the rule.
   ⛔ `SYS-26`(b), the grouped container, is NOT in this bump - see the outbox. */
/* V1.4 - 2026-09-05, Meryl. Bug Sweep 1 package A, `SYS-07`.
   `input, select, textarea` no longer inherit their size. `font-size: inherit`
   meant `--fs-base`, 15px, one pixel under the floor at which mobile Safari
   zooms the whole viewport on focus - measured across 27 field classes, 24 of
   them under the line. Specificity stays at (0,0,1) so a component that really
   wants a different size still wins. `button` keeps `inherit`. */
/* V1.3 — 2026-09-04, Vera. ONE CHANGE, AND IT IS AN ACCESSIBILITY FIX RATHER
   THAN A STYLE: `:focus-visible` was `outline: 2px solid var(--green)`, and
   --green is the ACCENT — so the keyboard focus ring was the accent colour
   painted on the accent fill and measured 1.00:1 on the primary button in both
   themes. It is two-tone now; worst case 13.62:1 against WCAG 2.2's 3.0 bar,
   measured across every surface token in both themes.
   ⚠️ THIS FILE IS BYTE-IDENTICAL WITH h365-site/assets/css/base.css. A change
   here is a two-surface change; both copies were updated together. */

/* Fonts ---------------------------------------------------------------------
   Public Sans (body) is loaded from Google Fonts in the page head.
   Mona Sans (headings) must be SELF-HOSTED — drop the variable woff2 at
   /assets/fonts/Mona-Sans.woff2 and this @font-face picks it up. Until that
   file exists the stack falls back to Public Sans, which is why every heading
   rule names both.  [TODO T-002a: add the Mona Sans woff2 to the repo] */
@font-face {
  font-family: 'Mona Sans';
  src: url('/assets/fonts/Mona-Sans.woff2') format('woff2 supports variations'),
       url('/assets/fonts/Mona-Sans.woff2') format('woff2-variations');
  font-weight: 200 900;
  font-stretch: 75% 125%;
  font-style: normal;
  font-display: swap;
}

*, *::before, *::after { box-sizing: border-box; }

/* The `hidden` ATTRIBUTE only sets display:none at the lowest specificity, so
   ANY display rule beats it — including `display:grid` on the element itself.
   That is how the boot screen stayed on the page above the app: hidden was set,
   and ignored. This is the single most common way `hidden` silently fails, so
   fix it once, globally, rather than per component. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: var(--lh);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

h1, h2, h3, h4, .h-head {
  font-family: var(--font-head);
  font-weight: 800;
  letter-spacing: -.01em;
  margin: 0;
}

svg { display: block; }
img { max-width: 100%; }

/* SYS-07 — MOBILE SAFARI ZOOMS THE WHOLE VIEWPORT WHEN A FOCUSED FIELD
   COMPUTES UNDER 16px, AND THIS LINE IS WHY IT DID IT EVERYWHERE.
   Karl: "extremely annoying because I have to unhook every time I type
   something." `font-size: inherit` on a field means `--fs-base`, which is
   15px — ONE PIXEL under the line, on every field in the app that does not
   name its own size. Measured: 27 classes land on an input/select/textarea and
   24 of them were under the floor, 20 of those purely by inheriting this.
   16px is the only lever: no other CSS stops that zoom, and the alternative —
   `maximum-scale=1, user-scalable=no` in the viewport meta — takes pinch-zoom
   away from everybody to fix a keyboard annoyance, which is a bad trade and is
   not what any page in this app does.
   SPECIFICITY IS DELIBERATELY LEFT AT (0,0,1) so a component that really wants
   a different size still wins; the four that did are raised at their own rules,
   named there. `button` keeps `inherit`: it is not a text-entry field and never
   triggered the zoom. */
button { font-family: inherit; font-size: inherit; color: inherit; }
input, select, textarea { font-family: inherit; font-size: 16px; color: inherit; }
button { cursor: pointer; }

/* 🔴 `S2-14` — THE SECOND WAY iOS ZOOMS, AND `SYS-07` NEVER COVERED IT.

   KARL REOPENED THE ZOOM COMPLAINT ON 2026-09-19: *“when I enter a field OR
   PRESS THE + OR - BUTTON, it is auto zooming a bit… I do not want auto zooming
   anywhere in this app.”*

   ✅ `SYS-07` HELD. Its fix is the 16px floor two lines above, and it is
   intact: measured on the exact sheet Karl named (`roleAskHtml`, the
   who-is-playing block with the + and -), **all nine visible fields compute to
   exactly 16px.** Nothing regressed.

   ⚖️ WHAT HE IS HITTING IS A DIFFERENT MECHANISM WITH THE SAME SYMPTOM.
   Safari on iOS also zooms on DOUBLE TAP, and it waits ~300ms after every tap
   to find out whether a second one is coming. **Pressing + twice to ask for two
   goalies IS a double tap**, on a 44px target, in the one place on the screen
   where repeated tapping is the intended interaction. A font-size sweep could
   never have found this and a second font-size sweep would not fix it.

   📏 MEASURED BEFORE THE RULE WAS WRITTEN: `touch-action` computed `auto` on
   **every tappable in the app** — nothing anywhere had ever set it. `auto`
   means the double-tap heuristic is live on all of them.

   ⛔ AND THIS IS NOT `user-scalable=no`, WHICH IS THE OTHER OBVIOUS ANSWER AND
   IS THE WRONG ONE. `SYS-07`'s own note above says why: *“it takes pinch-zoom
   away from everybody to fix a keyboard annoyance, which is a bad trade and is
   not what any page in this app does.”* `manipulation` keeps pinch, keeps
   panning, keeps browser zoom, and removes only double-tap-to-zoom — a gesture
   that on a BUTTON was never doing anything a member wanted.

   🔑 ELEMENT SELECTORS, SPECIFICITY (0,0,1), ON PURPOSE. Three controls in
   this app already set `touch-action: none` because a pointer drag owns their
   gesture — the waiver signature pad, the dock's resize grab and a widget being
   dragged on the hub — and all three are CLASS selectors, so they still win.
   Checked before writing this rather than after. */
button, [role="button"], a, label, summary,
input, select, textarea { touch-action: manipulation; }

a { color: var(--green); text-decoration: none; }

/* Focus — visible for keyboards, quiet for pointers.

   TWO-TONE SINCE 2026-09-04, and the reason is measured rather than aesthetic:
   this rule was `outline: 2px solid var(--green)`, and --green is the ACCENT --
   so the focus ring was the accent colour painted on the accent fill and
   measured **1.00:1 on the primary button in both themes**. It was also 1.17 on
   a warn fill in dark and 1.08 on a danger fill in light.

   The inner ring hugs the control and the outer ring sits behind it. Whatever
   the control is painted, one of the two contrasts: worst case 4.25:1 over 24
   surface/theme combinations, against WCAG 2.2's 3:1 bar. See the full
   reasoning and the measurements beside --ring-inner in tokens.css. */
:focus-visible {
  outline: var(--ring-w) solid var(--ring-inner);
  outline-offset: var(--ring-w);
  box-shadow: 0 0 0 var(--ring-w) var(--ring-outer);
  border-radius: var(--r-sm);
}

/* Scrollbars */
.h-scroll::-webkit-scrollbar { width: 5px; height: 5px; }
.h-scroll::-webkit-scrollbar-thumb { background: var(--line2); border-radius: 4px; }
.h-noscroll::-webkit-scrollbar { display: none; }
.h-noscroll { scrollbar-width: none; }

/* Utility ------------------------------------------------------------------ */
/* CONTRAST RULE (site-wide): anything a member has to READ uses --muted or
   brighter. --faint (#6D6E71) is ~3.8:1 on our dark background — it fails WCAG
   AA for small text and vanishes under sun glare at a rink. Reserve --faint for
   decorative and tertiary marks that carry no information. */
.h-eyebrow {
  font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase;
  font-weight: 800; color: var(--muted);
}
/* THE SECTION LABEL, AND THE RHYTHM AROUND IT.

   Karl, 2026-08-13, looking at the group room's Schedule tab:

     "there is something ugly about the way these buttons are organized with
      the data below them, especially the Upcoming label... Upcoming is much
      too close to the buttons and the buttons look awkward where they are...
      i want the app to be coherent and premium, not just a bunch of features
      and labels thrown onto a page."

   HE IS DESCRIBING A MISSING RULE, NOT ONE BAD SCREEN. This class had NO
   MARGIN AT ALL, so it took whatever gap the element above happened to leave —
   4px under a button row here, 14px under a card there, and the same label
   reading differently on every screen. A label with no rhythm of its own reads
   as a caption of the thing ABOVE it, which is exactly backwards: it is the
   start of what comes BELOW.

   Generous above, tight below. That is the whole trick, and it is one rule
   here rather than a margin typed into thirty call sites. */
.h-sec-label {
  display: block;
  /* 11px -> 12.5px, and this IS the line that moves boxes. Ruled 09-19 after
     the ladder was measured: at 11px the heading was SMALLER than the row copy
     under it (`.h-more__s` 11.5px, `.h-lr__hint` 12.5px), so it could not read
     as a heading however bright it was painted. Cost, measured headless at the
     real inherited `--lh: 1.5`, not guessed:
       plain block / <h2> / <summary>  16.50px -> 18.75px   +2.25
       <caption> in a stats table      12.00px -> 14.00px   +2.00
       a heading sharing a flex row    22.50px -> 22.50px   +0.00  (absorbed)
     Nothing reflows sideways. A sheet with two labels grows 4.5px; Settings,
     with twelve, about 27px. */
  font-size: 12.5px;
  /* `SYS-04`. These four are the style's, not this file's. Their :root defaults
     are the values that were hard-coded here, so this is a zero delta until a
     style sets one. font-SIZE stays here on purpose: it is the one type
     property that changes the line box, and Karl's rule is that a style
     repaints without moving anything. */
  font-family: var(--head-font);
  letter-spacing: var(--head-track);
  text-transform: var(--head-case);
  font-weight: var(--head-weight);
  text-shadow: var(--head-shadow);
  /* `SET-01`, AND THE REASON IS A MEASUREMENT RATHER THAN A TASTE. On the
     Settings screen four rules render at once:

       .h-frow__label  row title       14px / 600 / --ink
       .h-lr__hint     running prose   12.5px / 400 / --muted   (hub.css:898)
       .h-sec-label    THIS, a heading 11px / 700 / --muted
       .h-frow__sub    row subtitle    11px / 600 / --faint

     The section heading was SMALLER THAN THE PARAGRAPH BENEATH IT AND PAINTED
     THE SAME COLOUR. The one element on the screen marking a boundary was
     out-ranked by ordinary copy, which is Karl's "text blob" in four lines of
     CSS. Containers alone do not fix it: they draw a box around a heading that
     still does not read as one.

     V1.7 took the COLOUR only and said so: font-size is the one type property
     that resizes the line box, so an ink change moved nothing. It also did not
     fix the ranking — the heading stayed the smallest text on the screen, and
     Karl said so again on the More sheet two days later. V1.8 takes the size
     as well, with the cost measured rather than argued; that note is up at the
     `font-size` line. Both halves are here, and the ink half is still the one
     that costs nothing. */
  color: var(--ink);
  /* THE RULE, AND IT IS A BACKGROUND IMAGE FOR THE SAME REASON `--card-edge` IS
     A SHADOW: a border-bottom grows the box and pushes everything below it,
     and this element is used 23 ways. A background paints inside the box the
     element already has and moves nothing — measured: 18.75px with the rule
     and 18.75px without.
     `--head-rule` is the style's, so street draws purple, pond draws its own
     paper line, and light flips to purple on its own through `--green-rgb`. */
  background-image: linear-gradient(var(--head-rule), var(--head-rule));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 2px;
  /* 📐 `SYS-09` — THE GAP BELOW, AND IT IS THE HALF THE COMMENT ABOVE THIS
     BLOCK HAS PROMISED SINCE 08-17 WITHOUT DELIVERING. *"Generous above, tight
     below"* described a rule that read `margin: 0` on BOTH edges; `SYS-26`(a)
     paid the generous half and left the tight half at nothing, so a heading
     still sat flush on the thing it heads.
     🗣️ KARL: *"Everything sits too tight together."* 📏 MERYL COUNTED IT
     RATHER THAN ESTIMATING IT — **136 emission sites**, which this seat
     re-counted off the tree independently and got 136 too.
     ⚖️ **SHAPE B, THE ORCHESTRATOR'S RULING, AND IT IS THE MARGIN RATHER THAN
     A `gap` OR A RULE ON THE FOLLOWER:** a bottom margin **collapses** with
     whatever the follower already carries and the LARGER wins, so it fills the
     empty gaps and leaves the intentional ones exactly as they are. A rule that
     ADDED would make spacing worse, silently, in a hundred places nobody can
     enumerate.
     🔴 AND THE ONE THING THAT ARGUMENT IS NOT TRUE OF, SAID OUT LOUD: **margins
     do not collapse inside a flex or grid parent** — there this margin ADDS to
     the container's `gap`. ✅ That case is already answered and was answered
     before this row existed: `.h-wsec .h-sec-label { margin: 0 }` thirty lines
     down, written for exactly this reason (*"a label laid out by its parent
     keeps the parent's spacing"*). 🥇 **And the same dependency is already
     shipping**: `* + .h-sec-label`'s 22px collapses the same way and has been
     live since `SYS-26`(a) with `.h-wsec` as its only exemption. `--sp-2`
     matches what `S2-02` shipped for the same visual relationship; a second gap
     value for one relationship is the two-vocabularies defect this board keeps
     naming.
     🔑 `--sub` IS IN, AS A NAMED DECISION RATHER THAN A SILENT ONE. It is 20
     of the 136 and it matches this selector, so it is affected either way —
     and it is safe for the same collapse reason. Asserted separately. */
  margin: 0 0 var(--sp-2);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* ⛔ TWO PLACES THE RULE IS WRONG, AND I FOUND BOTH BY RENDERING THEM RATHER
   THAN BY READING THE SELECTOR LIST.
   ① A LABEL LAID OUT BY ITS PARENT IS NOT A SECTION HEAD. In `.h-wsec` the
      label shares a flex row with a count, so it is only as wide as its own
      word: the rule came out as a 47px stub under "Trend" with "12 games"
      floating unruled beside it. That reads as an underlined word, not a
      section boundary.
   ② A TABLE CAPTION ALREADY HAS A LINE. `stats.js` uses `.h-sec-label` on two
      `<caption>`s; a caption is centred, so the full-width rule does not relate
      to the text sitting above it, AND the table's own first `border-top`
      lands 1px below it. Two parallel lines that look like a mistake. */
.h-wsec .h-sec-label,
caption.h-sec-label { background-image: none; }
/* ===========================================================================
   `SET-01` — THE SECOND LEVEL, AND IT IS THE LEVEL THAT WAS MISSING.

   🗣️ KARL, REOPENING `SET-01`, `SPC-08` AND `SYS-10` WITH ONE SENTENCE:
   *"these type of screens still appear like a wall of text, the different rows
   kind of run together and there is no sectioning or clear organization."*

   📏 MEASURED ON SETTINGS BEFORE CHANGING ANYTHING: **ten headings on the
   screen and TWO distinct computed signatures between them — and the only
   thing that differs is a margin.** Every one is 12.5px / 800 / `--ink` /
   uppercase / 2px tracking with the same rule under it. So "Dark or light",
   which is a choice INSIDE Appearance, paints identically to "Account", which
   is a top-level division of the screen. **There is no hierarchy, so there is
   no organization — that is Karl's second clause, and it is arithmetic.**

   ⚖️ THE RULE THIS ADDS TO MY OWN RATIFIED ONE. "A section boundary must be a
   different KIND of thing, an edge, not a bigger gap" is still right and it is
   not enough by itself: **AN EDGE THAT REPEATS AT EVERY LEVEL STOPS BEING AN
   EDGE.** A level is made by CONTRAST between two treatments, never by giving
   both of them the loudest one.

   ✅ SO `.h-sec-label` IS UNCHANGED AND IS THE LOUD LEVEL — a CHAPTER, of which
   a screen has three or four. This modifier is the quiet one: a labelled run
   INSIDE a chapter. ⛔ ADDITIVE ON PURPOSE AND THAT IS THE WHOLE POINT OF DOING
   IT THIS WAY ROUND: `.h-sec-label` is emitted by 23 files, so demoting it
   would repaint every screen in the app on a pass that can only verify one.
   Nothing that does not ask for `--sub` moves by a pixel.
   📌 NO `font-size` CHANGE, DELIBERATELY. Size is the one type property that
   resizes the line box (the note at the `font-size` line above carries the
   measurements), so the demotion is carried by INK and by dropping the rule —
   both of which cost zero layout. Measured: 18.75px with and without.
   ========================================================================= */
.h-sec-label--sub {
  color: var(--muted);
  background-image: none;
  /* 🪞 THE HISTORY OF THIS ONE DECLARATION IS THE POINT OF `SYS-49`.
     V1.9 (mine) wrote `var(--head-weight-sub, 700)` against a name declared
     NOWHERE. `css-balance` red it on the first run — *"a literal fallback does
     not excuse a token that does not exist"* — and the Orchestrator flattened
     it to a bare 700 in V1.10, correctly, as a pixel-identical ledger repair.
     ⚖️ BUT THE LINT WAS POINTING AT A DESIGN DEFECT AND ONLY THE COSMETIC HALF
     HAD BEEN PAID. The literal 700 is the SAME VALUE as `--head-weight` on the
     default, on gm and on pond: 📏 six of ten theme x style combinations had a
     "quiet" level with no weight contrast whatsoever, carried only by ink.
     ✅ THE TOKEN NOW EXISTS IN ALL FIVE BLOCKS (`tokens.css` V2.1) with a value
     chosen per style from what that style's FACE CAN RENDER — so this is a real
     lookup rather than a lookup-shaped literal, and the fallback is gone.
     ⚠️ AND THE THING I WROTE HERE FIRST WAS WRONG, SO IT IS WORTH WRITING DOWN
     CORRECTLY: I claimed a `var()` with no fallback would "fail loudly if a
     future style forgets the slot". IT DOES NOT. `:root` declares the slot, so
     a style that omits it INHERITS 600 — silently, and wrongly for any style
     whose face is not Public Sans. 📏 Measured by deleting pond's declaration:
     pond went 400 → 600 with nothing red anywhere. **The guard against that is
     not this rule, it is `tests/tokens.test.mjs`**, which pins that every
     fallback lands on a token that exists and which is what caught this row in
     the first place — verified by mutation, 5 passed / 1 failed on the original
     shape. */
  font-weight: var(--head-weight-sub);
}
/* 🔴 `SYS-26`(a) — THE RULE THE COMMENT ABOVE PROMISES DID NOT EXIST.
   "Generous above, tight below. That is the whole trick, and it is one rule
   here rather than a margin typed into thirty call sites" — and the rule read
   `margin: 0`. There was no generous above. The `:first-child` exception below
   only makes sense against a margin that was never there, so the prose and its
   own exception both outlived the declaration between them.
   📌 THAT IS KARL'S COMPLAINT EXACTLY: *"Some of the issues here is just not
   enough separation between sections"* — on `Settings 1.PNG` the gap above
   "DARK OR LIGHT" is the same height as the gap between two rows, because both
   are the row's own 13px padding and nothing else.
   ✅ 22px is Vera's ratified mock 1 value, and it sits on the SIBLING rather
   than on the label because that is Karl's own correction (2026-09-05): the gap
   is a SEPARATOR, so it belongs to the boundary and not to the heading. Keep
   this rule and the `:first-child` one adjacent and in that order — the
   exception is only readable next to the thing it excepts. */
* + .h-sec-label { margin-top: 22px; }
/* AT THE TOP OF A PANEL there is nothing to separate it from, and 22px of air
   above the first thing on a screen reads as a rendering fault. */
.h-sec-label:first-child { margin-top: 2px; }
/* A GROUP HEADING SITS CLOSER TO ITS CHAPTER THAN A CHAPTER DOES TO THE ONE
   BEFORE IT. 22px is the separation between two chapters; inside one, that same
   gap is what made "Dark or light" read as a new chapter in the first place.
   ⛔ IT MUST SIT AFTER `* + .h-sec-label`, NOT BESIDE THE `--sub` PAINT RULES.
   Both selectors are (0,1,0) and both match a sub heading, so ORDER is the only
   thing that decides, and I landed this above the 22px rule first time and
   measured 22px coming back. Keep these three adjacent and in this order. */
* + .h-sec-label--sub { margin-top: 14px; }

/* A LABEL LAID OUT BY ITS PARENT keeps the parent's spacing — a heading with a
   count beside it is one row, not a stacked block. */
.h-wsec .h-sec-label { margin: 0; }
.h-sub   { color: var(--muted); font-size: 12.5px; }
.h-muted { color: var(--muted); }
.h-faint { color: var(--faint); }
.h-accent{ color: var(--green); }
.h-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.h-row  { display: flex; align-items: center; gap: var(--sp-3); }
.h-grow { flex: 1; min-width: 0; }

/* Section cross-fade — nothing hard-cuts. */
@keyframes h-secin { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }
.h-fadein { animation: h-secin var(--t-med); }
