/* ═══════════════════════════════════════════════════════════════════════════
   Homefield — the web front door. Direction: TURF & CHALK v1.0
   (docs/design/design-system.md).

   THE RULE, restated for the web: every colour on this site comes from the
   token block below and nowhere else. The block is a transcription of
   app/lib/ui/tokens/hf_tokens.dart (HfTokens.turf()). If a value changes
   there, change it here — do not invent a new one, and do not write a hex
   literal anywhere below the :root block, or in any .html file.

   app/tool/check_ui_boundary.sh enforces exactly that inside the Flutter app
   and does NOT cover web/. There is no build step here either. So the rule is
   kept by hand, and the check is one command:

       grep -o '#[0-9A-Fa-f]\{6\}' web/*.html    # must print nothing

   WHY THE NAMES LOOK LIKE THE DART AND NOT LIKE §2. §2 of the spec names its
   colours after what they are — turf, gold, sky, clay. HfTokens names them
   after what they DO — acc, acc2, ok — because CLAUDE.md's one architectural
   rule is that nothing downstream of the token layer may name a colour, which
   is what makes the app skinnable (Kit and Floodlit are the same widgets in a
   different block). These custom properties mirror the Dart field names so a
   grep for `--hf-acc-tint` and a grep for `accTint` find the same decision on
   both surfaces; §2's name for each value is in the comment beside it, so the
   spec is greppable too.

   A parent meets this site BEFORE the app (D7: sign-up is web-only, the app
   signs people in and never up). A landing page in one palette handing off to
   an app in another is the first thing they experience, and it reads as two
   products. That is the whole reason this file was rethemed.

   PURE SYSTEM FONTS. No @font-face, no Google Fonts, no external request of
   any kind. Settled in D2, restated by §3, and undoing it is a regression.

   NOTHING FLOATS AND NOTHING IS BEVELLED. §2: cards are a fill, a radius and a
   hairline — no elevation shadow. The one shadow on this site is under the
   Player Card, and it is HfTokens.turf().cardShadow, not a value invented here.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── §2 colour · transcribed from HfTokens.turf() ──────────────────────────
     Every ratio below is measured sRGB/WCAG 2.x, not estimated. */
  --hf-bg: #FBFBF8;         /* §2 chalk    · the canvas                        */
  --hf-surf: #FFFFFF;       /* §2 card     · only 1.04:1 above the ground —
                                              see the note on --hf-line        */
  --hf-ink: #17201B;        /* §2 ink      · 16.09 on bg / 16.68 on surf       */
  --hf-mut: #5C665F;        /* §2 ink60    · 5.75 / 5.96 — the ONLY secondary
                                              text step. §2's ink40 #98A19A is
                                              deliberately absent: 2.56:1 on
                                              chalk fails body text AND the 3:1
                                              non-text floor, and the repair
                                              lands on top of this value. Every
                                              string §2 paints ink40 is --hf-mut
                                              here, exactly as in the Dart.     */
  --hf-acc: #157A54;        /* §2 turf     · 5.14 / 5.32 — action, and nothing
                                              else. Never a decoration.        */
  --hf-acc-deep: #0C5238;   /* §2 turfDeep · 8.07 on --hf-acc-tint             */
  --hf-acc-tint: #E6F3ED;   /* §2 turfTint · ink 14.62 on it. 1.10 on chalk —
                                              a mood, NOT an edge.             */
  --hf-acc2: #8A5A08;       /* §2 goldDeep · 5.71 / 5.92 — earned, and honesty
                                              notes. §2's bright gold #E8A020
                                              is 2.14:1 on chalk and may not be
                                              a mark here; see --hf-gold.      */
  --hf-acc2-tint: #FBF1DC;  /* §2 goldTint · --hf-acc2 5.28 on it              */
  --hf-acc-ink: #FFFFFF;    /*               on acc 5.32, acc2 5.92, ok 7.30,
                                              danger 6.04                      */
  --hf-ok: #1B5A8A;         /* §2 skyDeep  · 7.04 / 7.30. RESERVED for VERIFIED
                                              — a counted, camera-witnessed
                                              thing. Not a general status hue. */
  --hf-ok-tint: #E9F1F8;    /* §2 skyTint  · --hf-ok 6.40 on it                */
  /* Hairline only: 1.18:1 on chalk, 1.22:1 on card. A rule, not a mark. Any
     control whose ONLY boundary is this line is identified at 1.22:1 where
     WCAG 1.4.11 wants 3:1 — so no control may borrow it. */
  --hf-line: #E7E9E4;       /* §2 line                                          */
  /* The stroke that IDENTIFIES a control. 3.02:1 on chalk, 3.13:1 on card.
     Not in §2 — the direction had no value between the 1.18:1 hairline and
     the 3:1 floor, and §4 hands the hairline four jobs that need one (the
     quiet chip, the ghost button, a ring track, an "ahead" dot). Added as a
     token rather than waived with a pragma. HfTokens.mark. */
  --hf-mark: #8C957D;
  --hf-danger: #B3362B;     /* §2 danger   · 5.82 / 6.04. Destructive confirm
                                              ONLY — never a missed goal, never
                                              progress UI.                      */
  --hf-cam: #17201B;        /* camera viewport = ink                            */
  /* HfTokens.kHfGold. §2's `gold`. A FILL ON DARK GROUND, NEVER A MARK on
     chalk or card: 7.53:1 on --hf-cam, 2.14:1 on the app ground. The hero
     placeholder's skeleton is the only place this site spends it. */
  --hf-gold: #E8A020;

  /* The Player Card · HfTokens.turf().card, a deep turf pitch falling into
     ink. Alignment(-0.4,-1) → (0.4,1) is 158deg in CSS. Both stops are
     declared palette values on purpose — an invented shadow colour is how a
     gradient ends up with an interpolated pixel nobody swept. */
  --hf-card-0: #0C5238;
  --hf-card-1: #17201B;
  --hf-card-grad: linear-gradient(158deg, var(--hf-card-0) 0%, var(--hf-card-1) 100%);
  --hf-card-ink: #FBFBF8;   /* HfTokens.cardInk · 8.89 on the top stop, 16.09
                                on the bottom                                  */
  /* HfTokens.turf().cardShadow — the ONE shadow on this site (§2: bottom
     sheets and the resume pill are the only other two, and neither is a web
     surface). Cards use a border and nothing else. */
  --hf-card-shadow: 0 8px 28px rgba(23, 32, 27, .10);

  /* ── the two alpha derivatives ────────────────────────────────────────────
     Both are a declared token at reduced opacity, and both live up here so the
     rule this file opens with — no colour below the :root block — stays
     literally true rather than nearly true. The channels are --hf-ink
     (23,32,27) and --hf-card-ink (251,251,248); nothing new is introduced.
     Not color-mix() or relative colour syntax: the hatch is one of the three
     signals that say a control is switched off, and it is not worth losing on
     the cheap Android phone this product is used from. */
  --hf-hatch: repeating-linear-gradient(45deg,
    rgba(23, 32, 27, .07) 0 5px, rgba(0, 0, 0, 0) 5px 11px);
  --hf-sheen: radial-gradient(38% 46% at var(--hf-mx) var(--hf-my),
    rgba(251, 251, 248, .85), rgba(251, 251, 248, 0) 70%);

  /* ── shape · HfTokens.r / .rcard / .rchip ─────────────────────────────────
     §4.1 and §4.3 ask for pill radii. The app cannot express one: `rchip` is
     derived as r * 0.7 for every skin at once, and changing that derivation to
     chase one direction would silently re-round Kit and Floodlit too — the
     argument is recorded on the Turf factory and was settled there. A pill
     here and a 16px control in the app would be the same drift this retheme
     exists to close, so the web follows the app. */
  --hf-r: 16px;             /* HfTokens.r                                      */
  --hf-rcard: 14px;         /* HfTokens.rcard — the Player Card                */
  --hf-rchip: 11.2px;       /* HfTokens.rchip = r * 0.7                        */

  /* ── §3 type ──────────────────────────────────────────────────────────────
     One system grotesque — SF Pro on Apple, Roboto on Android, Segoe UI on
     Windows — which is what `fontFamily: null` resolves to in Flutter. Never
     add a webfont to this list.

     WEIGHTS 400 / 600 / 700 ONLY (§3). Nothing heavier ships; the personality
     is weight discipline and the palette, not a typeface.

     The sizes are §3's ramp × 1.14, applied ONCE here rather than rule by
     rule, so the RATIOS are the app's and only the base moves. §3 is written
     in logical pixels for a phone held in the hand; this is a 1080px page read
     at arm's length, and 14px body on it is a squint. 1.14 also puts body at
     16px, the size below which iOS Safari zooms a focused input — which would
     otherwise fire on every field in the sign-up form. The app value each one
     comes from is in the comment, so the two are still greppable. */
  --hf-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
             system-ui, "Helvetica Neue", Arial, sans-serif;

  --hf-t-display: 27px;     /* §3 displayTitle 24 · w700 · -.02em              */
  --hf-t-card: 17px;        /* §3 cardTitle    15 · w600                       */
  --hf-t-prose: 16px;       /* §3 body         14 · w400 · 1.45                */
  --hf-t-secondary: 14px;   /* §3 secondary  12.5 · w400 · --hf-mut            */
  --hf-t-chip: 13.5px;      /* §3 chipText     12 · w600 (§3 tables w500 and
                                then lists 400/600/700 as the only legal
                                weights; the list wins, as in HfType)          */
  --hf-t-eyebrow: 12.5px;   /* §3 eyebrow      11 · w600 · +.07em · UPPERCASE   */
  --hf-t-hero: 30px;        /* §3 heroNumber   26 · w700                       */
  --hf-t-kid: 23px;         /* §3 kidDisplay   20 · w700                       */

  --hf-gap: clamp(20px, 4vw, 34px);
  --hf-sec: clamp(44px, 7vw, 76px);
}

/* ── reset ────────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

/* [hidden] must win. Several components below set an explicit display, and a
   class beats the UA stylesheet's [hidden] rule without this. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background-color: var(--hf-bg);
  color: var(--hf-ink);
  font-family: var(--hf-font);
  font-size: var(--hf-t-prose);
  font-weight: 400;
  line-height: 1.45;
  /* Tabular LINING numerals everywhere. The OVR must never inherit old-style
     figures and an age column must never jitter as digits change. */
  font-variant-numeric: tabular-nums lining-nums;
  -webkit-font-smoothing: antialiased;
}

img, svg { max-width: 100%; }
img { height: auto; display: block; }

h1, h2, h3, h4 { margin: 0; font-weight: 700; letter-spacing: -.02em; text-wrap: balance; }
p, ul, ol, dl, dd { margin: 0; }
ul, ol { padding: 0; }

/* A link in prose is turf, like every other action. Left to the UA it would be
   the browser's blue — a colour literal this site does not own and cannot
   theme. Components that are themselves links (.hf-btn, .hf-mark, .hf-trustrow)
   set their own colour and beat this. */
a { color: var(--hf-acc); }

/* ── focus · always visible, never removed ────────────────────────────────── */
:focus { outline: none; }
:focus-visible {
  /* Ink, offset onto the ground: 16.09:1 on chalk, 16.68:1 on a card.
     Deliberately not turf — a turf ring on a turf button is invisible. */
  outline: 3px solid var(--hf-ink);
  outline-offset: 2px;
  border-radius: 3px;
}

.hf-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 20;
  padding: 12px 18px;
  background: var(--hf-acc);
  color: var(--hf-acc-ink);
  font-size: var(--hf-t-prose);
  font-weight: 600;
  text-decoration: none;
  border-radius: 0 0 var(--hf-r) 0;
}
.hf-skip:focus { left: 0; }

.hf-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── layout ───────────────────────────────────────────────────────────────── */
.hf-wrap { width: 100%; max-width: 1080px; margin: 0 auto; padding: 0 var(--hf-gap); }
/* A form is read in one column. 760px is about 70 characters at this body
   size, which is where a line stops being comfortable. */
.hf-wrap--narrow { max-width: 760px; }
.hf-page { padding-top: clamp(28px, 5vw, 48px); padding-bottom: clamp(44px, 7vw, 72px); }
.hf-section { padding: var(--hf-sec) 0; border-top: 1px solid var(--hf-line); }
.hf-section:first-of-type { border-top: none; }

/* The ONE spacing utility. It exists so no file has to carry an inline
   `style="margin-top:…"`, which is how a stylesheet stops being the only
   place a page is styled. If a gap needs to be a different size, it is a
   component that needs a rule, not a second utility. */
.hf-gap-top { margin-top: 20px; }

/* Editorial two-column: heading block left, argument right. Below 940px it
   collapses to one column and the heading simply sits above its section. */
.hf-cols { display: grid; gap: clamp(18px, 3vw, 30px); }
@media (min-width: 940px) {
  .hf-cols { grid-template-columns: minmax(230px, 300px) 1fr; gap: 48px; align-items: start; }
}

.hf-stack > * + * { margin-top: 14px; }
.hf-stack-lg > * + * { margin-top: 24px; }

/* ── §3 typography ────────────────────────────────────────────────────────── */

/* THE EYEBROW, AND THE RULE THAT COMES WITH IT: max one per screen (§1.5), and
   never stacked above a sentence (§10). No page on this site uses it — every
   section label here is a quiet chip instead, which is what §1.5 prescribes
   for section separation ("position, weight, and chips — not stacked labels").
   It stays defined because it is part of §3's scale, and the next page that
   genuinely needs one must not invent its own. */
.hf-eyebrow {
  font-size: var(--hf-t-eyebrow);
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--hf-mut);
  margin: 0 0 12px;
}

/* 27 Sep 2026 craft pass: the page is read at arm's length and the plan asks
   the headline to carry the promise, so the two title steps grow with the
   viewport. Weights stay 700; nothing heavier ships. */
h1 { font-size: clamp(32px, 5vw, 52px); line-height: 1.06; letter-spacing: -.025em; }
h2 { font-size: clamp(var(--hf-t-display), 3.1vw, 36px); line-height: 1.14; letter-spacing: -.02em; max-width: 24ch; }
h3 { font-size: var(--hf-t-card); font-weight: 600; letter-spacing: 0; line-height: 1.3; }

/* One step above prose, for the sentence directly under a title. Not a §3
   style of its own — it is `prose` given room, and it is capped at 40ch so it
   stays a lead rather than becoming a paragraph. */
.hf-lead { font-size: clamp(17px, 1.9vw, 19px); line-height: 1.45; max-width: 40ch; }
.hf-prose { font-size: var(--hf-t-prose); line-height: 1.45; max-width: 64ch; }
.hf-secondary { font-size: var(--hf-t-secondary); line-height: 1.45; color: var(--hf-mut); max-width: 62ch; }
.hf-strong { font-weight: 600; }
/* §3's heroNumber has one call site on this site — the OVR on the Player Card,
   which reads --hf-t-hero directly — so there is no utility class for it. */

/* ── §4.1 chip ────────────────────────────────────────────────────────────────
   TONES ARE ROLES, NOT COLOURS, and the five names are HfChipTone's:
   quiet · action · earned · note · verified. §4.1 names its four variants
   after colours (quiet/turf/gold/sky); a call site that says "gold" cannot be
   read against the rule "gold, earned only", and a component that names a
   colour is what CLAUDE.md forbids. `earned` and `note` are both --hf-acc2 —
   one token, two jobs — and stay separate values because an achievement and a
   caveat about a figure are not the same claim.

   THE QUIET CHIP IS BOUNDED BY --hf-mark, NEVER --hf-line. Its fill is the
   surface it sits on, so the border is the entire component, and --hf-line is
   1.18:1. Tinted chips carry no border: the fill is a mood and the text does
   the identifying (14.62:1 and up).

   Max 3 chips per row (§4.1). Feelings are always quiet, never earned. */
.hf-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 28px;
  padding: 4px 11px;
  border: 1px solid transparent;
  border-radius: var(--hf-rchip);
  font-size: var(--hf-t-chip);
  font-weight: 600;
  line-height: 1.25;
  white-space: nowrap;
}
.hf-chip svg { width: 14px; height: 14px; flex: 0 0 auto; }

.hf-chip--quiet    { background: transparent;          color: var(--hf-mut);       border-color: var(--hf-mark); }
.hf-chip--action   { background: var(--hf-acc-tint);   color: var(--hf-acc-deep); }
.hf-chip--earned   { background: var(--hf-acc2-tint);  color: var(--hf-acc2); }
.hf-chip--note     { background: var(--hf-acc2-tint);  color: var(--hf-acc2); }
.hf-chip--verified { background: var(--hf-ok-tint);    color: var(--hf-ok); }
/* `earned` and `verified` are unused on this site AND THAT IS THE POINT: a
   marketing page has nothing a child earned and nothing a camera witnessed.
   They stay in the table because the tone vocabulary is closed — without them
   the next person needing one reaches for `note` or `action` and the rule
   ("gold, earned only" · "sky, verified only") is broken quietly. The button
   variants below are not closed in the same way, so this file carries only the
   three the three pages use; §4.3's `ghost` and destructive live in the app,
   where there is something to ghost and something to destroy. */

.hf-chiprow { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* ── §4.2 card ────────────────────────────────────────────────────────────────
   Fill, radius, hairline. No shadow — §2 allows exactly two on the whole
   product and neither is a card. */
.hf-card {
  background: var(--hf-surf);
  border: 1px solid var(--hf-line);
  border-radius: var(--hf-r);
  padding: clamp(16px, 2.4vw, 22px);
}
/* §4.2's selection state — 2px --hf-acc, "the ONE sanctioned emphasis; nothing
   else gets a coloured border" — is on .hf-choice, the only selectable card on
   this site, and the padding there compensates so nothing shifts by a pixel
   when it is picked. It is not duplicated here as a modifier nobody applies.

   List-style card: internal hairline dividers instead of nested cards. */
.hf-card--rows { padding: 2px clamp(14px, 2vw, 20px); list-style: none; }
.hf-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 13px 0;
  border-top: 1px solid var(--hf-line);
}
.hf-row:first-child { border-top: none; }
.hf-row__body { display: grid; flex: 1; min-width: 0; }
.hf-row__t { font-size: var(--hf-t-card); font-weight: 600; line-height: 1.35; }
.hf-row__s { font-size: var(--hf-t-secondary); color: var(--hf-mut); line-height: 1.45; margin-top: 3px; }
/* The leading glyph on a row. --hf-mut, never gold: bright gold is 2.14:1 on
   this ground and would be an invisible mark. */
.hf-row__i { flex: 0 0 auto; width: 20px; height: 20px; color: var(--hf-mut); margin-top: 1px; }

/* ── §4.3 button ──────────────────────────────────────────────────────────────
   HfButtonVariant, one for one: filled · tonal · plain · ghost. Radius is
   --hf-r, matching the app (see the shape block).

   ONE FILLED BUTTON PER SCREEN (§10). Everything else is tonal, plain or
   ghost. Turf owns actions; gold is never on a control (§2). */
.hf-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 48px;          /* §8's 44 floor with room for the label          */
  padding: 13px 22px;
  font-family: inherit;
  font-size: var(--hf-t-prose);
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  border-radius: var(--hf-r);
  border: 1px solid transparent;
  background: transparent;
  color: var(--hf-ink);
  /* §5 CALM: ≤250ms, standard easing, no overshoot. */
  transition: background-color 160ms ease-out, border-color 160ms ease-out;
}

.hf-btn--filled  { background: var(--hf-acc); color: var(--hf-acc-ink); }
.hf-btn--filled:hover  { background: var(--hf-acc-deep); }

/* A panel with a hairline: secondary actions. It is not a control boundary
   problem the way a ghost is, because it carries a fill of its own. */
.hf-btn--tonal   { background: var(--hf-surf); border-color: var(--hf-line); color: var(--hf-ink); }
.hf-btn--tonal:hover { border-color: var(--hf-mark); }
/* A store button before its listing exists (27 Sep 2026): a span, not a link, so it
   must not answer the pointer like one. Dashed edge + the words "coming soon" say
   it, never a colour alone. */
.hf-btn[aria-disabled="true"] { cursor: default; border-style: dashed; }
.hf-btn--tonal[aria-disabled="true"]:hover { border-color: var(--hf-line); }

/* No chrome. Tertiary, navigation, and "not now". */
.hf-btn--plain   { color: var(--hf-acc); padding-left: 10px; padding-right: 10px; }
.hf-btn--plain:hover { background: var(--hf-acc-tint); }

/* Disabled is DRAWN, not dimmed. `opacity` would drop the label below AA and
   make "unavailable" indistinguishable from "low quality print"; instead the
   fill goes flat, the border goes dashed, a hatch runs through it, and the
   label keeps 5.08:1. Whatever switched it off also has to say so in words
   nearby — see the note under the sign-up's last button. */
.hf-btn[disabled] {
  cursor: not-allowed;
  background-color: var(--hf-surf);
  color: var(--hf-mut);
  border: 1px dashed var(--hf-mark);
  background-image: var(--hf-hatch);
}

.hf-cta { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 16px; list-style: none; }

/* ── §4.7 trust row ───────────────────────────────────────────────────────────
   The progressive-disclosure primitive, and every privacy line on this site is
   one: a leading glyph, ONE line, and the full text one tap behind it. On the
   web "one tap behind" is a link to privacy.html, which is the long version
   the in-app notice refers to by name.

   A row with nothing behind it carries no chevron. That is legitimate for a
   product promise that is not a legal disclosure (HfTrustNotice makes the same
   distinction) — and a chevron that opens nothing is a lie about depth. */
.hf-trustrow {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 13px 16px;
  background: var(--hf-surf);
  border: 1px solid var(--hf-line);
  border-radius: var(--hf-r);
  color: var(--hf-mut);
  font-size: var(--hf-t-secondary);
  line-height: 1.45;
  text-decoration: none;
}
a.hf-trustrow:hover { border-color: var(--hf-mark); }
.hf-trustrow__i { flex: 0 0 auto; width: 20px; height: 20px; color: var(--hf-mut); }
.hf-trustrow__t { flex: 1; min-width: 0; }
.hf-trustrow__c { flex: 0 0 auto; width: 16px; height: 16px; color: var(--hf-mut); }

/* ── §4.11 station row ────────────────────────────────────────────────────────
   A numbered --hf-acc-tint disc, a cardTitle line, and a secondary line under
   it. The numeral is --hf-acc-deep on the tint at 8.07:1; the disc itself is
   1.10:1 on the ground and identifies nothing on its own, which is why the
   number is real text and not a painted mark. */
.hf-stations { list-style: none; display: grid; gap: 0; }
.hf-station {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 13px 0;
  border-top: 1px solid var(--hf-line);
}
.hf-station:first-child { border-top: none; }
.hf-station__n {
  flex: 0 0 auto;
  width: 30px; height: 30px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--hf-acc-tint);
  color: var(--hf-acc-deep);
  font-size: var(--hf-t-secondary);
  font-weight: 600;
}
.hf-station__b { flex: 1; min-width: 0; font-size: var(--hf-t-prose); line-height: 1.45; padding-top: 4px; }
.hf-station__b strong { font-weight: 600; }

/* ── bullet list ──────────────────────────────────────────────────────────────
   For prose lists that are NOT promise rows. The marker is a small turf
   square: shape, not a coloured dot doing semantic work. */
.hf-list { list-style: none; display: grid; gap: 12px; }
.hf-list > li {
  position: relative;
  padding-left: 24px;
  font-size: var(--hf-t-prose);
  line-height: 1.45;
  max-width: 62ch;
}
.hf-list > li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: .62em;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--hf-acc);
  transform: translateY(-50%);
}

/* ── header / footer ──────────────────────────────────────────────────────── */
.hf-header {
  border-bottom: 1px solid var(--hf-line);
  padding: 14px 0;
}
.hf-header__row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }

.hf-mark {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  text-decoration: none;
  color: var(--hf-ink);
  font-size: var(--hf-t-card);
  font-weight: 700;
  letter-spacing: -.02em;
}
.hf-mark small {
  font-size: var(--hf-t-secondary);
  font-weight: 400;
  letter-spacing: 0;
  color: var(--hf-mut);
}

.hf-footer {
  border-top: 1px solid var(--hf-line);
  padding: 26px 0 40px;
  font-size: var(--hf-t-secondary);
  color: var(--hf-mut);
}
.hf-footer__row { display: grid; gap: 14px; }
@media (min-width: 720px) {
  .hf-footer__row { grid-template-columns: auto minmax(280px, 420px); justify-content: space-between; align-items: center; }
}

/* ── hero ─────────────────────────────────────────────────────────────────── */
.hf-hero { padding: clamp(22px, 5vw, 56px) 0 var(--hf-sec); }
/* On a phone: headline, support, button, then the week (D72, plan §6 step 9),
   and the two drift lines after the week rather than before it. */
.hf-hero__grid {
  display: grid;
  gap: clamp(22px, 5vw, 44px);
  align-items: center;
  grid-template-areas: "text" "frame" "drift";
}
/* min-width: 0, or a grid item's min-content (a phone drawn in the frame)
   widens the whole page past a 320px screen. */
.hf-hero__grid > * { min-width: 0; }
/* The hero's eyebrow is a long chip; on a 320px screen it must wrap. */
.hf-hero__text .hf-chip { white-space: normal; }
.hf-hero__text { grid-area: text; }
.hf-hero__frame { grid-area: frame; }
.hf-hero__drift { grid-area: drift; align-self: start; }
@media (min-width: 900px) {
  .hf-hero__grid {
    grid-template-columns: 1.05fr .95fr;
    grid-template-areas: "text frame" "drift frame";
  }
}

/* The hero frame. It used to hold a labelled placeholder drawing; since D72
   (27 Sep 2026) it holds the product: a phone showing this week, rest locked,
   and a smaller "today" card beside it - HTML and CSS, generated from the
   catalog, no image. The camera ground and the four gold corners stay: they
   read as a viewfinder, and §6.5 makes dark camera chrome canon. Gold here is
   --hf-gold on --hf-cam (7.53:1), which is the only ground it is legal on. */
.hf-frame {
  position: relative;
  margin: 0;
  padding: 46px clamp(14px, 3vw, 28px) 22px;
  border-radius: var(--hf-r);
  border: 1px solid var(--hf-line);
  background: var(--hf-cam);
  overflow: hidden;
}
.hf-frame__brk { position: absolute; width: 30px; height: 30px; border: 2px solid var(--hf-gold); }
.hf-frame__brk.tl { top: 14px; left: 14px; border-right: 0; border-bottom: 0; }
.hf-frame__brk.tr { top: 14px; right: 14px; border-left: 0; border-bottom: 0; }
.hf-frame__brk.bl { bottom: 14px; left: 14px; border-right: 0; border-top: 0; }
.hf-frame__brk.br { bottom: 14px; right: 14px; border-left: 0; border-top: 0; }
.hf-frame__stage { display: flex; align-items: flex-end; justify-content: center; gap: 12px; }
/* --hf-card-ink on --hf-cam: 16.09:1. */
.hf-frame__cap { margin-top: 16px; text-align: center; font-size: var(--hf-t-secondary); color: var(--hf-card-ink); }

/* ── pricing ──────────────────────────────────────────────────────────────── */
.hf-plans { display: grid; gap: 16px; }
@media (min-width: 820px) { .hf-plans { grid-template-columns: repeat(3, 1fr); } }
.hf-plan { display: grid; gap: 8px; align-content: start; }
.hf-plan dt {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: var(--hf-t-card);
  font-weight: 600;
}
.hf-plan dd { font-size: var(--hf-t-secondary); line-height: 1.5; color: var(--hf-mut); }
.hf-plans__note { font-size: var(--hf-t-prose); font-weight: 600; line-height: 1.5; max-width: 64ch; }

/* ── final CTA band ───────────────────────────────────────────────────────── */
/* The close is the hero's viewfinder ground again: ink, with the one green
   button on it, centred. --hf-card-ink on --hf-cam is 16.09:1; the quiet link
   keeps the same ink so the green stays the only "do this". No gold here —
   the plan spends gold twice (corners, rest locks) and this is neither. */
.hf-final {
  padding: calc(var(--hf-sec) * 1.15) 0;
  background: var(--hf-cam);
  color: var(--hf-card-ink);
  text-align: center;
}
.hf-final h2 { margin-inline: auto; }
.hf-final .hf-prose { margin-inline: auto; color: var(--hf-card-ink); }
.hf-final .hf-cta { justify-content: center; gap: 12px 22px; }
.hf-final .hf-quiet { color: var(--hf-card-ink); }
.hf-final .hf-btn--filled { padding: 15px 26px; }

/* ── forms ────────────────────────────────────────────────────────────────── */
fieldset { margin: 0; padding: 0; border: 0; min-width: 0; }
legend { padding: 0; }

/* Field labels are cardTitle, NOT the eyebrow. Six uppercase tracked labels on
   one form is six eyebrows on a screen allowed one (§1.5), and it makes a
   plain question look like a section heading. */
.hf-field { display: grid; gap: 6px; margin-top: 20px; }
.hf-fieldset { margin-top: 22px; }
.hf-field > label,
.hf-fieldset > legend {
  font-size: var(--hf-t-card);
  font-weight: 600;
  color: var(--hf-ink);
}
.hf-hint { font-size: var(--hf-t-secondary); color: var(--hf-mut); line-height: 1.45; }

.hf-input {
  width: 100%;
  max-width: 26rem;
  min-height: 48px;
  padding: 12px 14px;
  font-family: inherit;
  font-size: var(--hf-t-prose);
  font-weight: 400;
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--hf-ink);
  background: var(--hf-surf);
  /* --hf-mark, not --hf-line: an input is a control, and its border is the
     only thing that says where it is. */
  border: 1px solid var(--hf-mark);
  border-radius: var(--hf-r);
}
select.hf-input { max-width: 14rem; }
.hf-input::placeholder { color: var(--hf-mut); opacity: 1; }
.hf-input:focus-visible { border-color: var(--hf-acc); }

/* Invalid is never colour alone: the border thickens, a dashed underline
   appears, and a worded error message is announced below the field. */
.hf-input[aria-invalid="true"] {
  border: 2px solid var(--hf-danger);
  border-bottom-style: dashed;
}
.hf-error {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  font-size: var(--hf-t-secondary);
  font-weight: 600;
  color: var(--hf-danger);        /* 6.04:1 on a card */
  max-width: 54ch;
}
.hf-error:empty { display: none; }
.hf-error::before { content: "\26A0"; font-size: 15px; line-height: 1.45; }

/* choice list — radios and checkboxes as §4.2 cards */
.hf-choices { list-style: none; display: grid; gap: 8px; margin-top: 10px; }
.hf-choice {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  background: var(--hf-surf);
  border: 1px solid var(--hf-mark);
  border-radius: var(--hf-r);
  cursor: pointer;
  font-size: var(--hf-t-prose);
  line-height: 1.4;
}
.hf-choice input { margin: 1px 0 0; width: 20px; height: 20px; accent-color: var(--hf-acc); flex: 0 0 auto; }
.hf-choice__body { display: grid; gap: 4px; justify-items: start; }
.hf-choice__t { font-size: var(--hf-t-card); font-weight: 600; }
.hf-choice__s { font-size: var(--hf-t-secondary); color: var(--hf-mut); }

/* §4.2's selection state, and the word. 2px --hf-acc is the one sanctioned
   coloured border in the system; the chip beside it is what a person who
   cannot see the border reads. */
.hf-choice:has(input:checked) { border: 2px solid var(--hf-acc); padding: 13px 15px; }
.hf-choice__on { display: none; }
.hf-choice:has(input:checked) .hf-choice__on { display: inline-flex; }

/* Unavailable: dashed edge + hatch + the words. Three signals, one of them is
   not colour. */
.hf-choice--soon {
  cursor: not-allowed;
  border-style: dashed;
  background-image: var(--hf-hatch);
  color: var(--hf-mut);
}
/* The chip keeps a solid ground so the row's hatch does not run under its
   label. --hf-mut on the hatched surface is 5.08:1 — legal, but a word sitting
   on diagonal stripes is not a word anyone reads at a glance. */
.hf-choice--soon .hf-chip { background: var(--hf-surf); }

/* ── the stepper ──────────────────────────────────────────────────────────────
   Five §4.1 chips, quiet in the markup. The state is a WORD — "Done", "Now",
   or the step number, written into .hf-stepper__st by signup.js — because a
   chip colour is not allowed to carry it. With JavaScript off no li ever gets
   a data-state, all five stay quiet, and all five steps are on the page. */
.hf-stepper { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; margin: 22px 0 0; }
.hf-stepper__st { font-weight: 700; }
.hf-stepper li[data-state="now"] {
  background: var(--hf-acc-tint);
  border-color: transparent;
  color: var(--hf-acc-deep);
}

.hf-stepnav { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 24px; }

/* One step at a time, and only once JS has said so. Without JavaScript the
   .js class is never added and all five steps render as one long, perfectly
   usable form. Step 1 carries .is-current in the markup, so there is no
   flash of all five before the script runs. */
.hf-step { scroll-margin-top: 20px; }
/* Only ever seen with JavaScript off, when all five steps are on the page at
   once and need to be told apart. */
.hf-step + .hf-step { margin-top: 18px; }
.js .hf-step { display: none; }
.js .hf-step.is-current { display: block; }

/* The clipboard fallback in signup.js. It lives here rather than in a cssText
   string so the stylesheet stays the only place this site is styled. */
.hf-code {
  margin-top: 16px;
  padding: 14px;
  overflow: auto;
  white-space: pre-wrap;
  background: var(--hf-bg);
  border: 1px solid var(--hf-line);
  border-radius: var(--hf-r);
  font-size: var(--hf-t-secondary);
  line-height: 1.5;
}

/* ── the Player Card ──────────────────────────────────────────────────────────
   The growth loop's one artifact, and on this page it is the thing a parent
   looks at while deciding. HfTokens.turf().card: a deep turf pitch falling
   into ink, chalk ink on it, a 2px gold edge — gold is legal as a mark HERE
   and nowhere else on the site, because this ground is dark (4.16:1 on the top
   stop, 7.53:1 at the bottom).

   §2 permits no gradients "except the badge medallion". This is the third
   exception the token file already makes, not a fourth one invented here: the
   card is a rendered artifact that also ships as a PNG out of
   services/media-engine, and its field comes from the token block on both
   surfaces so the in-app card and the shared card cannot drift. */
.hf-playercard {
  position: relative;
  overflow: hidden;
  max-width: 340px;
  padding: 20px 18px 18px;
  border-radius: var(--hf-rcard);
  border: 2px solid var(--hf-gold);
  color: var(--hf-card-ink);
  background: var(--hf-card-grad);
  box-shadow: var(--hf-card-shadow);
}

/* ── the card tilts toward the pointer ────────────────────────────────────────
   The Flutter widget already paints a raking highlight (hf_player_card.dart's
   `shine`); here the pointer moves it, which is the same idea rather than a new
   one bolted on.

   §5 CALM: ≤250ms, standard easing, no overshoot. A card that sprang back past
   its rest angle would be a different product's card, so the return is a short
   ease-out and stops.

   The tilt is deliberately small (±7deg). This is a card a parent is reading
   numbers off, not a toy — enough to say "this is an object", not enough to
   make the OVR hard to read.

   `--hf-rx`/`--hf-ry` are the tilt, `--hf-mx`/`--hf-my` the pointer position for
   the sheen. All four are set from JS and all four have sane defaults, so the
   card renders correctly flat with JS disabled. */
.hf-playercard-stage {
  /* Perspective lives on the parent so the card's own transform stays a pure
     rotation — nesting perspective inside the transformed element flattens it. */
  perspective: 900px;
  max-width: 340px;
}
.hf-playercard {
  --hf-rx: 0deg;
  --hf-ry: 0deg;
  --hf-mx: 50%;
  --hf-my: 30%;
  --hf-lift: 1;
  transform: rotateX(var(--hf-rx)) rotateY(var(--hf-ry)) scale(var(--hf-lift));
  transform-style: preserve-3d;
  /* Only while resting. The script clears it during tracking so the card follows
     the finger 1:1 instead of lagging behind it by a transition. */
  transition: transform 180ms cubic-bezier(.2, .8, .3, 1);
}
.hf-playercard.is-tilting { transition: none; }

/* The raking highlight. A separate layer so it can move independently of the
   card's own gradient, and `mix-blend-mode: soft-light` so it lightens the
   field instead of painting grey over it. */
.hf-playercard__sheen {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  mix-blend-mode: soft-light;
  background: var(--hf-sheen);
  transition: opacity 180ms cubic-bezier(.2, .8, .3, 1);
}
.hf-playercard.is-tilting .hf-playercard__sheen { opacity: 1; }

/* Keyboard users get the object-ness without a pointer: focusing the card lifts
   it. Focus is also how a screen-reader user lands here, and a visible change on
   focus is a WCAG 2.4.7 obligation, not decoration. */
.hf-playercard:focus-visible {
  outline: 3px solid var(--hf-gold);
  outline-offset: 4px;
  --hf-lift: 1.02;
}
.hf-playercard__lvl { font-size: var(--hf-t-secondary); font-weight: 600; letter-spacing: .12em; text-transform: uppercase; }
.hf-playercard__ovr { display: flex; align-items: baseline; gap: 8px; margin: 6px 0 0; }
.hf-playercard__ovr b { font-size: var(--hf-t-hero); font-weight: 700; letter-spacing: -.02em; line-height: 1; }
.hf-playercard__ovr span { font-size: var(--hf-t-secondary); font-weight: 600; letter-spacing: .12em; text-transform: uppercase; }
.hf-playercard__nm { display: inline-block; margin-top: 14px; font-size: var(--hf-t-kid); font-weight: 700; letter-spacing: -.02em; }
.hf-playercard__sub { margin-top: 4px; font-size: var(--hf-t-secondary); }

/* ── review / summary rows on the last step ───────────────────────────────── */
.hf-review { display: grid; gap: 0; }
.hf-review div {
  display: grid;
  grid-template-columns: minmax(130px, 32%) 1fr;
  gap: 4px 16px;
  padding: 11px 0;
  border-top: 1px solid var(--hf-line);
}
.hf-review div:first-child { border-top: none; }
.hf-review dt { font-size: var(--hf-t-secondary); color: var(--hf-mut); }
.hf-review dd { margin: 0; font-size: var(--hf-t-prose); font-weight: 600; }
@media (max-width: 480px) { .hf-review div { grid-template-columns: 1fr; } }

/* ── web → app handoff (signup step 5) ─────────────────────────────────────
   Two equal store actions, no filled primary — a desktop parent is picking
   a store for a phone they are not holding. The QR inherits --hf-ink via
   currentColor; there is no hex in the SVG. */
.hf-store {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 16px;
}
.hf-store .hf-btn { width: 100%; }
@media (max-width: 480px) { .hf-store { grid-template-columns: 1fr; } }

.hf-qr { display: grid; gap: 12px; color: var(--hf-ink); }
.hf-qr .hf-field { margin-top: 0; }
.hf-qr__mark:empty { display: none; }
.hf-qr__mark svg {
  display: block;
  width: 180px;
  height: 180px;
  background: var(--hf-surf);
  border: 1px solid var(--hf-line);
  border-radius: var(--hf-r);
}

.hf-quote {
  margin: 0;
  font-size: clamp(19px, 2.2vw, 22px);
  font-weight: 600;
  line-height: 1.4;
  max-width: 46ch;
  letter-spacing: -.01em;
}

/* ── motion ───────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }

  /* The card stops moving entirely. Killing the transition alone would make it
     SNAP to each tilt angle, which is worse for someone with a vestibular
     disorder than the smooth version — so the rotation itself is pinned flat and
     the script is told to stand down. The sheen stays put at its rest position;
     it is a highlight on a field, not motion. */
  .hf-playercard {
    --hf-rx: 0deg !important;
    --hf-ry: 0deg !important;
    --hf-lift: 1 !important;
  }
  .hf-playercard__sheen { opacity: 0 !important; }
}

/* ── forced colours (Windows high contrast) ───────────────────────────────── */
@media (forced-colors: active) {
  .hf-card, .hf-btn, .hf-choice, .hf-chip, .hf-trustrow, .hf-input { border: 1px solid; }
  .hf-frame, .hf-phone, .hf-today, .hf-day, .hf-sportcard, .hf-strip > li, .hf-group__s, .hf-group__a { border: 1px solid; }
  .hf-qr__mark svg { border: 1px solid; }
}

/* ── help page (web/help.html, generated by tools/gen_help.py) ────────────────
   Topics are plain links; each answer is a native <details>, so it opens and
   closes with no script and is keyboard-operable by the browser itself. The
   open/closed state is the disclosure triangle AND the answer being there -
   never a colour. */
.hf-search .hf-field { margin-top: 0; }
.hf-topics { list-style: none; display: flex; flex-wrap: wrap; gap: 8px 18px; }
.hf-topics a { font-weight: 600; }
.hf-faq { display: grid; gap: 0; margin-top: 22px; }
.hf-faq__cat {
  font-size: var(--hf-t-secondary);
  font-weight: 600;
  color: var(--hf-mut);
  margin-bottom: 4px;
}
.hf-faq__item { border-top: 1px solid var(--hf-line); }
.hf-faq__item:last-child { border-bottom: 1px solid var(--hf-line); }
.hf-faq__q {
  cursor: pointer;
  padding: 13px 0;
  font-size: var(--hf-t-card);
  font-weight: 600;
  line-height: 1.35;
}
.hf-faq__a { padding: 0 0 16px; font-size: var(--hf-t-prose); line-height: 1.5; max-width: 64ch; }
.hf-faq__item:target .hf-faq__q { text-decoration: underline; }

/* ── support inbox (web/admin/support.html, staff only) ───────────────────────
   Three panes on a wide screen, stacked on a narrow one (.hf-console below). Every state here is
   carried by a word as well as a fill: the status chip says Waiting / Open /
   Closed, unread is a dot AND "N unread", the selected conversation gets the
   2px turf edge §4.2 sanctions plus aria-current, a pressed filter gets a
   heavier weight and an underline, and each message is labelled Parent or
   Support. */
.hf-admin { padding: 22px 0 60px; }
.hf-admin__bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; }
.hf-admin__h { font-size: var(--hf-t-display); }
.hf-admin__grid { display: grid; gap: 18px; margin-top: 18px; align-items: start; }
@media (min-width: 900px) {
  .hf-admin__grid { grid-template-columns: minmax(280px, 380px) 1fr; }
}
.hf-admin__list { display: grid; gap: 12px; min-width: 0; }
.hf-admin__filters .hf-btn[aria-pressed="true"] {
  background: var(--hf-acc-tint);
  color: var(--hf-acc-deep);
  font-weight: 700;
  text-decoration: underline;
}
.hf-admin__thread { min-width: 0; }
.hf-admin__head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; gap: 12px; }
.hf-admin__head h2 { font-size: var(--hf-t-card); }

.hf-inbox { list-style: none; display: grid; gap: 8px; }
.hf-inbox__row {
  display: grid;
  gap: 6px;
  width: 100%;
  text-align: left;
  font: inherit;
  color: var(--hf-ink);
  background: var(--hf-surf);
  border: 1px solid var(--hf-line);
  border-radius: var(--hf-r);
  padding: 12px 14px;
  cursor: pointer;
}
.hf-inbox__row:hover { border-color: var(--hf-mark); }
.hf-inbox__row[aria-current="true"] { border: 2px solid var(--hf-acc); padding: 11px 13px; }
.hf-inbox__top { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; }
.hf-inbox__name { font-weight: 600; font-size: var(--hf-t-card); overflow-wrap: anywhere; }
.hf-inbox__time { flex: 0 0 auto; font-size: var(--hf-t-secondary); color: var(--hf-mut); }
.hf-inbox__line { font-size: var(--hf-t-secondary); color: var(--hf-mut); overflow-wrap: anywhere; }
.hf-inbox__preview {
  color: var(--hf-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hf-inbox__unread { gap: 6px; }
.hf-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}

.hf-msgs {
  list-style: none;
  display: grid;
  gap: 10px;
  max-height: 60vh;
  overflow-y: auto;
  padding: 4px 2px;
}
.hf-msg {
  max-width: 85%;
  padding: 10px 12px;
  border-radius: var(--hf-r);
  border: 1px solid var(--hf-line);
}
.hf-msg--parent { justify-self: start; background: var(--hf-bg); }
.hf-msg--agent { justify-self: end; background: var(--hf-acc-tint); border-color: var(--hf-acc-tint); }
.hf-msg__meta { font-size: var(--hf-t-secondary); color: var(--hf-mut); font-weight: 600; }
.hf-msg--agent .hf-msg__meta { color: var(--hf-acc-deep); }
.hf-msg__text { margin-top: 4px; white-space: pre-wrap; overflow-wrap: anywhere; }
.hf-reply { max-width: none; min-height: 110px; resize: vertical; line-height: 1.45; }

@media (forced-colors: active) {
  .hf-inbox__row, .hf-msg { border: 1px solid; }
  .hf-inbox__row[aria-current="true"] { border-width: 3px; }
}

/* ── the specialist console (web/admin/*, staff only · D68) ───────────────────
   The inbox is three panes — list | conversation | family — from 900px up,
   and one column below it, in that order. Same rule as the inbox above: every
   state is a word as well as a fill. A session divider says "Session", a date
   and a length; a cleared session says "Cleared by customer"; a read receipt
   says "Seen"; typing says "Parent is typing" beside the dots; a helpful ratio
   is a percentage, a thumb and the word "helpful". No colour below this line
   is anything but a token. */
.hf-wrap--wide { max-width: 1480px; }

.hf-adminnav { list-style: none; display: flex; flex-wrap: wrap; gap: 4px 6px; }
.hf-adminnav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 8px 12px;
  border-radius: var(--hf-r);
  font-weight: 600;
  text-decoration: none;
  color: var(--hf-acc);
}
.hf-adminnav a:hover { background: var(--hf-acc-tint); }
/* The page you are on: ink, heavier, underlined — not a hue change alone. */
.hf-adminnav a[aria-current="page"] {
  color: var(--hf-ink);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 5px;
}

.hf-console { display: grid; gap: 18px; margin-top: 18px; align-items: start; }
@media (min-width: 900px) {
  .hf-console {
    grid-template-columns: minmax(250px, 320px) minmax(0, 1fr) minmax(230px, 300px);
  }
}
.hf-console__pane { min-width: 0; }
.hf-console__pane h2 { font-size: var(--hf-t-card); }

/* Dividers inside the message list. A session divider carries its label in
   ink between two hairlines; a day divider is the same shape in --hf-mut. */
.hf-divider {
  justify-self: stretch;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 10px;
  margin: 6px 0 2px;
  font-size: var(--hf-t-secondary);
  font-weight: 600;
  color: var(--hf-mut);
}
.hf-divider::before, .hf-divider::after {
  content: "";
  flex: 1 1 24px;
  border-top: 1px solid var(--hf-line);
}
.hf-divider--session { color: var(--hf-ink); margin-top: 14px; }
.hf-divider--session::before, .hf-divider--session::after { border-top-color: var(--hf-mark); }
.hf-divider .hf-btn { min-height: 36px; padding: 6px 10px; font-size: var(--hf-t-secondary); }

/* "Seen" sits under the staff bubble it belongs to, on the staff side. */
.hf-seen {
  justify-self: end;
  margin-top: -6px;
  font-size: var(--hf-t-secondary);
  font-weight: 600;
  color: var(--hf-mut);
}

.hf-typing { display: inline-flex; align-items: center; gap: 8px; }
.hf-typing__dots { display: inline-flex; gap: 4px; }
.hf-typing__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  animation: hf-typing-pulse 1.2s ease-in-out infinite;
}
.hf-typing__dot:nth-child(2) { animation-delay: .2s; }
.hf-typing__dot:nth-child(3) { animation-delay: .4s; }
@keyframes hf-typing-pulse {
  0%, 80%, 100% { opacity: .25; }
  40% { opacity: 1; }
}

/* The composer's small controls: macro inserts and the drawer toggle. */
.hf-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin-top: 12px; }
.hf-macro {
  min-height: 36px;
  padding: 4px 10px;
  font: inherit;
  font-size: var(--hf-t-chip);
  font-weight: 600;
  color: var(--hf-acc-deep);
  background: var(--hf-surf);
  border: 1px solid var(--hf-mark);
  border-radius: var(--hf-rchip);
  cursor: pointer;
}
.hf-macro:hover { background: var(--hf-acc-tint); }
.hf-preview {
  padding: 10px 12px;
  border: 1px dashed var(--hf-mark);
  border-radius: var(--hf-r);
  background: var(--hf-bg);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Quick replies: a drawer under the composer, rows like the inbox's. */
.hf-quick {
  margin-top: 14px;
  padding: 14px;
  border: 1px solid var(--hf-mark);
  border-radius: var(--hf-r);
  background: var(--hf-bg);
}
.hf-quick .hf-field { margin-top: 0; }
.hf-quick .hf-input { max-width: none; }
.hf-quick__list { list-style: none; display: grid; gap: 8px; margin-top: 12px; max-height: 40vh; overflow-y: auto; }
.hf-quick__item {
  display: grid;
  gap: 4px;
  width: 100%;
  text-align: left;
  font: inherit;
  color: var(--hf-ink);
  background: var(--hf-surf);
  border: 1px solid var(--hf-line);
  border-radius: var(--hf-r);
  padding: 10px 12px;
  cursor: pointer;
}
.hf-quick__item:hover { border-color: var(--hf-mark); }
.hf-quick__text {
  font-size: var(--hf-t-secondary);
  color: var(--hf-mut);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The family pane: a definition list of facts, then one row per child. */
.hf-facts { display: grid; gap: 10px; margin-top: 12px; }
.hf-facts dt { font-size: var(--hf-t-secondary); color: var(--hf-mut); font-weight: 600; }
.hf-facts dd { margin: 2px 0 0; overflow-wrap: anywhere; }
.hf-kids { list-style: none; display: grid; gap: 8px; margin-top: 10px; }
.hf-kid {
  display: grid;
  gap: 2px;
  padding: 10px 12px;
  border: 1px solid var(--hf-line);
  border-radius: var(--hf-r);
  background: var(--hf-bg);
  font-size: var(--hf-t-secondary);
}
.hf-kid__h { font-weight: 600; font-size: var(--hf-t-prose); }

/* The help-bot page. */
.hf-table-wrap { overflow-x: auto; margin-top: 12px; }
.hf-table { width: 100%; border-collapse: collapse; font-size: var(--hf-t-secondary); }
.hf-table th, .hf-table td {
  text-align: left;
  vertical-align: top;
  padding: 10px 8px;
  border-bottom: 1px solid var(--hf-line);
}
.hf-table th { color: var(--hf-mut); font-weight: 600; }
.hf-table .hf-num { text-align: right; white-space: nowrap; }
.hf-days { display: flex; flex-wrap: wrap; gap: 8px 14px; margin-top: 10px; }
.hf-check { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; font-weight: 600; }
.hf-check input { width: 22px; height: 22px; accent-color: var(--hf-acc); }
.hf-row .hf-btn { flex: 0 0 auto; min-height: 40px; padding: 8px 12px; font-size: var(--hf-t-secondary); }

@media (forced-colors: active) {
  .hf-quick__item, .hf-kid, .hf-macro { border: 1px solid; }
  .hf-typing__dot { background: CanvasText; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE WEEK, AND WHAT IS BUILT AROUND IT (D72, 27 Sep 2026)

   The website leads with the week. One component - .hf-weekc - is used in the
   hero phone, on the homepage, on every sport page and in sign-up, and its
   markup is GENERATED by tools/gen_web_sports.py from config/program/. The
   switcher (hf-site.js) only re-points what the generator already wrote.

   COLOUR HAS EXACTLY TWO JOBS HERE, and the plan names both:
     · green (--hf-acc family) is the primary action and "rest protected";
     · gold is the viewfinder corners and the rest-day lock. On chalk and on a
       card bright --hf-gold is 2.14:1 and may never be a mark, so the lock is
       --hf-acc2 (goldDeep, 5.71 / 5.92) - gold's own legal step.
   Everything else is ink, --hf-mut and the two hairlines. Body links in these
   components are ink and underlined, so green keeps meaning "do this".

   A DAY'S TYPE IS NEVER A COLOUR. Five words in four treatments - Skill;
   Conditioning and Light recovery (one treatment, two words); Game; Rest - and
   each carries a glyph. Rest carries three signals: the lock, the word, and a
   dashed edge on a recessed ground, plus the word "Protected".
   ═══════════════════════════════════════════════════════════════════════════ */

/* The glyph sprite: an inline <svg> of <symbol>s the days <use>. Zero-size
   rather than display:none, which some engines treat as "do not render the
   symbols either". */
.hf-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* ── the header: one primary button, links in a menu on a phone ──────────── */
.hf-nav { display: flex; align-items: center; gap: 6px; }
.hf-nav__links { display: none; list-style: none; margin: 0; padding: 0; gap: 2px; }
.hf-nav__start { min-height: 44px; padding: 10px 16px; }
.hf-nav__full { display: none; }
.hf-menu { position: relative; }
.hf-menu__s { min-height: 44px; padding: 10px 14px; list-style: none; }
.hf-menu__s::-webkit-details-marker { display: none; }
/* Open is said by the word changing shape, not a colour: a chevron turns. */
.hf-menu__s::after {
  content: "";
  width: 7px; height: 7px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
}
.hf-menu[open] .hf-menu__s::after { transform: translateY(2px) rotate(-135deg); }
.hf-menu__list {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: 30;
  display: grid;
  min-width: 230px;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: var(--hf-surf);
  border: 1px solid var(--hf-mark);
  border-radius: var(--hf-r);
}
.hf-menu__list .hf-btn { width: 100%; justify-content: flex-start; color: var(--hf-ink); }
@media (min-width: 940px) {
  .hf-nav__links { display: flex; }
  .hf-nav__links .hf-btn { color: var(--hf-ink); }
  .hf-menu { display: none; }
  .hf-nav__full { display: inline; }
  .hf-nav__short { display: none; }
}

/* A first section that opens a page sits closer to the header. */
.hf-section--lead { padding-top: clamp(24px, 4vw, 44px); }

/* A quiet link: ink, underlined, a full touch target. The Player Card's door
   on the close, deliberately not a button (plan §5). */
.hf-quiet {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--hf-ink);
  font-weight: 600;
  text-underline-offset: 3px;
}
a.hf-chip--link { text-decoration: none; color: var(--hf-ink); }
a.hf-chip--link:hover { border-color: var(--hf-ink); }

/* ── phones and the today card ─────────────────────────────────────────── */
/* A phone is a phone: a deeper radius, a bezel-weight edge and a speaker
   slit, so a parent recognises a device rather than a card. Still a fill, a
   radius and a border — nothing floats (§2). */
.hf-phone {
  position: relative;
  flex: 0 1 244px;
  min-width: 0;
  padding: 22px 10px 14px;
  background: var(--hf-surf);
  color: var(--hf-ink);
  border: 2px solid var(--hf-mark);
  border-radius: calc(var(--hf-r) * 1.9);
}
.hf-phone::before {
  content: "";
  position: absolute;
  top: 9px;
  left: 50%;
  width: 44px;
  height: 5px;
  transform: translateX(-50%);
  border-radius: 5px;
  background: var(--hf-line);
}
.hf-phone--mini { flex-basis: 156px; padding-top: 18px; }
.hf-phone--mini::before { width: 30px; top: 7px; }
.hf-phone__bar {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  padding: 0 6px 8px;
  font-size: var(--hf-t-eyebrow);
  font-weight: 600;
  color: var(--hf-mut);
}

.hf-today { display: grid; gap: 6px; padding: 4px 6px; }
.hf-today__h {
  font-size: var(--hf-t-eyebrow);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--hf-mut);
}
.hf-today__k { font-size: var(--hf-t-secondary); font-weight: 600; }
.hf-today__timer { font-size: var(--hf-t-hero); font-weight: 700; letter-spacing: -.02em; line-height: 1.05; }
.hf-today--lg .hf-today__timer { font-size: calc(var(--hf-t-hero) * 1.35); }
.hf-today__list { list-style: none; margin: 0; padding: 0; display: grid; font-size: var(--hf-t-secondary); }
.hf-today__list li { padding: 6px 0; border-top: 1px solid var(--hf-line); line-height: 1.3; }

/* ── the week component ─────────────────────────────────────────────────── */
.hf-weekc { display: grid; gap: 12px; min-width: 0; }
.hf-weekc__name { font-size: var(--hf-t-card); font-weight: 600; }
.hf-weekc__shape { font-size: var(--hf-t-secondary); color: var(--hf-mut); }
.hf-weekc__lock { display: flex; gap: 8px; align-items: flex-start; font-weight: 600; max-width: 64ch; }
.hf-weekc__lockg { flex: 0 0 auto; width: 18px; height: 18px; margin-top: 2px; color: var(--hf-acc2); }
.hf-weekc--list { gap: 6px; }
.hf-weekc--list .hf-weekc__name { font-size: var(--hf-t-secondary); padding: 0 6px; }
.hf-weekc--list .hf-weekc__shape { padding: 0 6px; font-size: var(--hf-t-eyebrow); }

.hf-days { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.hf-day {
  display: grid;
  grid-template-columns: 2.6em 18px minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px 8px;
  padding: 9px 11px;
  background: var(--hf-surf);
  border: 1px solid var(--hf-line);
  border-radius: var(--hf-rchip);
}
.hf-day__d {
  font-size: var(--hf-t-eyebrow);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--hf-mut);
}
.hf-day__g { width: 18px; height: 18px; color: var(--hf-mut); }
.hf-day__k { font-size: var(--hf-t-secondary); font-weight: 600; line-height: 1.25; }
.hf-day__s { font-size: var(--hf-t-eyebrow); font-weight: 600; color: var(--hf-mut); white-space: nowrap; }
.hf-day__s:empty { display: none; }

/* The four treatments. Body days (conditioning, light recovery) carry an
   inset rule down their leading edge; game days a control-weight border;
   rest is recessed, dashed, locked and labelled. */
.hf-day--skill { border-style: solid; }   /* the plain card: the default day */
.hf-day--body { box-shadow: inset 3px 0 0 var(--hf-mark); }
.hf-day--game { border-color: var(--hf-mark); }
.hf-day--rest { background: var(--hf-bg); border: 1px dashed var(--hf-mark); }
.hf-day--rest .hf-day__g { color: var(--hf-acc2); }
.hf-day--rest .hf-day__s {
  padding: 2px 8px;
  border-radius: var(--hf-rchip);
  background: var(--hf-acc-tint);
  color: var(--hf-acc-deep);          /* 8.07:1 - "rest protected" is green's second job */
}
/* On the parent phone: the rest day's own control, visible, not a link. */
.hf-day__act {
  grid-column: 3 / -1;
  justify-self: start;
  padding: 2px 8px;
  font-size: var(--hf-t-eyebrow);
  font-weight: 600;
  color: var(--hf-ink);
  border: 1px solid var(--hf-mark);
  border-radius: var(--hf-rchip);
}

/* On a narrow screen a day's sub-label (Protected, Game speed) takes its own
   line under the word instead of pushing the row wider than the phone. */
@media (max-width: 520px) {
  .hf-day { grid-template-columns: 2.4em 16px minmax(0, 1fr); padding: 8px 9px; }
  .hf-day__s { grid-column: 3; justify-self: start; }
  .hf-frame__stage { gap: 8px; flex-wrap: wrap; }
  .hf-frame__stage .hf-phone { flex: 1 1 190px; max-width: 236px; }
  .hf-frame__stage .hf-phone--mini { flex: 1 1 108px; max-width: 150px; }
}

/* Seven across on a desktop; the list form everywhere a phone is drawn. */
@media (min-width: 760px) {
  .hf-weekc:not(.hf-weekc--list) .hf-days { grid-template-columns: repeat(7, minmax(0, 1fr)); }
  .hf-weekc:not(.hf-weekc--list) .hf-day {
    grid-template-columns: 1fr;
    align-content: start;
    justify-items: start;
    gap: 6px;
    min-height: 124px;
    padding: 12px 10px;
  }
}

/* ── the sport switcher: six shelves, then that shelf's sports ─────────── */
.hf-groups { display: flex; flex-wrap: wrap; gap: 8px; }
.hf-group[open] { flex-basis: 100%; }
.hf-group__s {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 8px 14px;
  list-style: none;
  cursor: pointer;
  font-weight: 600;
  background: var(--hf-surf);
  border: 1px solid var(--hf-mark);
  border-radius: var(--hf-r);
}
.hf-group__s::-webkit-details-marker { display: none; }
.hf-group__s::after { content: "+"; font-weight: 700; color: var(--hf-mut); }
.hf-group[open] .hf-group__s { border: 2px solid var(--hf-ink); padding: 7px 13px; }
.hf-group[open] .hf-group__s::after { content: "\2212"; }
.hf-group__n { font-size: var(--hf-t-chip); color: var(--hf-mut); }
.hf-group__list { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 10px 0 2px; }
.hf-group__a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 6px 12px;
  color: var(--hf-ink);
  font-weight: 600;
  text-decoration: none;
  background: var(--hf-surf);
  border: 1px solid var(--hf-mark);
  border-radius: var(--hf-rchip);
}
.hf-group__a:hover { border-color: var(--hf-ink); }
/* The sport on show: a heavier edge, bold and underlined - never a hue. */
.hf-group__a[aria-current="true"] { border: 2px solid var(--hf-ink); padding: 5px 11px; text-decoration: underline; }

.hf-agectl { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.hf-agectl label { font-weight: 600; }
.hf-agectl .hf-input { width: auto; min-width: 9rem; }

/* ── the homepage's session beats, two people, and the trust strip ─────── */
.hf-frames { list-style: none; padding: 0; display: grid; gap: 12px; }
@media (min-width: 720px) { .hf-frames { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .hf-frames { grid-template-columns: repeat(4, 1fr); } }
.hf-frames > li { display: grid; gap: 10px; align-content: start; }

.hf-people { display: grid; gap: 22px; align-items: start; }
@media (min-width: 900px) { .hf-people { grid-template-columns: 1fr 1fr .9fr; } }
.hf-person { display: grid; gap: 14px; align-content: start; }
.hf-people > *, .hf-frames > *, .hf-sportcards > *, .hf-strip > * { min-width: 0; }
.hf-person .hf-phone { max-width: 280px; }

.hf-strip { list-style: none; padding: 0; display: grid; gap: 10px; }
@media (min-width: 620px) { .hf-strip { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .hf-strip { grid-template-columns: repeat(5, 1fr); } }
.hf-strip > li {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 14px;
  font-weight: 600;
  line-height: 1.35;
  background: var(--hf-surf);
  border: 1px solid var(--hf-line);
  border-radius: var(--hf-r);
}
.hf-strip svg { flex: 0 0 auto; width: 20px; height: 20px; color: var(--hf-mut); }

/* ── the sports hub's cards ─────────────────────────────────────────────── */
.hf-sportcards { list-style: none; padding: 0; display: grid; gap: 12px; }
@media (min-width: 620px) { .hf-sportcards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 960px) { .hf-sportcards { grid-template-columns: repeat(3, 1fr); } }
.hf-sportcard {
  display: grid;
  gap: 8px;
  align-content: start;
  padding: 16px;
  background: var(--hf-surf);
  border: 1px solid var(--hf-line);
  border-radius: var(--hf-r);
  scroll-margin-top: 20px;
}
.hf-sportcard__t { font-size: var(--hf-t-card); }
.hf-sportcard__t a { color: var(--hf-ink); text-decoration: none; }
.hf-sportcard__t a:hover { text-decoration: underline; }
.hf-sportcard__shape { font-size: var(--hf-t-secondary); font-weight: 600; }
.hf-sportcard__gear { font-size: var(--hf-t-secondary); color: var(--hf-mut); }
.hf-sportcard__go { justify-self: start; min-height: 44px; display: inline-flex; align-items: center; color: var(--hf-ink); font-weight: 600; text-underline-offset: 3px; }

/* A seven-cell glance at the week on each card, beside the words that say it. */
.hf-mini { display: flex; gap: 4px; }
.hf-mini__c {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  color: var(--hf-mut);
  background: var(--hf-surf);
  border: 1px solid var(--hf-line);
  border-radius: calc(var(--hf-rchip) * .6);
}
.hf-mini__c svg { width: 14px; height: 14px; }
.hf-mini__c--skill { border-style: solid; }
.hf-mini__c--body { box-shadow: inset 2px 0 0 var(--hf-mark); }
.hf-mini__c--game { border-color: var(--hf-mark); }
.hf-mini__c--rest { background: var(--hf-bg); border: 1px dashed var(--hf-mark); color: var(--hf-acc2); }

/* Sign-up's sport picker: thirty short rows, so a compact grid. */
.hf-choices--grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.hf-choice--sport { padding: 10px 12px; align-items: center; }
.hf-choice--sport:has(input:checked) { padding: 9px 11px; }

/* ── a phone's sticky start bar, after the first scroll ─────────────────── */
.hf-sticky { display: none; }
@media (max-width: 719px) {
  .hf-sticky.is-on {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 25;
    padding: 10px 0 calc(10px + env(safe-area-inset-bottom));
    background: var(--hf-surf);
    border-top: 1px solid var(--hf-line);
  }
  .hf-sticky .hf-btn { width: 100%; }
  .has-sticky .hf-footer { padding-bottom: 110px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE CRAFT PASS (27 Sep 2026, after D72 landed)

   D72 put the right things on the page in the right order. This block is
   about rhythm: the sections alternate ground, the week sits on a board
   instead of in a column of grey notes, the four beats read as an editorial
   row rather than four more cards, the phones look like phones, and the trust
   strip is a strip. Nothing here adds a colour, a shadow or a weight the
   token block does not already allow; the plan's §8 rules are the brief.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Sections alternate chalk and card so a long page has a beat. */
.hf-section--surf { background: var(--hf-surf); }
.hf-sechead { display: grid; gap: 12px; max-width: 60ch; }
.hf-sechead .hf-lead { max-width: 52ch; }

/* ── hero: the drift line is an aside, not an orphan ─────────────────────── */
.hf-hero { padding-top: clamp(26px, 5.5vw, 64px); }
.hf-hero__grid { gap: clamp(26px, 5vw, 56px); }
.hf-hero__text > .hf-lead { max-width: 44ch; }
.hf-hero__drift {
  padding-left: 16px;
  border-left: 3px solid var(--hf-mark);
  max-width: 52ch;
}
.hf-frame { padding: 52px clamp(16px, 3vw, 30px) 24px; border-radius: calc(var(--hf-r) * 1.4); }
.hf-frame__stage { align-items: flex-end; gap: 14px; }
.hf-frame__cap { margin-top: 18px; }

/* ── the week board ──────────────────────────────────────────────────────── */
.hf-board {
  display: grid;
  gap: clamp(18px, 2.4vw, 26px);
  padding: clamp(16px, 2.8vw, 30px);
  background: var(--hf-bg);
  border: 1px solid var(--hf-line);
  border-radius: calc(var(--hf-r) * 1.4);
}
.hf-board__pick { display: grid; gap: 12px; }
.hf-board__label { font-size: var(--hf-t-secondary); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--hf-mut); }
.hf-board .hf-weekc { gap: 14px; }
.hf-board .hf-weekc__name { font-size: clamp(var(--hf-t-card), 2vw, 22px); font-weight: 700; letter-spacing: -.01em; }
.hf-board .hf-weekc__shape { font-weight: 600; color: var(--hf-ink); }
/* "Rest protected" is green's second job (plan §8): the lock line is a tinted
   bar, --hf-acc-deep on --hf-acc-tint at 8.07:1, and the lock keeps gold. */
.hf-board .hf-weekc__lock {
  align-items: center;
  max-width: none;
  padding: 12px 16px;
  border-radius: var(--hf-r);
  background: var(--hf-acc-tint);
  color: var(--hf-acc-deep);
}
.hf-board .hf-weekc__lockg { margin-top: 0; width: 20px; height: 20px; }
.hf-board__foot { display: grid; gap: 10px; padding-top: 4px; }
.hf-board__foot .hf-secondary { max-width: none; }
.hf-notes { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.hf-notes > li { padding: 12px 0; border-top: 1px solid var(--hf-line); font-size: var(--hf-t-secondary); line-height: 1.45; color: var(--hf-mut); }
@media (min-width: 760px) {
  .hf-board__foot { grid-template-columns: auto 1fr; align-items: center; gap: 18px; }
  .hf-notes { grid-template-columns: repeat(3, 1fr); gap: 0 28px; }
  .hf-notes > li { padding: 14px 0 0; }
}

/* Desktop day cells: taller, a bigger glyph, a rounder corner — the seven
   cells are the product, so they are the largest thing on the board. */
@media (min-width: 760px) {
  .hf-weekc:not(.hf-weekc--list) .hf-days { gap: 8px; }
  .hf-weekc:not(.hf-weekc--list) .hf-day {
    gap: 8px;
    min-height: 140px;
    padding: 14px 12px;
    border-radius: var(--hf-r);
  }
  .hf-weekc:not(.hf-weekc--list) .hf-day__d { font-size: 13px; }
  .hf-weekc:not(.hf-weekc--list) .hf-day__g { width: 24px; height: 24px; margin-top: 2px; }
  .hf-weekc:not(.hf-weekc--list) .hf-day__k { font-size: var(--hf-t-prose); margin-top: auto; }
  .hf-weekc:not(.hf-weekc--list) .hf-day--rest .hf-day__g { width: 26px; height: 26px; }
}
.hf-group__s { border-radius: var(--hf-rchip); }

/* Inside a drawn phone the row is narrow at every viewport, so a day's
   sub-label (Protected, Game speed) always takes its own line under the word
   instead of crowding it — the same rule the ≤520px screen already had. */
.hf-weekc--list .hf-day { grid-template-columns: 2.4em 16px minmax(0, 1fr); padding: 8px 9px; }
.hf-weekc--list .hf-day__s { grid-column: 3; justify-self: start; }
.hf-weekc--list .hf-day__act { margin-top: 2px; }

/* ── the four beats: an editorial row, hairlines between, big numerals ──── */
.hf-frames { gap: 0; }
.hf-frames > li.hf-card {
  padding: 18px 0 4px;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--hf-ink);
  border-radius: 0;
  gap: 12px;
}
.hf-frames .hf-station__n {
  font-size: var(--hf-t-hero);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--hf-mut);
}
.hf-frames .hf-prose { font-size: var(--hf-t-prose); }
.hf-frames .hf-strong { display: block; font-size: var(--hf-t-card); margin-bottom: 4px; }
@media (min-width: 720px) {
  .hf-frames { gap: 0 28px; }
}

/* ── two people: phones at a phone's size, the split as three columns ───── */
.hf-people { gap: 28px; }
.hf-person > h3 { font-size: var(--hf-t-secondary); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--hf-mut); }
.hf-person .hf-phone { max-width: 300px; padding-bottom: 16px; }
.hf-person .hf-prose { font-size: var(--hf-t-secondary); line-height: 1.5; }
.hf-person .hf-prose .hf-strong { font-size: var(--hf-t-prose); }
@media (min-width: 900px) {
  .hf-people { grid-template-columns: 1fr 1fr 1fr; gap: 36px; }
  .hf-person:last-child { padding-top: 34px; }
}

/* ── the trust strip: one band, five items, hairlines, no cards ──────────── */
.hf-section--strip { background: var(--hf-surf); }
.hf-section--strip h2 { text-align: center; margin-inline: auto; }
.hf-section--strip .hf-stack-lg > p { text-align: center; }
.hf-strip { gap: 0; border-top: 1px solid var(--hf-line); border-bottom: 1px solid var(--hf-line); }
.hf-strip > li {
  padding: 16px 4px;
  background: transparent;
  border: 0;
  border-radius: 0;
  border-top: 1px solid var(--hf-line);
  font-size: var(--hf-t-prose);
}
.hf-strip > li:first-child { border-top: 0; }
.hf-strip svg { width: 22px; height: 22px; color: var(--hf-acc2); }
@media (min-width: 620px) {
  .hf-strip { grid-template-columns: repeat(2, 1fr); }
  .hf-strip > li:nth-child(2) { border-top: 0; }
}
@media (min-width: 1000px) {
  .hf-strip { grid-template-columns: repeat(5, 1fr); border-top: 0; border-bottom: 0; }
  .hf-strip > li {
    display: grid;
    justify-items: center;
    gap: 12px;
    padding: 22px 16px;
    text-align: center;
    border-top: 0;
    border-left: 1px solid var(--hf-line);
    font-size: var(--hf-t-secondary);
  }
  .hf-strip > li:first-child { border-left: 0; }
  .hf-strip svg { width: 26px; height: 26px; }
}

/* ── footer: a two-column grid on a desktop ─────────────────────────────── */
.hf-footer { padding: 34px 0 44px; }
.hf-footer .hf-list { margin-top: 6px; }
@media (min-width: 860px) {
  .hf-footer .hf-stack { display: grid; grid-template-columns: 1.3fr 1fr; gap: 8px 48px; }
  .hf-footer .hf-stack > * + * { margin-top: 0; }
  .hf-footer .hf-mark { grid-column: 1 / -1; }
  .hf-footer .hf-list { grid-column: 2; grid-row: 2 / 4; margin-top: 0; }
}

/* ── the sports hub: six shelves read as one catalog, not six chapters ──── */
.hf-section[id^="cat-"] { padding: clamp(26px, 3.6vw, 40px) 0; border-top: 0; }
.hf-section[id^="cat-"] + .hf-section:not([id^="cat-"]) { margin-top: clamp(18px, 3vw, 36px); }
.hf-section[id^="cat-"] h2 { font-size: clamp(var(--hf-t-card), 2.2vw, 24px); }
.hf-sportcard { border-radius: var(--hf-r); transition: border-color 160ms ease-out; }
.hf-sportcard:hover { border-color: var(--hf-mark); }
.hf-sportcard__t { font-size: clamp(var(--hf-t-card), 1.6vw, 19px); }
.hf-mini__c { width: 28px; height: 28px; }

/* ── a sport page's week: the same board as the homepage, from CSS alone ──
   The generator writes the week straight into the section (no wrapper), so
   the board look is put on the component itself where it stands there. The
   name line duplicates the h2 above it ("Badminton's week" / "This week")
   and is hidden; the list's aria-label still names the sport. */
.hf-section > .hf-wrap > .hf-weekc {
  padding: clamp(16px, 2.8vw, 30px);
  background: var(--hf-bg);
  border: 1px solid var(--hf-line);
  border-radius: calc(var(--hf-r) * 1.4);
  gap: 14px;
}
.hf-section--surf > .hf-wrap > .hf-weekc { background: var(--hf-bg); }
.hf-section > .hf-wrap > .hf-weekc > .hf-weekc__name { display: none; }
.hf-section > .hf-wrap > .hf-weekc > .hf-weekc__shape { font-weight: 600; color: var(--hf-ink); }
.hf-section > .hf-wrap > .hf-weekc > .hf-weekc__lock {
  align-items: center;
  max-width: none;
  padding: 12px 16px;
  border-radius: var(--hf-r);
  background: var(--hf-acc-tint);
  color: var(--hf-acc-deep);
}
.hf-section > .hf-wrap > .hf-weekc > .hf-weekc__lock .hf-weekc__lockg { margin-top: 0; width: 20px; height: 20px; }

/* ── sign-up step 1: find a sport, and the plan's cap in words ──────────── */
.hf-finder { display: grid; gap: 10px; margin-top: 22px; }
.hf-finder .hf-input { max-width: 100%; }
.hf-finder__cats { display: flex; flex-wrap: wrap; gap: 6px; }
/* A chip that is a button: the pressed one is said by weight and edge, never
   a hue — 2px ink and bold, the same grammar as the homepage switcher. */
.hf-finder__cat { cursor: pointer; font-family: inherit; min-height: 36px; color: var(--hf-ink); background: var(--hf-surf); }
.hf-finder__cat:hover { border-color: var(--hf-ink); }
.hf-finder__cat[aria-pressed="true"] { border: 2px solid var(--hf-ink); padding: 3px 10px; font-weight: 700; }
.hf-finder__count:empty, #sport-cap:empty { display: none; }
#sport-cap { margin-top: 12px; font-weight: 600; color: var(--hf-ink); }
.hf-fieldset[hidden], .hf-choices > li[hidden] { display: none; }
.hf-signup-weeks { display: grid; gap: 18px; }
.hf-signup-weeks .hf-weekc + .hf-weekc { padding-top: 18px; border-top: 1px solid var(--hf-line); }
