/* ==========================================================================
   Store Blindern Poker - global styles
   --------------------------------------------------------------------------
   Warm earth tones on near-black. One accent: brass. One radius: 4px.
   Newsreader (display) + Instrument Sans (body) + JetBrains Mono (all numerals).
   Phone-first, dark-room-first: 48px tap targets, high contrast, no glow.

   Binding spec: docs/DESIGN_SYSTEM.md. Do not add tokens that are not in it.

   --------------------------------------------------------------------------
   NOTES FOR MARKUP AGENTS - read this before editing any HTML
   --------------------------------------------------------------------------
   Every class the pages and js/*.js currently use still exists and still works.
   Nothing was renamed. The following are NEW, added for the layouts the audit
   asked for. Use them instead of `.card-grid`.

     .editorial              Two-column editorial block. Lead statement left,
                             detail list right. Stacks below 900px.
     .editorial__lead        The lead statement. Display face, larger, brass rule.
     .editorial__body        Right column. Put a `.deflist` in here.

     .deflist                Sharp ruled definition list (<dl>). 1px rules
                             between pairs, no boxes, no hover. dt is the term,
                             dd the body. Wrap numerals in <span class="num">.
     .deflist--lead          Bigger dt, for a standalone list not inside .editorial.

     .steps / .step          RESTYLED, not new. No longer cards. A numbered list
                             with a mono counter and a 1px rule between steps.
                             Safe to use on index.html for "How a night works"
                             and on rules.html. `.step__title` or an <h3> works.

     .stats / .stat          RESTYLED, not new. No longer boxes. A sharp bordered
                             row: rules top and bottom, 1px dividers between
                             cells, mono numerals. `.stats--4` for four cells.
                             index.html needs no markup change for this.

     .podium                 RESTYLED, not new. No longer three raised cards.
                             Three sharp bordered rows. js/app.js builds this
                             markup and was not touched.

     .card-grid              KEPT so admin.html's two side-by-side forms still
                             work. With three or more children it collapses to a
                             ruled list automatically (`:has()`), because a
                             three-card equal grid is forbidden. Prefer
                             .editorial or .deflist for prose; keep .card-grid
                             only where each child is a real object (a form).

     .num / .mono            Numerals. `.num` is mono + tabular + right aligned
                             (already used by js/app.js and js/admin.js).
                             `.mono` is mono + tabular with no alignment change.
                             Most components apply mono themselves, so existing
                             markup gets it without changes.

     .skeleton               Loading placeholder block. Modifiers: --rank, --name,
     .skeleton-table         --num, --line, --title. Wrap rows in
     .skeleton-table__row    `.skeleton-table` > `.skeleton-table__row`. Always
                             mark the wrapper aria-hidden="true".

     .is-busy                On a button: shows a spinner, keeps the width, stops
                             input. `[aria-busy="true"]` does the same, so JS can
                             use the ARIA attribute instead of a class.
     .is-disabled            Deliberate disabled look. `[disabled]` and
                             `[aria-disabled="true"]` match too.

     .lede                   One-off larger intro paragraph, display face.
     .rule                   A plain 1px horizontal rule, no gradient.

   RSVP (section 23). js/rsvp.js clones the whole block from the
   `#rsvp-template` in events.html and appends it inside
   `.event-card__info`, so no page hand-writes these except that template.

     .rsvp                   The block. Sits under an event card's meta,
                             separated by a 1px rule, never its own card.
     .rsvp__head             Count row. Wraps at 360px.
     .rsvp__count            "14 going". Always rendered, to everybody.
     .rsvp__count-num        The numeral. Mono, tabular.
     .rsvp__count--empty     Nobody has answered yet: dimmer, no numeral.
     .rsvp__aside            The declined count. Never a list of names.
     .rsvp__aside-num        That numeral. Mono, tabular.

     .rsvp__control          The two-state control. One bordered box split by
                             a 1px rule, stacked below 420px, side by side
                             above. Deliberately not a pill and not a form.
     .rsvp__choice           One half of it. 48px minimum, `aria-pressed`
                             carries the state. `--in` is Going (felt),
                             `--out` is Can't make it (clay). Busy is
                             `aria-busy="true"` (spinner, keeps its width),
                             off is `aria-disabled="true"`. Do not put the
                             global `.is-disabled` on it: that class repaints
                             the whole control. Switching the control off
                             greys the half you did NOT pick; your own answer
                             keeps its colour, because it is a record and not
                             a dead button. Press feedback is an inset line,
                             never a translate: the halves share one box.
     .rsvp__tick             Selection mark. Space is reserved when hidden so
                             the label never jumps.
     .rsvp__choice-label     The word.

     .rsvp__signin           Sign in / claim a pseudonym link. Full tap height.
     .rsvp__hint             One quiet line under the control.
     .rsvp__msg              Status line. `--error` for a rolled-back save.
     .rsvp__list             Pseudonyms going. Signed-in members only.
     .rsvp__who              One pseudonym. `--me` marks your own.
     .rsvp__you              The "you" tag inside `.rsvp__who--me`.

     .countdown__going       Headcount line inside the home page countdown.
     .countdown__going-num   Its numeral. Mono, tabular. The line keeps its
                             height while empty, so filling it shifts nothing.

   BANK SLIPS AND RE-BUY (section 24). Used only by report.html + js/report.js.

     .slip                   Full-screen takeover, same pattern as the night
                             code: the phone is shown to the organiser at the
                             chip bank. Toggle with [hidden]. Contains, top to
                             bottom: band, kicker, give-line, amount, unit,
                             note, age, clock, night, close.
     .slip--reshow           A re-rendered slip for a buy-in or re-buy already
                             issued. The kicker goes clay, which is the only
                             alarm colour on a slip, so the bank can tell a
                             fresh slip from an old one.
     .slip--take             The closing slip: chips coming back rather than
                             going out. It repaints the amount cream.
     .slip__band             Direction, pinned to the top edge and read before
                             the number: GIVE CHIPS OUT (brass) or TAKE CHIPS
                             BACK (felt). The one brass fill on a give slip;
                             a take slip has none. className is rebuilt by JS.
     .slip__kicker           What the slip is for ("BUY-IN", "RE-BUY"). Mono,
                             uppercase, brass; clay under --reshow.
     .slip__give             "GIVE <pseudonym>". Sans 600, big.
     .slip__amount           THE number. Giant mono, brass, tabular.
     .slip__unit             "chips" under the number. Mono, uppercase.
     .slip__note             One quiet sanity line ("Stack becomes 10,000").
                             Wrap numerals in <span class="mono">.
     .slip__age              How old the slip is, in words ("checked in 4
                             minutes ago"). Always cream-dim, every kind, every
                             age. Its element is #slip-stamp: the id is
                             historical and is deliberately not renamed.
     .slip__clock            Live clock, ticking seconds. Mono. Freshness proof.
     .slip__night            Night title. Mono, small caps style.
     .slip__close            The one obvious close. Full-width-ish button at
                             the bottom, 56px.

     .rebuy-quote            "You can take up to 7,000." Display face, the
                             numeral in <span class="mono"> goes brass.
     .rebuy-done             Quiet one-liner recording a bank re-buy, with an
                             inline .link-gold re-show button.
     .report-deadline        "You can report until 09:00 tomorrow." Centred
                             help line under the report form.
     .report-deadline--closed  Same line after the deadline: clay.

   Legacy token names (--gold, --text-primary, --space-*, --radius-lg, ...) are
   aliased to the new palette at the bottom of the token block, so the inline
   styles still in the HTML keep rendering correctly. Prefer the new names.

   Removed on purpose: mahogany and all its tokens, every glow, the animated
   gradient hero text, the film grain overlay, decorative backdrop blur, pill
   radii, and the three competing radii.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Self-hosted type. No runtime CDN, per CLAUDE.md.

   These blocks are what fonts.googleapis.com served for the three families,
   with the gstatic URLs swapped for our own. Descriptors are unchanged, so
   matching and rendering are identical to what the @import produced.

   The files are variable fonts. Instrument Sans and JetBrains Mono carry a
   wght axis, Newsreader carries opsz 6..72 plus wght, which is why one file
   serves several weights. Optical sizing stays on the browser default, so
   Newsreader still picks its optical size from the font size.

   Regenerating: refetch the css2 URL in the commit message, download the
   woff2 files it points at, and rewrite the src lines.
   -------------------------------------------------------------------------- */

/* Instrument Sans */
/* latin-ext */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url('../assets/fonts/instrument-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url('../assets/fonts/instrument-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url('../assets/fonts/instrument-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 500;
  font-stretch: 100%;
  font-display: swap;
  src: url('../assets/fonts/instrument-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url('../assets/fonts/instrument-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url('../assets/fonts/instrument-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* JetBrains Mono */
/* cyrillic-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../assets/fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Newsreader */
/* vietnamese */
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/newsreader-italic-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/newsreader-italic-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/newsreader-italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/newsreader-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/newsreader-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/newsreader-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../assets/fonts/newsreader-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../assets/fonts/newsreader-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../assets/fonts/newsreader-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ==========================================================================
   1. TOKENS
   ========================================================================== */

:root {
  /* ---- Palette. Locked by docs/DESIGN_SYSTEM.md. Do not drift. ---------- */
  --bg: #0c0c0d;          /* near-black, warm */
  --surface: #131315;     /* raised */
  --surface-2: #1a1a1d;   /* input wells, table stripes */
  --border: #2a2a2f;      /* crisp, visible. never rgba white */
  --border-loud: #3f3f47; /* hover and focus intensification */
  --cream: #fbfaf8;       /* primary text */
  --cream-dim: #a5a5ac;   /* secondary text */
  --cream-mute: #74747c;  /* tertiary, captions, labels */
  --brass: #c9a84c;       /* THE single accent, used sparingly */
  --brass-lift: #dcbe6b;  /* hover only */
  --felt: #1d4030;        /* semantic only, positive */
  --clay: #b4503f;        /* semantic only, negative and danger */

  /* ---- Derived. Not new hues: legible tints of the locked three. --------
     Felt and clay are surface-weight greens and reds. They cannot carry
     small text on near-black, so each gets one lift for type and one
     solid-hex fill and rule for its state block. All solid hex, no alpha,
     no glassmorphism. Brass reuses --brass-lift for type. -------------- */
  --felt-lift: #4f9d74;   /* felt as readable type, 5.9:1 on --bg */
  --clay-lift: #d9705c;   /* clay as readable type, 6.0:1 on --bg */
  --felt-fill: #13211b;   /* felt mixed into --bg */
  --felt-line: #274636;
  --clay-fill: #251614;
  --clay-line: #4f2721;
  --brass-fill: #241f13;
  --brass-line: #584a26;
  --on-brass: #14100a;    /* type on a brass fill */

  /* ---- Type ------------------------------------------------------------ */
  --font-display: 'Newsreader', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --font-sans: 'Instrument Sans', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  /* ---- Spacing. Unchanged: the markup depends on these. ---------------- */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px;
  --space-3xl: 64px;
  --space-4xl: 96px;

  /* ---- Radius. One value. No exceptions. ------------------------------- */
  --radius: 4px;

  /* ---- Controls -------------------------------------------------------- */
  --tap-target: 48px;     /* minimum height for anything a thumb hits */
  --nav-height: 64px;

  /* ---- Motion ---------------------------------------------------------- */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 0.18s;
  --dur-enter: 0.42s;     /* derived: first-paint and scroll entrances only */

  /* ---- Compatibility aliases -------------------------------------------
     The HTML still carries inline `var(--gold)`, `var(--text-primary)` and
     friends, and js/app.js reads `var(--text-tertiary)`. These map the old
     names onto the new palette so nothing renders wrong mid-migration.
     Delete an alias only after grepping *.html and js/ for it. ---------- */
  --bg-primary: var(--bg);
  --bg-secondary: var(--surface);
  --bg-card: var(--surface);
  --bg-card-hover: var(--surface-2);
  --bg-elevated: var(--surface-2);
  --gold: var(--brass);
  --gold-bright: var(--brass-lift);
  --gold-dim: var(--brass);
  --text-primary: var(--cream);
  --text-secondary: var(--cream-dim);
  --text-tertiary: var(--cream-mute);
  --text-on-gold: var(--on-brass);
  --border-hover: var(--border-loud);
  --border-accent: var(--brass-line);
  --ok: var(--felt-lift);
  --danger: var(--clay-lift);
  --warn: var(--brass);   /* the spec has no third semantic hue. Brass is the
                             attention state: felt positive, brass pending,
                             clay negative. */
  --font-serif: var(--font-display);
  --radius-sm: var(--radius);
  --radius-md: var(--radius);
  --radius-lg: var(--radius);
  --radius-xl: var(--radius);
}

/* ==========================================================================
   2. RESET AND BASE
   ========================================================================== */

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

html {
  font-size: 16px;
  scroll-behavior: smooth;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background: var(--bg);
}

body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--cream);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.55;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }

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

button {
  cursor: pointer;
  border: none;
  background: none;
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

ul, ol { list-style: none; }

code, kbd, samp {
  font-family: var(--font-mono);
  font-size: 0.9em;
  font-variant-numeric: tabular-nums;
  color: var(--cream);
}

::selection {
  background: var(--brass);
  color: var(--on-brass);
}

/* One global focus ring: 2px brass at 2px offset. Never a glow. */
:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
  border-radius: var(--radius);
}

.skip-link {
  position: absolute;
  top: -60px;
  left: var(--space-md);
  z-index: 2000;
  min-height: var(--tap-target);
  display: inline-flex;
  align-items: center;
  padding: 0 18px;
  background: var(--brass);
  color: var(--on-brass);
  border-radius: 0 0 var(--radius) var(--radius);
  font-weight: 600;
  transition: top var(--dur) var(--ease);
}

.skip-link:focus { top: 0; }

/* The film grain was decoration, not signal. The element stays in the markup
   for now so no page has to change; it renders nothing. */
.noise-overlay { display: none; }

/* ==========================================================================
   3. UTILITIES
   ========================================================================== */

/* Numerals. Points, chips, ranks, dates, codes: all mono, all tabular, so
   columns hold their position as the leaderboard reorders. */
.mono,
.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  letter-spacing: -0.01em;
}

.num { text-align: right; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

[hidden] { display: none !important; }

.rule {
  height: 1px;
  background: var(--border);
  border: 0;
  margin: var(--space-2xl) 0;
}

/* .divider is the old name for the same thing. It used to be a gradient. */
.divider {
  height: 1px;
  background: var(--border);
  margin: var(--space-2xl) 0;
}

.lede {
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 3vw, 1.6rem);
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: -0.01em;
  color: var(--cream);
}

.link-gold {
  color: var(--brass);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--brass-line);
  transition: color var(--dur) var(--ease), text-decoration-color var(--dur) var(--ease);
}

.link-gold:hover {
  color: var(--brass-lift);
  text-decoration-color: var(--brass-lift);
}

/* ==========================================================================
   4. LAYOUT
   ========================================================================== */

.container {
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 var(--space-md);
}

.container--narrow { max-width: 760px; }

@media (min-width: 640px) {
  .container { padding: 0 var(--space-lg); }
}

.page-wrapper {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  position: relative;
  z-index: 2;
}

.page-content { flex: 1; }

.section { padding: var(--space-3xl) 0; }

@media (min-width: 768px) {
  .section { padding: var(--space-4xl) 0; }
}

.section__header {
  margin-bottom: var(--space-2xl);
  max-width: 720px;
}

.section__label {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--brass);
  margin-bottom: var(--space-sm);
}

.section__title {
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 3.5vw, 2.5rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.15;
  color: var(--cream);
}

.section__description {
  color: var(--cream-dim);
  font-size: 1rem;
  line-height: 1.55;
  margin-top: var(--space-md);
  max-width: 60ch;
}

/* ==========================================================================
   5. NAVIGATION (shared chrome, byte-identical markup on every page)
   ========================================================================== */

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  height: var(--nav-height);
}

.nav__brand {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  white-space: nowrap;
  color: var(--cream);
  transition: color var(--dur) var(--ease);
}

.nav__brand:hover { color: var(--brass-lift); }

.nav__brand-icon {
  color: var(--brass);
  font-size: 1.2rem;
  line-height: 1;
}

.nav__links {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  /* Report is a sibling of this panel rather than a child of it, so the
     panel is what gets pushed right and the CTA follows it. Same bar as
     before at desktop widths. */
  margin-left: auto;
}

.nav__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  padding: 0 var(--space-xs);
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--cream-dim);
  position: relative;
  transition: color var(--dur) var(--ease);
}

.nav__link:hover,
.nav__link--active { color: var(--cream); }

.nav__link--active::after {
  content: '';
  position: absolute;
  bottom: 10px;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--brass);
}

/* "Tonight" call to action in the nav: the permanent route to report.html,
   which nothing on the public site linked to before. Brass OUTLINED, never
   filled, so it does not spend a screen's one brass fill. */
.nav__cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* Was 40px, which is the one control in the shared chrome that broke the
     48px floor. The bar is 64px tall, so there is room. */
  min-height: var(--tap-target);
  padding: 0 var(--space-md);
  border: 1px solid var(--brass-line);
  border-radius: var(--radius);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--brass);
  white-space: nowrap;
  /* .nav__link is position:relative and this was static, so on report.html,
     the one page whose current item IS the CTA, .nav__link--active::after had
     no positioned ancestor and resolved against nav.nav (position:fixed).
     left:0 right:0 then painted the 2px brass rule across the entire bar:
     measured 1197px at 1280 wide instead of the 83px link. */
  position: relative;
  transition: background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}

.nav__cta:hover {
  background-color: var(--brass-fill);
  border-color: var(--brass);
  color: var(--brass-lift);
}

/* The current-page marker on the CTA. Even scoped correctly the underline is
   wrong here: the CTA is a bordered pill, not a bare word, and left:0 right:0
   would run the rule wall to wall across its own border. The pill takes the
   marker on its frame instead, so report.html still says which page you are
   on, like the five other pages that carry a marker. Still an OUTLINE: the
   ground is --brass-fill #241f13, a dark brass-tinted ground, not a brass
   fill, so the screen's one brass fill stays with the primary action. Same
   frame the hover state uses, deliberately: it is the one "this pill is
   lit" treatment the design already has, a hover is transient, and a phone
   has none at all. Two classes, so this beats .nav__cta's own colour, which
   sits later in the file than .nav__link--active and was quietly winning it:
   without this the page would carry aria-current with nothing to see. */
.nav__cta.nav__link--active::after { content: none; }

.nav__cta.nav__link--active {
  background-color: var(--brass-fill);
  border-color: var(--brass);
  color: var(--brass-lift);
}

/* Mobile menu */
.nav__toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: var(--tap-target);
  height: var(--tap-target);
  margin-right: calc(var(--space-xs) * -1);
}

.nav__toggle span {
  display: block;
  width: 20px;
  height: 2px;
  background: var(--cream);
  transition: background-color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}

@media (max-width: 720px) {
  .nav__toggle { display: flex; }

  .nav__links {
    position: fixed;
    top: var(--nav-height);
    left: 0;
    right: 0;
    background: var(--bg);
    flex-direction: column;
    align-items: stretch;
    padding: var(--space-sm) var(--space-md) var(--space-lg);
    gap: 0;
    border-bottom: 1px solid var(--border);
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: transform var(--dur) var(--ease),
                opacity var(--dur) var(--ease),
                visibility 0s linear var(--dur);
  }

  .nav__links--open {
    transform: translateY(0);
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: transform var(--dur) var(--ease),
                opacity var(--dur) var(--ease),
                visibility 0s;
  }

  .nav__link {
    min-height: var(--tap-target);
    font-size: 1.05rem;
    padding: 0 var(--space-sm);
    border-bottom: 1px solid var(--border);
  }

  .nav__link--active::after { display: none; }
  .nav__link--active { color: var(--brass); }

  /* Report lives in the BAR at this width, not in the panel, so it is on
     screen without opening anything. That is the whole point of the move:
     the club is phone first, and a route hidden behind a hamburger is a
     route people ask an organiser for instead. The panel is position:fixed
     and out of flow, so the bar lays out as brand, Report, hamburger with
     nothing else competing for the width. Measured at 375px: brand 176,
     Report 78, hamburger 48, leaving 41px of slack.
     margin-left:auto is what pins it beside the hamburger rather than
     letting space-between strand it in the middle of the bar. */
  .nav__cta {
    margin-left: auto;
    padding: 0 var(--space-sm);
    font-size: 0.8rem;
  }
}

/* Scroll target for a step. .nav is fixed, 65px tall (64 plus its border)
   and z-index 1000, and nothing else in this file sets scroll-margin, so a
   plain scrollIntoView drops a step's first line behind it: measured, the h1
   landed at viewport top -27, fully hidden. 88px puts a card's section label
   23px clear of the nav. Applied by class, never by attribute selector, so
   it can never reach the slip takeover. */
.step-anchor { scroll-margin-top: calc(var(--nav-height) + var(--space-lg)); }

/* ==========================================================================
   6. BUTTONS
   ========================================================================== */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  min-height: var(--tap-target);
  padding: 12px 22px;
  border: 1px solid transparent;   /* keeps every variant the same height */
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.005em;
  text-align: center;
  transition: background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
  -webkit-tap-highlight-color: transparent;
}

@media (hover: hover) {
  .btn:hover { transform: translateY(-1px); }
}

.btn:active { transform: translateY(0); }

.btn--primary {
  background-color: var(--brass);
  border-color: var(--brass);
  color: var(--on-brass);
}

.btn--primary:hover {
  background-color: var(--brass-lift);
  border-color: var(--brass-lift);
}

.btn--secondary {
  background-color: var(--surface);
  border-color: var(--border);
  color: var(--cream);
}

.btn--secondary:hover {
  background-color: var(--surface-2);
  border-color: var(--border-loud);
}

.btn--ghost {
  background-color: transparent;
  border-color: transparent;
  color: var(--cream-dim);
}

.btn--ghost:hover {
  color: var(--cream);
  border-color: var(--border);
}

.btn--danger {
  background-color: var(--clay-fill);
  border-color: var(--clay-line);
  color: var(--clay-lift);
}

.btn--danger:hover {
  background-color: var(--clay);
  border-color: var(--clay);
  color: var(--cream);
}

/* Full width on phones. The report screen is used one-handed. */
.btn--block { width: 100%; }

.btn--lg {
  min-height: 56px;
  padding: 16px 28px;
  font-size: 1.05rem;
}

/* ---- Disabled. Deliberate, not broken. --------------------------------
   No opacity trick: a half-faded control reads as a rendering fault. This
   reads as a control that is switched off on purpose. ------------------- */
.btn[disabled],
.btn[aria-disabled="true"],
.btn--disabled,
.is-disabled {
  background-color: var(--surface);
  border-color: var(--border);
  color: var(--cream-mute);
  cursor: not-allowed;
  transform: none;
  pointer-events: none;
}

.btn[disabled]:hover,
.btn--disabled:hover,
.is-disabled:hover {
  transform: none;
  background-color: var(--surface);
  border-color: var(--border);
  color: var(--cream-mute);
}

/* ---- Busy. Keeps its own width so the layout never jumps. ------------- */
.btn.is-busy,
.btn[aria-busy="true"] {
  color: transparent;
  pointer-events: none;
  cursor: progress;
}

.btn.is-busy::after,
.btn[aria-busy="true"]::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 18px;
  height: 18px;
  margin: -9px 0 0 -9px;
  border: 2px solid currentColor;
  border-radius: 50%;               /* a ring, not a corner */
  border-top-color: transparent;
  color: var(--on-brass);
  animation: spin 0.7s linear infinite;
}

.btn--secondary.is-busy::after,
.btn--ghost.is-busy::after,
.btn--danger.is-busy::after,
.btn--secondary[aria-busy="true"]::after,
.btn--ghost[aria-busy="true"]::after,
.btn--danger[aria-busy="true"]::after {
  color: var(--cream-dim);
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* ==========================================================================
   7. FORMS
   ========================================================================== */

.field { margin-bottom: var(--space-lg); }

/* Two fields that are one answer: first name and last name. Stacked on a
   phone, side by side once there is room, and the pair takes the spacing of a
   single field so a form does not gap open where it splits. A .help inside
   the row runs the full width and stays close to the inputs it explains. */
.field-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}

.field-row > .field { margin-bottom: 0; }

.field-row > .help {
  grid-column: 1 / -1;
  margin-top: calc(var(--space-sm) * -1);
}

@media (min-width: 420px) {
  .field-row { grid-template-columns: 1fr 1fr; }
}

.label {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--cream);
  margin-bottom: var(--space-sm);
}

.help {
  font-size: 0.85rem;
  color: var(--cream-dim);
  margin-top: var(--space-sm);
  line-height: 1.5;
}

/* "Not done until the slip is on screen." A member who walks away half way
   through a step leaves nothing behind, and until now no copy said so. Brass
   text and a brass rule, never a fill: the one brass fill on the screen is
   the button directly under this line. */
.not-done {
  border-left: 2px solid var(--brass);
  padding-left: var(--space-sm);
  color: var(--brass);
}

.input,
select.input,
textarea.input {
  display: block;
  width: 100%;
  min-height: var(--tap-target);
  padding: 12px 14px;
  background-color: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--cream);
  font-family: var(--font-sans);
  font-size: 1rem;                 /* 16px or more stops iOS zoom on focus */
  line-height: 1.5;
  transition: border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}

.input::placeholder { color: var(--cream-mute); }

/* Native checkboxes and radios pick up the accent instead of browser blue.
   admin.html sizes its one checkbox inline, so it stays under 48px until
   that inline style is removed. Its label is the reliable tap target. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--brass);
  cursor: pointer;
}

.input:hover { border-color: var(--border-loud); }

.input:focus,
.input:focus-visible {
  border-color: var(--brass);
  outline: 2px solid var(--brass);
  outline-offset: 2px;
}

/* Disabled means FROZEN, and it has to look it. admin.html locks the scoring
   fields of a night once somebody has checked in, and an organiser must be
   able to see which boxes are shut without trying one: the well goes flat and
   the cursor says no.

   The VALUE stays readable at --cream-dim, not --cream-mute. A frozen stack
   size or date is still the number the night is being scored on, and an
   organiser reads it far more often than they try to change it. Muting it to
   caption grey drops it under 4:1 on this well, which turns "locked" into
   "unreadable". The flat well and the cursor carry the disabled signal. */
.input:disabled,
select.input:disabled,
textarea.input:disabled {
  background-color: var(--surface);
  border-color: var(--border);
  color: var(--cream-dim);
  cursor: not-allowed;
}

.input:disabled:hover { border-color: var(--border); }

/* A locked switch still has to say WHICH WAY it is set. On a night with a
   check-in, "counts as a round" and "counts toward season points" are frozen,
   and that is exactly the night where an organiser most needs to read them.
   No opacity here: dimming both states towards the same grey is how the two
   pills end up looking identical and the setting becomes unreadable. */
.choice input:disabled + span {
  background-color: var(--surface);
  color: var(--cream-dim);
  border-color: var(--border);
  cursor: not-allowed;
}

/* On, but not touchable. A full brass fill reads as pressable, so a frozen ON
   drops to the brass surface pair the notices use: brass type on a brass
   tinted well, unmistakably the accent, clearly not a button. */
.choice input:disabled:checked + span {
  background-color: var(--brass-fill);
  border-color: var(--brass-line);
  color: var(--brass-lift);
  font-weight: 600;
}

.input--error { border-color: var(--clay); }

.input--error:focus,
.input--error:focus-visible {
  border-color: var(--clay);
  outline-color: var(--clay-lift);
}

/* Big numeric entry (final stack). Read at arm's length, typed one-handed. */
.input--numeric {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 1.5rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  text-align: center;
  min-height: 56px;
}

textarea.input {
  min-height: 120px;
  resize: vertical;
  line-height: 1.55;
}

select.input {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%2374747c' stroke-width='2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 40px;
}

/* Stepper: minus, value, plus. Big targets for tired thumbs. */
.stepper {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background-color: var(--surface-2);
  overflow: hidden;
}

.stepper:focus-within { border-color: var(--brass); }

.stepper__btn {
  width: 56px;
  min-height: 56px;
  font-size: 1.5rem;
  font-weight: 500;
  color: var(--brass);
  background-color: var(--surface);
  flex-shrink: 0;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

.stepper__btn:hover { background-color: var(--surface-2); color: var(--brass-lift); }
.stepper__btn:active { background-color: var(--brass-fill); }

.stepper__btn[disabled] {
  color: var(--cream-mute);
  background-color: var(--surface);
  cursor: not-allowed;
  pointer-events: none;
}

.stepper__value {
  flex: 1;
  border: none;
  background: transparent;
  color: var(--cream);
  text-align: center;
  font-family: var(--font-mono);
  font-size: 1.35rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  min-width: 0;
}

/* The field keeps a focus ring. Stripping it left a keyboard user with no
   visible focus at all, which is a bug even in a component nothing uses yet. */
.stepper__value:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
}

/* Choice group (for example "Did you re-buy tonight?") */
.choice-row {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.choice { position: relative; }

.choice input {
  position: absolute;
  opacity: 0;
  inset: 0;
  cursor: pointer;
}

.choice span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-target);
  padding: 10px 20px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--cream-dim);
  background-color: var(--surface);
  transition: background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}

.choice:hover span { border-color: var(--border-loud); color: var(--cream); }

.choice input:checked + span {
  background-color: var(--brass);
  border-color: var(--brass);
  color: var(--on-brass);
  font-weight: 600;
}

.choice input:focus-visible + span {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
}

/* ==========================================================================
   8. CARDS, BADGES, NOTICES
   A card is a container for a real thing: one night, one event, one form.
   It is never a way to arrange three sentences.
   ========================================================================== */

.card {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-lg);
}

@media (min-width: 640px) {
  .card { padding: var(--space-xl); }
}

.card--accent { border-color: var(--brass-line); }

.card--hover {
  transition: border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}

@media (hover: hover) {
  .card--hover:hover {
    border-color: var(--border-loud);
    background-color: var(--surface-2);
    transform: translateY(-2px);
  }
}

.card--hover:active { transform: translateY(0); }

.card__icon {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--brass);
  line-height: 1;
  margin-bottom: var(--space-md);
}

.card__title {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--cream);
  margin-bottom: var(--space-sm);
}

.card__text {
  color: var(--cream-dim);
  font-size: 0.95rem;
  line-height: 1.55;
}

/* ---- .card-grid -------------------------------------------------------
   Two real objects side by side is a legitimate grid: admin.html puts two
   forms here. Three or more is the forbidden cookie-cutter feature grid,
   so it collapses into a ruled list instead. Browsers without :has() fall
   back to the old grid, which is off-spec but never broken. ------------- */
.card-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
}

@media (min-width: 720px) {
  .card-grid {
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: var(--space-lg);
  }
}

.card-grid:has(> :nth-child(3)) {
  display: block;
  border-top: 1px solid var(--border);
  max-width: 72ch;
}

.card-grid:has(> :nth-child(3)) > .card {
  background-color: transparent;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: var(--space-lg) 0;
  transform: none;
}

.card-grid:has(> :nth-child(3)) > .card:hover {
  background-color: transparent;
  transform: none;
}

.card-grid:has(> :nth-child(3)) > .card .card__icon {
  margin-bottom: var(--space-sm);
  letter-spacing: 0.08em;
}

.card-grid:has(> :nth-child(3)) > .card .card__title {
  font-size: 1.15rem;
}

/* ---- Badges. Square, not pills. --------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--radius);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  border: 1px solid var(--border);
  background-color: var(--surface-2);
  color: var(--cream-dim);
}

.badge--gold {
  background-color: var(--brass-fill);
  color: var(--brass);
  border-color: var(--brass-line);
}

/* Live is the attention state, so it is brass. It does not blink: a badge
   that pulses forever is decoration, and it competes with the sync pill. */
.badge--live {
  background-color: var(--brass);
  color: var(--on-brass);
  border-color: var(--brass);
}

.badge--ok {
  background-color: var(--felt-fill);
  color: var(--felt-lift);
  border-color: var(--felt-line);
}

.badge--warn {
  background-color: var(--brass-fill);
  color: var(--brass);
  border-color: var(--brass-line);
}

.badge--muted {
  color: var(--cream-mute);
  background-color: var(--surface);
}

/* ---- Notices ---------------------------------------------------------- */
.notice {
  display: flex;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brass);
  border-radius: var(--radius);
  background-color: var(--surface);
  color: var(--cream-dim);
  font-size: 0.95rem;
  line-height: 1.55;
}

.notice--warn {
  border-color: var(--brass-line);
  border-left-color: var(--brass);
  background-color: var(--brass-fill);
}

.notice--danger {
  border-color: var(--clay-line);
  border-left-color: var(--clay);
  background-color: var(--clay-fill);
}

/* ==========================================================================
   9. EDITORIAL BLOCK AND DEFINITION LIST  (NEW)
   The replacement for the three-card feature grid. A lead statement carried
   by the display face, against a ruled list of specifics.
   ========================================================================== */

.editorial {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-xl);
  align-items: start;
}

@media (min-width: 900px) {
  .editorial {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: var(--space-4xl);
  }
}

.editorial__lead {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2.6vw, 1.75rem);
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: -0.02em;
  color: var(--cream);
  border-left: 2px solid var(--brass);
  padding-left: var(--space-lg);
}

.editorial__lead p + p { margin-top: var(--space-md); }

.editorial__body { min-width: 0; }

/* ---- Definition list -------------------------------------------------- */
.deflist {
  border-top: 1px solid var(--border);
}

.deflist dt {
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 600;
  color: var(--cream);
  letter-spacing: -0.01em;
  padding-top: var(--space-lg);
}

.deflist dd {
  color: var(--cream-dim);
  font-size: 0.95rem;
  line-height: 1.55;
  padding: var(--space-xs) 0 var(--space-lg);
  border-bottom: 1px solid var(--border);
  max-width: 60ch;
}

.deflist dt .num,
.deflist dt .mono {
  color: var(--brass);
  font-weight: 500;
  text-align: left;
}

.deflist--lead dt {
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}

/* ==========================================================================
   10. NUMBERED STEP LIST
   Was five cards with circular counters. Now a list with a rule between
   steps and a mono counter in the gutter. Shared by rules.html and index.html.
   ========================================================================== */

.steps {
  counter-reset: step;
  margin: var(--space-xl) 0;
  border-top: 1px solid var(--border);
  max-width: 68ch;
}

.step {
  counter-increment: step;
  display: grid;
  grid-template-columns: 2.75rem 1fr;
  gap: var(--space-md);
  align-items: start;
  padding: var(--space-lg) 0;
  border-bottom: 1px solid var(--border);
  background: none;
  border-radius: 0;
}

.step::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--font-mono);
  font-size: 0.9rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
  color: var(--brass);
  padding-top: 2px;
}

.step h3,
.step .step__title {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--cream);
  margin: 0 0 var(--space-xs);
}

.step p {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--cream-dim);
  max-width: 60ch;
}

/* ==========================================================================
   11. STATS BAND
   A sharp bordered row. Rules top and bottom, 1px dividers between cells,
   mono numerals, no boxes.
   ========================================================================== */

.stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  gap: 0;
}

@media (min-width: 640px) {
  .stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .stats--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.stat {
  padding: var(--space-lg) var(--space-md);
  border-left: 1px solid var(--border);
  background: none;
  border-radius: 0;
  text-align: left;
}

/* No dangling left rule at the start of each visual row. */
.stat:nth-child(odd) { border-left: 0; }

@media (min-width: 640px) {
  .stat:nth-child(odd) { border-left: 1px solid var(--border); }
  .stat:first-child { border-left: 0; }
  .stats:not(.stats--4) .stat:nth-child(3n + 1) { border-left: 0; }
  .stats--4 .stat:nth-child(4n + 1) { border-left: 0; }
}

/* The second row of the 2-up phone layout needs its own top rule. */
@media (max-width: 639px) {
  .stat:nth-child(n + 3) { border-top: 1px solid var(--border); }
}

.stat__value {
  font-family: var(--font-mono);
  font-size: clamp(1.6rem, 6vw, 2.1rem);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
  line-height: 1.1;
  color: var(--brass);
}

.stat__label {
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--cream-mute);
  margin-top: var(--space-sm);
}

/* ==========================================================================
   12. HERO
   ========================================================================== */

.hero {
  position: relative;
  padding: calc(var(--nav-height) + var(--space-3xl)) 0 var(--space-3xl);
  overflow: hidden;
  min-height: 78vh;
  display: flex;
  align-items: center;
  text-align: center;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -1;
}

.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 60%;
}

/* The one motivated gradient on the site: a scrim so the title stays
   readable over a photograph. Tuned to --bg so it lands on the page colour. */
.hero__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(12, 12, 13, 0.86) 0%,
    rgba(12, 12, 13, 0.66) 45%,
    rgba(12, 12, 13, 0.97) 100%);
}

.hero__inner { width: 100%; }

.hero__title {
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 7vw, 4.5rem);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--cream);
  margin-bottom: var(--space-xl);
}

.hero__title em {
  font-style: italic;
  font-weight: 400;
  color: var(--brass);
}

/* Sits BELOW the actions: the title and the two buttons carry the hero, and
   this reads as the quieter explanation underneath rather than competing. */
.hero__subtitle {
  font-size: clamp(1.05rem, 2vw, 1.15rem);
  font-weight: 400;
  color: var(--cream-dim);
  max-width: 52ch;
  margin: var(--space-2xl) auto 0;
  line-height: 1.55;
}

.hero__actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}

@media (max-width: 479px) {
  .hero__actions { flex-direction: column; align-items: stretch; }
  .hero__actions .btn { width: 100%; }
}

/* The live-night route on the landing page. Hidden in the markup and
   revealed by js/rsvp.js only when the database says a night is open, so the
   six quiet days render exactly as they did before. */
.hero__tonight {
  max-width: 420px;
  margin: 0 auto var(--space-lg);
}

.hero__tonight .help { margin-top: var(--space-sm); }

/* ==========================================================================
   13. COUNTDOWN
   ========================================================================== */

.countdown-section { padding: var(--space-2xl) 0 0; }

.countdown {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-lg);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brass);
  border-radius: var(--radius);
  padding: var(--space-lg);
  transition: border-color var(--dur) var(--ease);
}

@media (min-width: 768px) {
  .countdown {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-xl) var(--space-2xl);
  }
}

.countdown__info { flex: 1; min-width: 0; }

.countdown__label {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--brass);
  margin-bottom: var(--space-xs);
}

.countdown__event-name {
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--cream);
  margin-bottom: var(--space-xs);
}

.countdown__meta {
  font-size: 0.85rem;
  color: var(--cream-dim);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.countdown__meta-sep { color: var(--cream-mute); }

.countdown__timer {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  flex-shrink: 0;
}

.countdown__unit {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 48px;
}

.countdown__digit {
  font-family: var(--font-mono);
  font-size: 1.6rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--cream);
  background-color: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 8px;
  min-width: 48px;
  text-align: center;
}

@media (min-width: 768px) {
  .countdown__digit { font-size: 1.8rem; min-width: 54px; }
}

.countdown__unit-label {
  font-size: 0.6rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--cream-mute);
  margin-top: 6px;
}

.countdown__sep {
  font-family: var(--font-mono);
  font-size: 1.4rem;
  font-weight: 400;
  color: var(--cream-mute);
  line-height: 1;
  padding-top: 12px;
}

.countdown--live { border-left-color: var(--brass-lift); }
.countdown--live .countdown__label { color: var(--brass-lift); }

/* ==========================================================================
   14. PHOTOGRAPHY
   ========================================================================== */

.photo-break {
  position: relative;
  height: 260px;
  overflow: hidden;
}

@media (min-width: 768px) {
  .photo-break { height: 380px; }
}

.photo-break img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Motivated: blends a full-bleed photograph into the page colour. */
.photo-break::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--bg) 0%, transparent 22%, transparent 78%, var(--bg) 100%);
  pointer-events: none;
}

.figure {
  margin: var(--space-xl) 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.figure img { width: 100%; }

.figure figcaption {
  padding: var(--space-sm) var(--space-md);
  font-size: 0.8rem;
  color: var(--cream-dim);
  background-color: var(--surface);
  border-top: 1px solid var(--border);
}

/* ==========================================================================
   15. EVENT CARDS
   A night is a real object, so this one stays a card.
   ========================================================================== */

.events-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.event-card {
  display: grid;
  grid-template-columns: 56px 1fr;
  align-items: center;
  gap: var(--space-md);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-md);
  transition: border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}

@media (min-width: 640px) {
  .event-card {
    grid-template-columns: 72px 1fr auto;
    gap: var(--space-lg);
    padding: var(--space-lg) var(--space-xl);
  }
}

@media (hover: hover) {
  .event-card:hover {
    border-color: var(--border-loud);
    background-color: var(--surface-2);
    transform: translateY(-2px);
  }
}

.event-card__date {
  text-align: center;
  border-right: 1px solid var(--border);
  padding-right: var(--space-md);
}

.event-card__day {
  font-family: var(--font-mono);
  font-size: 1.7rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--cream);
}

.event-card__month {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--brass);
  margin-top: 6px;
}

.event-card__weekday {
  font-size: 0.7rem;
  color: var(--cream-mute);
  margin-top: 2px;
}

.event-card__info { min-width: 0; }

.event-card__title {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--cream);
  margin-bottom: var(--space-xs);
}

.event-card__meta {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  font-size: 0.85rem;
  color: var(--cream-dim);
  flex-wrap: wrap;
}

/* The first cell is always the start time, from js/app.js and from the
   noscript fallback alike. Styling it here keeps both renders identical
   without touching the script. */
.event-card__meta > span:first-child {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--cream);
}

/* "Venue still to be confirmed", written by js/rsvp.js when the database
   says the room is not settled. Muted on purpose: it is an honest not-yet,
   not an alarm, and it must not out-shout the confirmed room on the card
   above it. Used on the events cards and on the home page countdown. */
.venue--tbd {
  color: var(--cream-mute);
  font-style: italic;
}

.event-card__desc {
  margin-top: var(--space-sm);
  font-size: 0.9rem;
  color: var(--cream-dim);
  line-height: 1.55;
  max-width: 60ch;
}

.event-card__links {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-top: var(--space-md);
  flex-wrap: wrap;
}

.event-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 38px;
  padding: 4px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--cream-dim);
  background-color: var(--surface-2);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.event-link:hover {
  color: var(--cream);
  border-color: var(--border-loud);
}

.event-card__badge { justify-self: start; }

@media (max-width: 639px) {
  .event-card__badge { grid-column: 1 / -1; }
}

/* ==========================================================================
   16. TABLES, LEADERBOARD, PODIUM
   ========================================================================== */

.table-wrapper {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.table {
  width: 100%;
  border-collapse: collapse;
}

.table th {
  padding: var(--space-md) var(--space-lg);
  font-family: var(--font-sans);
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--cream-mute);
  text-align: left;
  background-color: var(--surface);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.table th.num,
.table td.num {
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  white-space: nowrap;
}

.table th.num {
  font-family: var(--font-sans);
  letter-spacing: 0.12em;
}

.table td {
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
  vertical-align: middle;
  color: var(--cream);
}

.table tbody tr:last-child td { border-bottom: none; }

.table tbody tr { transition: background-color var(--dur) var(--ease); }
.table tbody tr:hover { background-color: var(--surface-2); }

.table td.num { font-weight: 500; }

@media (max-width: 639px) {
  .table th, .table td { padding: var(--space-sm) var(--space-md); }
  .table td { font-size: 0.875rem; }
}

/* ---- Rank ------------------------------------------------------------- */
.rank-number {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--cream-mute);
  min-width: 2.2ch;
  display: inline-block;
  text-align: right;
}

/* One accent. The top three are marked by brass and a rule, not by silver,
   bronze, and a gold wash. */
.leaderboard tbody tr:nth-child(-n + 3) .rank-number {
  color: var(--brass);
}

/* An inset shadow here is a rule, not a lift: a real border-left would
   reflow the cell by 2px and knock the rank column out of alignment. */
.leaderboard tbody tr:nth-child(-n + 3) td:first-child {
  box-shadow: inset 2px 0 0 0 var(--brass);
}

/* A full-width cell is never a rank cell: it is the empty state or a message
   row. Without this it would sit at tr:nth-child(1) and get flagged as first
   place. No :has() involved, so it holds in every browser. */
.leaderboard tbody td[colspan] {
  box-shadow: none;
}

.leaderboard tbody tr:nth-child(-n + 3) .player-name {
  color: var(--cream);
  font-weight: 600;
}

.player-cell {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-width: 0;
}

.player-avatar {
  width: 34px;
  height: 34px;
  border-radius: var(--radius);
  background-color: var(--surface-2);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--brass);
  flex-shrink: 0;
}

.player-name {
  font-weight: 500;
  color: var(--cream);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- Podium -----------------------------------------------------------
   Was three raised cards with a glow. Now three sharp rows above the table.
   js/app.js emits medal, name, points in that order. ------------------- */
.podium {
  display: block;
  border-top: 1px solid var(--border);
  margin-bottom: var(--space-xl);
}

.podium__place {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: var(--space-md);
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--border);
  background: none;
  border-radius: 0;
  text-align: left;
}

.podium__place--1 {
  border-bottom-color: var(--border-loud);
  padding: var(--space-lg) 0;
}

.podium__medal {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--cream-mute);
  min-width: 8ch;
}

.podium__place--1 .podium__medal { color: var(--brass); }

.podium__name {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--cream);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.podium__place--1 .podium__name { font-size: 1.5rem; }

.podium__points {
  font-family: var(--font-mono);
  font-size: 0.9rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--cream-dim);
  white-space: nowrap;
}

.podium__place--1 .podium__points { color: var(--brass); }

@media (max-width: 479px) {
  .podium__medal { min-width: 6ch; font-size: 0.65rem; }
  .podium__name { font-size: 1rem; }
  .podium__place--1 .podium__name { font-size: 1.2rem; }
}

.data-status {
  margin-top: var(--space-md);
  font-size: 0.8rem;
  color: var(--cream-mute);
}

/* ==========================================================================
   17. PAGE HEADER AND PROSE
   ========================================================================== */

.page-header {
  padding: calc(var(--nav-height) + var(--space-2xl)) 0 var(--space-2xl);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-2xl);
}

@media (min-width: 768px) {
  .page-header { padding: calc(var(--nav-height) + var(--space-3xl)) 0 var(--space-2xl); }
}

.page-header__title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, 3rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.1;
  color: var(--cream);
  margin-bottom: var(--space-sm);
}

.page-header__subtitle {
  color: var(--cream-dim);
  font-size: 1.05rem;
  max-width: 56ch;
  line-height: 1.55;
}

.prose {
  color: var(--cream-dim);
  font-size: 1rem;
  line-height: 1.55;
  max-width: 68ch;
}

.prose h2 {
  font-family: var(--font-display);
  font-size: 1.7rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--cream);
  margin: var(--space-2xl) 0 var(--space-md);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--border);
}

.prose > h2:first-child { border-top: 0; padding-top: 0; margin-top: 0; }

.prose h3 {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--cream);
  margin: var(--space-xl) 0 var(--space-sm);
}

.prose p { margin-bottom: var(--space-md); }

.prose ul, .prose ol {
  list-style: revert;
  padding-left: 1.4em;
  margin-bottom: var(--space-md);
}

.prose li { margin-bottom: var(--space-sm); }

.prose strong { color: var(--cream); font-weight: 600; }

.prose em { color: var(--cream); font-style: italic; }

.prose a:not(.btn) {
  color: var(--brass);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-color: var(--brass-line);
  transition: color var(--dur) var(--ease), text-decoration-color var(--dur) var(--ease);
}

.prose a:not(.btn):hover {
  color: var(--brass-lift);
  text-decoration-color: var(--brass-lift);
}

.prose .table-wrapper { margin: var(--space-lg) 0; max-width: none; }

/* ---- Worked example ledger -------------------------------------------- */
.ledger {
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  margin: var(--space-lg) 0;
}

.ledger__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--border);
  font-size: 0.95rem;
  color: var(--cream-dim);
}

.ledger__row:last-child { border-bottom: none; }

.ledger__row--total {
  background-color: var(--surface-2);
  border-top: 1px solid var(--border-loud);
  font-weight: 600;
  color: var(--cream);
}

.ledger__amount {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  font-weight: 500;
  color: var(--cream);
  white-space: nowrap;
}

.ledger__amount--plus { color: var(--felt-lift); }
.ledger__amount--minus { color: var(--clay-lift); }

/* ---- The points-only statement. Deliberately the loudest block. ------- */
.no-money {
  border: 1px solid var(--border);
  border-left: 3px solid var(--brass);
  border-radius: var(--radius);
  background-color: var(--surface);
  padding: var(--space-xl);
  margin: var(--space-xl) 0;
}

.no-money h2, .no-money h3 {
  font-family: var(--font-display);
  font-size: 1.4rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--cream);
  margin: 0 0 var(--space-sm);
  padding: 0;
  border: 0;
}

.no-money p { color: var(--cream-dim); margin-bottom: var(--space-sm); }
.no-money p:last-child { margin-bottom: 0; }

/* ---- Founder quote ---------------------------------------------------- */
.story-quote {
  margin: var(--space-lg) 0 0;
  padding: var(--space-sm) 0 var(--space-sm) var(--space-lg);
  border-left: 2px solid var(--brass);
  background: none;
  border-radius: 0;
}

.story-quote p {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.15rem;
  line-height: 1.45;
  color: var(--cream);
  margin-bottom: var(--space-sm);
}

.story-quote cite {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cream-mute);
}

/* ==========================================================================
   18. FOOTER (shared chrome, byte-identical markup on every page)
   ========================================================================== */

.footer {
  margin-top: var(--space-3xl);
  padding: var(--space-2xl) 0;
  border-top: 1px solid var(--border);
}

.footer__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
  text-align: center;
}

.footer__brand {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--cream);
}

.footer__links {
  display: flex;
  gap: var(--space-lg);
  flex-wrap: wrap;
  justify-content: center;
}

.footer__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  font-size: 0.85rem;
  color: var(--cream-dim);
  transition: color var(--dur) var(--ease);
}

.footer__link:hover { color: var(--cream); }

.footer__socials {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.footer__social {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-target);
  height: var(--tap-target);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--cream-dim);
  transition: color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}

@media (hover: hover) {
  .footer__social:hover {
    color: var(--brass);
    border-color: var(--border-loud);
    transform: translateY(-1px);
  }
}

.footer__social:active { transform: translateY(0); }

.footer__nomoney {
  max-width: 60ch;
  font-size: 0.8rem;
  color: var(--cream-mute);
  line-height: 1.55;
}

.footer__text {
  font-size: 0.8rem;
  color: var(--cream-mute);
}

/* ==========================================================================
   19. EMPTY, LOADING, AND ENTRANCE STATES
   ========================================================================== */

.empty-state {
  text-align: center;
  padding: var(--space-3xl) var(--space-lg);
  border: 1px dashed var(--border-loud);
  border-radius: var(--radius);
}

/* The 3rem glyph at half opacity was decoration. Kept as a small brass mark
   so existing markup does not leave a hole. */
.empty-state__icon {
  font-family: var(--font-mono);
  font-size: 1rem;
  color: var(--brass);
  margin-bottom: var(--space-sm);
  line-height: 1;
}

.empty-state__text {
  color: var(--cream-dim);
  font-size: 1rem;
}

/* ---- Skeletons --------------------------------------------------------
   For the leaderboard and the admin tables while the first query is in
   flight. The sweep is motion that means "working", not decoration.
   Mark the wrapper aria-hidden="true": screen readers get aria-live copy. */
.skeleton {
  display: block;
  position: relative;
  overflow: hidden;
  height: 0.95em;
  background-color: var(--surface-2);
  border-radius: var(--radius);
  color: transparent;
  user-select: none;
}

.skeleton::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, var(--border-loud), transparent);
  animation: skeleton-sweep 1.3s var(--ease) infinite;
}

@keyframes skeleton-sweep {
  to { transform: translateX(100%); }
}

.skeleton--rank { width: 2ch; }
.skeleton--name { width: min(14ch, 60%); }
.skeleton--num { width: 6ch; margin-left: auto; }
.skeleton--line { width: 100%; }
.skeleton--title { width: 45%; height: 1.4em; }

.skeleton-table {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background-color: var(--surface);
}

.skeleton-table__row {
  display: grid;
  grid-template-columns: 3ch 1fr 6ch;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--border);
}

.skeleton-table__row:last-child { border-bottom: 0; }

/* Stagger, so the rows read as a list loading rather than one flat block. */
.skeleton-table__row:nth-child(2) .skeleton::after { animation-delay: 0.08s; }
.skeleton-table__row:nth-child(3) .skeleton::after { animation-delay: 0.16s; }
.skeleton-table__row:nth-child(4) .skeleton::after { animation-delay: 0.24s; }
.skeleton-table__row:nth-child(5) .skeleton::after { animation-delay: 0.32s; }

@media (max-width: 639px) {
  .skeleton-table__row { padding: var(--space-md); }
}

/* ---- Entrance ---------------------------------------------------------
   .fade-in runs on first paint. .reveal is gated behind the .js class that
   js/app.js sets before anything else, so if the script never runs the
   content is simply there. Enhance downward, never hide by default. ----- */
.fade-in {
  opacity: 0;
  transform: translateY(8px);
  animation: enter var(--dur-enter) var(--ease) forwards;
}

@keyframes enter {
  to { opacity: 1; transform: translateY(0); }
}

.fade-in-delay-1 { animation-delay: 0.06s; }
.fade-in-delay-2 { animation-delay: 0.12s; }
.fade-in-delay-3 { animation-delay: 0.18s; }

.js .reveal {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity var(--dur-enter) var(--ease),
              transform var(--dur-enter) var(--ease);
}

.js .reveal--visible {
  opacity: 1;
  transform: translateY(0);
}

/* ==========================================================================
   20. MEMBER APP SCREENS (login, report, admin)
   Phone-first, dark-room-first: big targets, loud states, zero ambiguity.
   ========================================================================== */

.app-main {
  padding-top: calc(var(--nav-height) + var(--space-xl));
  padding-bottom: var(--space-3xl);
}

.app-title {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 5vw, 2.3rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.15;
  color: var(--cream);
  margin-bottom: var(--space-xs);
}

.app-subtitle {
  color: var(--cream-dim);
  font-size: 0.95rem;
  line-height: 1.55;
  margin-bottom: var(--space-xl);
}

/* ---- Segmented tabs (Sign in / Create account) ------------------------ */
.tabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: var(--space-lg);
}

.tabs__btn {
  min-height: var(--tap-target);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--cream-dim);
  background-color: var(--surface);
  border-radius: 0;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.tabs__btn + .tabs__btn { border-left: 1px solid var(--border); }

.tabs__btn:hover { color: var(--cream); }

/* Same again: the selected underline is drawn inset so the label does not
   shift 2px when selection moves between the two tabs. */
.tabs__btn[aria-selected="true"] {
  background-color: var(--brass-fill);
  color: var(--brass);
  box-shadow: inset 0 -2px 0 0 var(--brass);
}

/* ---- "or" divider ----------------------------------------------------- */
.or-divider {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin: var(--space-lg) 0;
  color: var(--cream-mute);
  font-size: 0.8rem;
}

.or-divider::before,
.or-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background-color: var(--border);
}

/* ---- Inline status line under a form ---------------------------------- */
.form-msg {
  margin-top: var(--space-md);
  font-size: 0.9rem;
  line-height: 1.5;
  min-height: 1.4em;
  color: var(--cream-dim);
}

.form-msg--error { color: var(--clay-lift); }
.form-msg--ok { color: var(--felt-lift); }
.form-msg--busy { color: var(--cream-dim); }

/* ---- Pseudonym availability ------------------------------------------- */
.avail { font-size: 0.85rem; margin-top: var(--space-sm); min-height: 1.4em; }
.avail--free { color: var(--felt-lift); }
.avail--legacy { color: var(--brass); }
.avail--neutral { color: var(--cream-mute); }

.chip-suggestions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-top: var(--space-md);
}

.chip-suggestion {
  min-height: 40px;
  padding: 6px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background-color: var(--surface);
  color: var(--cream-dim);
  font-size: 0.9rem;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.chip-suggestion:hover { border-color: var(--border-loud); color: var(--brass); }

/* ---- Signed-in identity strip ----------------------------------------- */
.whoami {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-sm) 0 var(--space-lg);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-lg);
  flex-wrap: wrap;
}

.whoami__name { font-weight: 600; color: var(--cream); }

.whoami__label {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--cream-mute);
}

/* ---- The venue, under the night title on admin.html ------------------- */
.night-venue {
  font-size: 0.9rem;
  color: var(--cream-dim);
  margin-top: var(--space-xs);
  line-height: 1.5;
}

/* Brass, because an unset room is a job with a deadline, not a blank field.
   The one accent, used where it means "this wants your attention". */
.night-venue--unset {
  color: var(--brass);
  font-weight: 500;
}

.night-venue__map {
  color: var(--cream-mute);
  border-bottom: 1px solid var(--border-loud);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.night-venue__map:hover { color: var(--brass); border-color: var(--brass); }

/* ---- Night banner at the top of the report screen --------------------- */
.night-banner {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-md);
  flex-wrap: wrap;
  padding: var(--space-md) var(--space-lg);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brass);
  border-radius: var(--radius);
  margin-bottom: var(--space-lg);
}

.night-banner__title {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--cream);
}

.night-banner__meta {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  color: var(--cream-dim);
}

/* ---- Key figures (bonus, buy-in, cap) --------------------------------- */
.kv {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0 var(--space-md);
  margin: var(--space-md) 0;
  font-size: 0.95rem;
}

.kv dt {
  color: var(--cream-dim);
  padding: var(--space-sm) 0;
  border-top: 1px solid var(--border);
}

.kv dd {
  text-align: right;
  font-family: var(--font-mono);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--cream);
  padding: var(--space-sm) 0;
  border-top: 1px solid var(--border);
}

.kv dt:first-of-type, .kv dd:first-of-type { border-top: 0; }

/* ---- The one-tap bust button. Impossible to miss, hard to hit by
   accident: it sits apart and reads as a state, not a submit. ---------- */
.btn--bust {
  width: 100%;
  min-height: 64px;
  margin-top: var(--space-md);
  background-color: var(--clay-fill);
  border: 1px solid var(--clay-line);
  border-radius: var(--radius);
  color: var(--clay-lift);
  font-family: var(--font-sans);
  font-size: 1.1rem;
  font-weight: 600;
  transition: background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
  -webkit-tap-highlight-color: transparent;
}

.btn--bust:hover { border-color: var(--clay); color: var(--cream); }
.btn--bust:active { transform: translateY(1px); }

/* Chosen. Sits after .btn--bust:hover on purpose: the two rules agree on
   border and text, and only this one fills the button. */
.btn--bust--on {
  background-color: var(--clay);
  border-color: var(--clay);
  color: var(--cream);
  font-weight: 700;
}

/* ---- Outbox status pill ----------------------------------------------- */
.sync-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  border: 1px solid var(--border);
  background-color: var(--surface);
  color: var(--cream-dim);
}

.sync-pill__dot {
  width: 8px;
  height: 8px;
  background-color: var(--cream-mute);
  flex-shrink: 0;
}

/* Sending is the one place a repeating animation is earned: it is the
   spinner for a queued write, and it stops when the write lands. */
.sync-pill--sending .sync-pill__dot {
  background-color: var(--brass);
  animation: blink 1.1s steps(2, jump-none) infinite;
}

.sync-pill--queued .sync-pill__dot { background-color: var(--brass); }
.sync-pill--sent .sync-pill__dot { background-color: var(--felt-lift); }
.sync-pill--failed .sync-pill__dot { background-color: var(--clay-lift); }

.sync-pill--sent { border-color: var(--felt-line); color: var(--felt-lift); }
.sync-pill--failed { border-color: var(--clay-line); color: var(--clay-lift); }
.sync-pill--queued, .sync-pill--sending { border-color: var(--brass-line); color: var(--brass); }

@keyframes blink {
  50% { opacity: 0.25; }
}

/* ---- Receipt ---------------------------------------------------------- */
.receipt__icon {
  width: 56px;
  height: 56px;
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.6rem;
  margin: 0 auto var(--space-md);
  background-color: var(--felt-fill);
  border: 1px solid var(--felt-line);
  color: var(--felt-lift);
}

.receipt__icon--saved {
  background-color: var(--brass-fill);
  border-color: var(--brass-line);
  color: var(--brass);
}

/* ---- "Could not send, show this to an organiser" ----------------------
   Read across a table in a dark room. Mono, 24px, high contrast.
   The margin is on the BOTTOM, not the top: this card is the first child
   of #state-receipt so that the heading pointing at it is the line
   directly above it, and the space it needs is the space under it. ---- */
.organiser-card {
  border: 1px solid var(--clay);
  border-left: 3px solid var(--clay);
  border-radius: var(--radius);
  background-color: var(--clay-fill);
  padding: var(--space-lg);
  margin-bottom: var(--space-lg);
}

.organiser-card h3 {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  margin-bottom: var(--space-md);
  color: var(--cream);
}

.organiser-payload {
  background-color: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-lg);
  font-family: var(--font-mono);
  font-size: 24px;
  line-height: 1.4;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--cream);
}

.organiser-payload .organiser-payload__label {
  display: block;
  font-family: var(--font-sans);
  font-size: 0.65em;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cream-mute);
  margin-bottom: 2px;
}

.organiser-payload > div + div {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border);
}

/* ==========================================================================
   21. ADMIN CONSOLE
   ========================================================================== */

.toolbar {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
  align-items: center;
}

.toolbar--right { justify-content: flex-end; }

/* ---- Night status pills ----------------------------------------------- */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border: 1px solid var(--border);
  color: var(--cream-dim);
  background-color: var(--surface-2);
}

.status-pill--draft { color: var(--cream-mute); }

.status-pill--open {
  color: var(--felt-lift);
  border-color: var(--felt-line);
  background-color: var(--felt-fill);
}

.status-pill--reconciling {
  color: var(--brass);
  border-color: var(--brass-line);
  background-color: var(--brass-fill);
}

/* Settled is a fact in the banner, not something to press. A solid brass
 * fill made it the one brass element on the whole screen, which breaks the
 * rule that brass marks the primary action: on the escalated receipt, where
 * the closing slip is deliberately demoted because the only thing left to do
 * is read the numbers to an organiser, this pill was the single brass thing
 * on screen, pulling the eye to a status word. Brass type on the same tint
 * the closing pill uses, told apart by the full strength border and by the
 * word itself. */
.status-pill--settled {
  color: var(--brass);
  border-color: var(--brass);
  background-color: var(--brass-fill);
}

.status-pill--void {
  color: var(--clay-lift);
  border-color: var(--clay-line);
  background-color: var(--clay-fill);
}

/* ---- Night list rows --------------------------------------------------- */
.night-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  width: 100%;
  min-height: var(--tap-target);
  text-align: left;
  padding: var(--space-md) var(--space-lg);
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: var(--space-sm);
  font-size: 0.95rem;
  transition: border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}

.night-row:hover { border-color: var(--border-loud); background-color: var(--surface-2); }

.night-row--selected {
  border-color: var(--brass);
  background-color: var(--brass-fill);
}

.night-row__title { font-weight: 600; color: var(--cream); }

.night-row__meta {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  color: var(--cream-mute);
}

/* ---- Mini stats for the selected night --------------------------------
   Same sharp-row logic as the front page band, at admin density. ------- */
.mini-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin: var(--space-md) 0 var(--space-lg);
  overflow: hidden;
}

@media (min-width: 640px) {
  .mini-stats { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.mini-stat {
  padding: var(--space-md);
  background-color: var(--surface);
  border-left: 1px solid var(--border);
  border-top: 1px solid var(--border);
  text-align: left;
}

.mini-stat:nth-child(odd) { border-left: 0; }
.mini-stat:nth-child(-n + 2) { border-top: 0; }

@media (min-width: 640px) {
  .mini-stat { border-top: 0; }
  .mini-stat:nth-child(odd) { border-left: 1px solid var(--border); }
  .mini-stat:first-child { border-left: 0; }
}

.mini-stat__value {
  font-family: var(--font-mono);
  font-size: 1.5rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
  line-height: 1.15;
  color: var(--cream);
}

.mini-stat__label {
  font-size: 0.7rem;
  color: var(--cream-mute);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-top: var(--space-xs);
}

.mini-stat--alert { background-color: var(--brass-fill); }
.mini-stat--alert .mini-stat__value { color: var(--brass); }
.mini-stat--ok .mini-stat__value { color: var(--felt-lift); }

/* ---- Who has not reported. The loudest thing on the admin screen. ----- */
.notreported {
  border: 1px solid var(--brass);
  border-left: 3px solid var(--brass);
  border-radius: var(--radius);
  background-color: var(--brass-fill);
  padding: var(--space-lg);
  margin-bottom: var(--space-lg);
}

.notreported h3 {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--brass);
  margin-bottom: var(--space-md);
}

.notreported__names {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.notreported__name {
  padding: 8px 14px;
  border-radius: var(--radius);
  background-color: var(--surface);
  border: 1px solid var(--brass-line);
  color: var(--cream);
  font-size: 1rem;
  font-weight: 600;
}

.notreported--clear {
  border-color: var(--felt-line);
  border-left-color: var(--felt-lift);
  background-color: var(--felt-fill);
}

.notreported--clear h3 { color: var(--felt-lift); }

/* ---- Inline two-tap confirm ------------------------------------------- */
.confirm-strip {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
  flex-wrap: wrap;
  padding: var(--space-md);
  border: 1px solid var(--clay-line);
  border-left: 3px solid var(--clay);
  border-radius: var(--radius);
  background-color: var(--clay-fill);
  font-size: 0.9rem;
  color: var(--cream);
}

/* ---- Bulk paste preview ----------------------------------------------- */
.bulk-line--ok { color: var(--felt-lift); }
.bulk-line--bad { color: var(--clay-lift); }

/* ---- Compact tables inside admin cards -------------------------------- */
.table--compact th,
.table--compact td { padding: var(--space-sm) var(--space-md); }

/* ---- Member directory: search, the removed toggle, removed rows --------
   Search and the "Show removed" toggle sit on one line, and wrap onto two on
   a phone rather than shrinking the search box to nothing.

   A removed member is muted, never struck through and never clay red:
   removing is reversible, the row is out of the way, not damaged. The muting
   is reinforcement only. The word on the badge carries the meaning, so the
   row still reads as removed in a screenshot, in sunlight, or to somebody who
   does not see the contrast step at all. */
.members-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}

.members-toolbar .btn { flex: 0 0 auto; }

.row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
}

/* --cream-dim, not --cream-mute. A removed row still holds the pseudonym,
   the real name and the email an organiser has to read to be sure they are
   restoring the right person, so it is secondary text, not a caption.
   --cream-mute on --surface measures 4.0:1, under the 4.5:1 floor for body
   text; --cream-dim measures 7.5:1 and is still a clear step down from the
   --cream of a live row. The captions inside the row keep --cream-mute, the
   same as on every other row, so the hierarchy inside the cell survives. */
.table tbody tr.row--removed td { color: var(--cream-dim); }

/* ---- Deviation figure -------------------------------------------------- */
.deviation--bad { color: var(--brass); font-weight: 600; }
.deviation--zero { color: var(--felt-lift); }

/* ==========================================================================
   22. NIGHT ATTENDANCE CODE
   The 5-character code, typed on report.html and projected from admin.html.
   The alphabet already drops 0/O and 1/I/L; mono removes the rest of the
   ambiguity. This is why the whole site carries a mono face.
   ========================================================================== */

.input--code {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 1.8rem;
  font-weight: 500;
  letter-spacing: 0.3em;
  text-indent: 0.3em;              /* re-centres against the trailing space */
  text-align: center;
  text-transform: uppercase;
  padding: var(--space-md);
  min-height: 64px;
}

.input--code::placeholder {
  letter-spacing: 0.3em;
  color: var(--cream-mute);
}

/* Body lock while the takeover is up. */
.no-scroll { overflow: hidden; }

.code-takeover {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background-color: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4vmin;
  text-align: center;
}

.code-takeover[hidden] { display: none; }

.code-takeover__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2.5vmin;
  max-width: 100%;
}

.code-takeover__night {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 2.2vmin;
  color: var(--cream-mute);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.code-takeover__lead {
  font-size: 3vmin;
  font-weight: 500;
  color: var(--cream-dim);
}

.code-takeover__url {
  font-family: var(--font-mono);
  font-size: 4vmin;
  color: var(--brass);
  letter-spacing: -0.01em;
}

/* White card behind the QR: quiet-zone contrast for phone cameras. */
.code-takeover__qr {
  background: #fff;
  border-radius: var(--radius);
  padding: 2vmin;
  width: min(44vmin, 90vw);
  height: min(44vmin, 90vw);
  display: flex;
  align-items: center;
  justify-content: center;
}

.code-takeover__qr svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* The single biggest thing on the screen. No text-shadow: a glow on the one
   glyph set that has to survive being read across a room is sabotage. */
.code-takeover__code {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  /* Sized off the WIDTH, not vmin, and never allowed to wrap. At 16vmin on a
     900px-wide screen the five characters broke onto two lines, which is the
     one thing this element must never do: it is read across a room, off a TV
     nobody controls the resolution of. min() keeps it large on a big display
     and shrinks it to fit a narrow one instead of breaking. */
  font-size: min(16vmin, 15vw);
  line-height: 1;
  white-space: nowrap;
  max-width: 100%;
  letter-spacing: 0.08em;          /* deliberate positive tracking: a code,
                                      not a headline. Separates the glyphs. */
  text-indent: 0.08em;
  color: var(--brass);
}

.code-takeover__close {
  position: absolute;
  top: var(--space-md);
  right: var(--space-md);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--cream-dim);
  font-size: 1rem;
  width: var(--tap-target);
  height: var(--tap-target);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.code-takeover__close:hover {
  color: var(--cream);
  border-color: var(--border-loud);
}

/* ==========================================================================
   23. RSVP
   Tapped on a phone, in a corridor, between lectures. So: two targets at the
   full 48px, one tap, no form, and no pill. The control is a single bordered
   box split by a 1px rule; the chosen half fills with a semantic colour and
   shows a mono tick, so the answer reads by shape as well as by colour.

   Felt for going, clay for not going. That is the semantic use the palette
   reserves them for (positive and negative), not decoration.

   The privacy split lives in the queries in js/rsvp.js, not here: the count
   is anon-readable, the pseudonym list is not, and nobody's decline is ever
   listed by name.
   ========================================================================== */

.rsvp {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border);
}

.rsvp__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 2px var(--space-md);
}

.rsvp__count {
  font-size: 0.9rem;
  line-height: 1.4;
  color: var(--cream);
}

.rsvp__count--empty { color: var(--cream-dim); }

.rsvp__count-num,
.rsvp__aside-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  letter-spacing: -0.01em;
  font-weight: 500;
}

.rsvp__count-num { color: var(--cream); }

.rsvp__aside {
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--cream-mute);
}

/* ---- The control ------------------------------------------------------
   Stacked on a phone: at 360px the card's info column is about 230px wide,
   and two halves of that cannot hold "Can't make it" without wrapping.
   Full-width rows are also the easier thumb target. ---------------------- */
.rsvp__control {
  display: grid;
  grid-template-columns: 1fr;
  max-width: 420px;
  margin-top: var(--space-md);
  background-color: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;              /* the fills stop at the one 4px radius */
}

@media (min-width: 420px) {
  .rsvp__control { grid-template-columns: 1fr 1fr; }
}

.rsvp__choice {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  min-height: var(--tap-target);
  padding: 10px 14px;
  border: 0;
  border-top: 1px solid var(--border);
  background-color: transparent;
  color: var(--cream-dim);
  font-family: var(--font-sans);
  font-size: 0.9rem;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur) var(--ease),
              color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}

.rsvp__choice:first-child { border-top: 0; }

@media (min-width: 420px) {
  .rsvp__choice { border-top: 0; }
  .rsvp__choice + .rsvp__choice { border-left: 1px solid var(--border); }
}

@media (hover: hover) {
  .rsvp__choice:hover {
    background-color: var(--surface);
    color: var(--cream);
  }
}

/* The press. This control is tapped far more often than it is hovered, and
   the hover above is gated behind a pointer a phone does not have. With the
   tap highlight suppressed and the answer only landing on the next render, a
   thumb otherwise got no acknowledgement at all.

   Border intensification, not a translate: the two halves are welded into
   one bordered box and sliding one of them would break the box. Every rule
   below is guarded on `:not([aria-disabled])` so a switched-off control
   never pretends to respond, and each one only intensifies the line the
   half already has, so pressing never changes what the answer looks like. */
.rsvp__choice:not([aria-disabled="true"]):active {
  box-shadow: inset 0 0 0 1px var(--border-loud);
}

.rsvp__choice--in[aria-pressed="true"]:not([aria-disabled="true"]):active {
  box-shadow: inset 0 0 0 1px var(--felt-lift);
}

.rsvp__choice--out[aria-pressed="true"]:not([aria-disabled="true"]):active {
  box-shadow: inset 0 0 0 1px var(--clay-lift);
}

/* The half you have not chosen also takes the surface lift, which is the
   feedback the hover gives on a desktop. The chosen half keeps its fill. */
.rsvp__choice:not([aria-pressed="true"]):not([aria-disabled="true"]):active {
  background-color: var(--surface);
}

/* The control is clipped to its radius, so the ring goes inside it. Same
   2px brass, same intent as the global focus ring. */
.rsvp__choice:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: -4px;
  border-radius: 0;
}

/* Selected. Colour plus a tick, never colour alone. */
.rsvp__choice--in[aria-pressed="true"] {
  background-color: var(--felt-fill);
  color: var(--felt-lift);
  box-shadow: inset 0 0 0 1px var(--felt-line);
}

.rsvp__choice--out[aria-pressed="true"] {
  background-color: var(--clay-fill);
  color: var(--clay-lift);
  box-shadow: inset 0 0 0 1px var(--clay-line);
}

.rsvp__tick {
  width: 1ch;
  font-family: var(--font-mono);
  font-size: 0.85em;
  line-height: 1;
  visibility: hidden;            /* space reserved, so the label never moves */
}

.rsvp__choice[aria-pressed="true"] .rsvp__tick { visibility: visible; }

/* Off on purpose, not broken: no opacity trick, the label just goes quiet.
   Scoped to the half you did NOT pick, and that scoping is the whole point.
   Your own answer is not a disabled control you can ignore, it is the record
   of what you said, and it has to stay readable in exactly the states that
   switch the control off: a save in flight, and a night that has closed.
   Greying it there dropped it to 3.6:1 on the felt fill. Left alone it keeps
   its semantic colour at 5.1:1, which is what the tick beside it implies. */
.rsvp__choice[aria-disabled="true"] { cursor: not-allowed; }

.rsvp__choice[aria-disabled="true"]:not([aria-pressed="true"]) {
  color: var(--cream-mute);
}

/* In flight. Keeps its own width, so the row never jumps. */
.rsvp__choice[aria-busy="true"],
.rsvp__choice[aria-busy="true"][aria-disabled="true"] {
  color: transparent;
  cursor: progress;
}

.rsvp__choice[aria-busy="true"]::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border: 2px solid var(--cream-dim);
  border-radius: 50%;            /* a ring, not a corner */
  border-top-color: transparent;
  animation: spin 0.7s linear infinite;
}

/* ---- Supporting lines ------------------------------------------------- */
.rsvp__signin {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  margin-top: var(--space-xs);
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--brass);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--brass-line);
  transition: color var(--dur) var(--ease),
              text-decoration-color var(--dur) var(--ease);
}

.rsvp__signin:hover {
  color: var(--brass-lift);
  text-decoration-color: var(--brass-lift);
}

.rsvp__hint {
  margin-top: var(--space-sm);
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--cream-mute);
}

.rsvp__msg {
  margin-top: var(--space-sm);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--cream-dim);
}

.rsvp__msg--error { color: var(--clay-lift); }

/* ---- Who is going. Signed-in members only. ---------------------------- */
.rsvp__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-sm);
  margin-top: var(--space-md);
  padding: 0;
  list-style: none;
}

.rsvp__who {
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background-color: var(--surface-2);
  font-size: 0.8rem;
  color: var(--cream-dim);
}

.rsvp__who--me {
  border-color: var(--brass-line);
  background-color: var(--brass-fill);
  color: var(--brass);
}

.rsvp__you {
  margin-left: 6px;
  font-size: 0.7rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--brass);
}

/* ---- Headcount on the home page countdown -----------------------------
   Part of the countdown to look at, part of the RSVP feature to maintain,
   so it lives here with the rest of it. The line holds its height while
   empty: the fetch lands late and must not push the timer around. */
.countdown__going {
  margin-top: var(--space-sm);
  min-height: 1.4em;
  font-size: 0.85rem;
  line-height: 1.4;
  color: var(--cream-dim);
}

.countdown__going-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  letter-spacing: -0.01em;
  font-weight: 500;
  color: var(--cream);
}

/* ==========================================================================
   24. BANK SLIPS AND RE-BUY FLOW
   A slip is the phone screen a player holds up at the chip bank. It is read
   at arm's length in a dim room: one giant mono number, the pseudonym, what
   the number is for, and a live clock so last week's screenshot cannot pass
   as fresh. Same takeover pattern as the night code (section 22).
   ========================================================================== */

.slip {
  position: fixed;
  inset: 0;
  z-index: 1100;
  background-color: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2.2vmin;
  padding: 4vmin;
  padding-top: calc(4vmin + 9vmin);   /* room for the pinned .slip__band */
  text-align: center;
}

/* Direction, read before the number. Colour is faster than a word at four
   metres in bad light, and handing chips OUT to somebody who came to hand
   chips BACK is the most expensive mistake this screen can cause. The words
   carry the same information independently, so a colour-blind organiser
   loses nothing. Pinned to the top edge rather than sitting in the centred
   flow, so it costs the giant numeral nothing: .slip's padding-top above
   reserves its room, and flex centring works inside the content box, so the
   two cannot collide. */
.slip__band {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  min-height: 9vmin;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 4vmin;
  font-family: var(--font-sans);
  font-size: clamp(0.8rem, 2.6vmin, 1.2rem);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}

.slip__band--give { background-color: var(--brass); color: var(--on-brass); }
.slip__band--take { background-color: var(--felt);  color: var(--cream); }

.slip__kicker {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(0.8rem, 2.4vmin, 1rem);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brass);
}

.slip__give {
  font-family: var(--font-sans);
  font-size: clamp(1.5rem, 7vmin, 2.4rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--cream);
  overflow-wrap: anywhere;
  max-width: 100%;
}

/* The single biggest thing on the screen. No glow, no shadow: the number
   has to survive being read across a chip case. */
.slip__amount {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: clamp(3.2rem, 17vmin, 8rem);
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--brass);
  max-width: 100%;
}

.slip__unit {
  font-family: var(--font-mono);
  font-size: clamp(0.95rem, 3.2vmin, 1.3rem);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-indent: 0.22em;             /* re-centres against the tracking */
  text-transform: uppercase;
  color: var(--cream-dim);
}

.slip__note {
  font-size: clamp(1rem, 3vmin, 1.15rem);
  line-height: 1.4;
  color: var(--cream-dim);
}

.slip__note .mono { color: var(--cream); font-weight: 500; }

/* The closing slip's send verdict. The organiser reads this across a chip
   case, so colour is doing real work here. Instant, no transition, so
   prefers-reduced-motion needs nothing. */
.slip__note--ok      { color: var(--felt-lift); }
.slip__note--pending { color: var(--brass); }
.slip__note--alert   { color: var(--clay-lift); }

.slip__night {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(0.75rem, 2.2vmin, 0.9rem);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cream-mute);
  margin-top: 1vmin;
}

/* Ticking seconds: the freshness proof. A screenshot's clock stands still. */
.slip__clock {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.1rem, 3.6vmin, 1.5rem);
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--cream);
}

/* How old this slip is, in words, re-rendered on every tick of the clock so
   it cannot go stale while it is being held up. Always --cream-dim, on every
   slip kind at every age: the words catch an honest double count, and a
   colour would invite a tired organiser to refuse an honest slip. This is a
   trust based club. The clay kicker on a re-show is the only alarm colour
   here, and that is enough. */
.slip__age {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(0.8rem, 2.6vmin, 1rem);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cream-dim);
}

.slip--reshow .slip__kicker { color: var(--clay-lift); }

/* Brass means the bank pays out tonight; cream means the bank counts in.
   The happy side effect: cream on --bg is about 18.8:1 against brass's
   8.5:1, so the closing slip, read at 20:35 in the worst light of the night,
   gets the highest-contrast numeral on the site. */
.slip--take .slip__amount { color: var(--cream); }

.slip__close {
  margin-top: 2vmin;
  min-height: 56px;
  width: min(340px, 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background-color: var(--surface);
  color: var(--cream);
  font-size: 1rem;
  font-weight: 600;
  transition: background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}

.slip__close:hover {
  background-color: var(--surface-2);
  border-color: var(--border-loud);
}

/* ---- Re-buy flow on the report screen --------------------------------- */

/* "You can take up to 7,000." The answer to the whole trip to the bank. */
.rebuy-quote {
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.3;
  color: var(--cream);
  margin-bottom: var(--space-md);
}

.rebuy-quote .mono { color: var(--brass); }

/* Quiet record of a re-buy already taken. A line, not a card in a card. */
.rebuy-done {
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--cream-dim);
}

.rebuy-done .mono {
  font-weight: 500;
  color: var(--cream);
}

/* .link-gold on a <button>: the button reset (section 2) already strips the
   chrome, this restores the 48px tap height an inline link button loses.
   inline-flex + min-height grows only its own line box, so the sentence
   above it keeps its rhythm. */
.rebuy-done .link-gold {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  text-align: left;
}

/* ---- Reporting deadline ------------------------------------------------ */
.report-deadline {
  margin-top: var(--space-md);
  font-size: 0.85rem;
  line-height: 1.5;
  text-align: center;
  color: var(--cream-dim);
}

.report-deadline .mono { color: var(--cream); }

.report-deadline--closed,
.report-deadline--closed .mono { color: var(--clay-lift); }

/* ==========================================================================
   25. REDUCED MOTION
   Cut the duration, never the state. Everything still changes, it just
   arrives immediately.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur: 0.01ms;
    --dur-enter: 0.01ms;
  }

  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* The skeleton has to keep saying "still working", so it stays animated,
     slowly and without travel. */
  .skeleton::after {
    animation: none !important;
    transform: none;
    background: var(--surface-2);
  }

  .btn.is-busy::after,
  .btn[aria-busy="true"]::after,
  .rsvp__choice[aria-busy="true"]::after {
    animation: spin 1.4s linear infinite !important;
  }
}


/* ============================================================================
   25. THE FOUNDER'S LETTER (all-in.html)
   A single long read, so it breaks the site's usual card discipline on
   purpose: no boxes, generous measure, display serif at body size. This is
   the one page meant to be read rather than used.
   ========================================================================= */

.letter {
  max-width: 62ch;
  margin: 0 auto;
}

.letter__note {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cream-mute);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-xl);
}

.letter__body p {
  font-family: var(--font-display);
  font-size: 1.1rem;
  line-height: 1.65;
  color: var(--cream-dim);
  margin-bottom: var(--space-lg);
}

/* The opening address carries the letter, so it gets the full cream. */
.letter__body p:first-child {
  color: var(--cream);
}

.letter__signoff {
  color: var(--cream);
  font-style: italic;
}

.letter__rule {
  border: 0;
  border-top: 1px solid var(--border);
  margin: var(--space-2xl) 0;
}

.letter__after {
  max-width: 62ch;
  margin: var(--space-2xl) auto 0;
  padding-top: var(--space-lg);
  border-top: 1px solid var(--border);
}

.letter__after p {
  color: var(--cream-mute);
  font-size: 0.95rem;
  margin-bottom: var(--space-sm);
}

.letter__after a {
  color: var(--brass);
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color var(--dur) var(--ease);
}

@media (hover: hover) {
  .letter__after a:hover { color: var(--brass-lift); }
}

.story__more {
  margin-top: var(--space-lg);
  font-size: 0.95rem;
}

.story__more a {
  color: var(--brass);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color var(--dur) var(--ease);
}

@media (hover: hover) {
  .story__more a:hover { color: var(--brass-lift); }
}

/* ===========================================================================
   What the round did to you: movement marks on the board
   ===========================================================================
   Two semantic colours and nothing else. --felt-lift and --clay-lift are the
   palette's readable positive and negative, already contrast-checked against
   --bg, so a gain and a loss never rely on the arrow alone to be told apart.
   The muted dot is a held place, which is a result and deserves a mark; a
   member with no earlier standing gets nothing, because we have nothing to
   say about them yet. */

.move {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  margin-left: var(--space-xs);
  white-space: nowrap;
}

.move--up    { color: var(--felt-lift); }
.move--down  { color: var(--clay-lift); }
.move--level { color: var(--cream-mute); }

/* Under the points, not beside them: the number is what the eye wants first
   and the change is the footnote. Block so a long "+26,350" never pushes the
   column wider on a phone. */
.delta {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.4;
  margin-top: 2px;
}

.delta--up    { color: var(--felt-lift); }
.delta--down  { color: var(--clay-lift); }
.delta--level { color: var(--cream-mute); }

.board-open-row {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--space-md);
}

/* ===========================================================================
   Display mode: the board on the screen in the room
   ===========================================================================
   Everything is sized in viewport units so one stylesheet serves a laptop
   lid, a 1080p projector and whatever television the room has this term. The
   row height is the exception: js/app.js computes it and writes
   --board-row-h, because it also has to divide the available height into
   whole rows, and a layout where CSS and JS each guess separately is a
   layout that disagrees with itself at some screen size nobody tested. */

.board {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background-color: var(--bg);
  display: flex;
  flex-direction: column;
  padding: 2.2vmin 2.6vmin;
  --board-row-h: 48px;
}

.board[hidden] { display: none; }

.board__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-lg);
  padding-bottom: 1.4vmin;
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}

.board__heading { display: flex; align-items: baseline; gap: 1.6vmin; flex-wrap: wrap; }

.board__title {
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 2.6vmin, 2.4rem);
  color: var(--cream);
  letter-spacing: -0.01em;
}

.board__sub {
  font-family: var(--font-sans);
  font-size: clamp(0.8rem, 1.5vmin, 1.3rem);
  color: var(--brass);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.board__close {
  font-family: var(--font-mono);
  font-size: clamp(0.7rem, 1.2vmin, 1rem);
  color: var(--cream-mute);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.4em 0.9em;
  cursor: pointer;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.board__close:hover { color: var(--cream); border-color: var(--border-loud); }

.board__grid {
  flex: 1 1 auto;
  display: grid;
  gap: 0 3vmin;
  align-content: start;
  padding-top: 1.2vmin;
  min-height: 0;
}

.board-col { list-style: none; margin: 0; padding: 0; }

.board-row {
  display: grid;
  grid-template-columns: 2.6em 1fr auto auto auto;
  align-items: center;
  gap: 0 1.2vmin;
  height: var(--board-row-h);
  padding: 0 0.6vmin;
  border-bottom: 1px solid var(--border);
}

.board-row__rank {
  font-family: var(--font-mono);
  font-size: calc(var(--board-row-h) * 0.38);
  color: var(--cream-mute);
  text-align: right;
}

/* The name truncates; the 'not reported' badge does not. Putting the
   ellipsis on the whole cell ate the badge from the right on any long
   pseudonym, which hid exactly the rows this screen exists to chase. */
.board-row__name {
  font-family: var(--font-sans);
  font-size: calc(var(--board-row-h) * 0.42);
  color: var(--cream);
  display: flex;
  align-items: baseline;
  min-width: 0;
}

.board-row__who {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.board-row__points {
  font-family: var(--font-mono);
  font-size: calc(var(--board-row-h) * 0.42);
  color: var(--cream);
  font-variant-numeric: tabular-nums;
}

/* The two marks sit on one line here rather than stacked: a wall board has
   width to spare and height it cannot waste. A fixed min-width keeps the
   points column from jittering as numbers arrive during the night. */
.board-row__move {
  min-width: 7.5em;
  text-align: right;
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 0.6em;
}

.board-row__move .delta { display: inline; margin-top: 0; font-size: calc(var(--board-row-h) * 0.3); }
.board-row__move .move  { margin-left: 0; font-size: calc(var(--board-row-h) * 0.3); }

/* The only brass on the screen. Three rows, so it still means something. */
.board-row--top .board-row__rank,
.board-row--top .board-row__name { color: var(--brass); }

.board__foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  padding-top: 1.2vmin;
  border-top: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: clamp(0.75rem, 1.5vmin, 1.4rem);
  color: var(--cream-mute);
}

/* Loud on purpose while anybody is still out. This is the one number on the
   screen that somebody in the room can act on before they go home. */
.board__waiting--open {
  color: var(--clay-lift);
  border: 1px solid var(--clay-line);
  background-color: var(--clay-fill);
  border-radius: var(--radius);
  padding: 0.3em 0.8em;
}

/* A narrow window is not what this screen is for, but it must not fall
   apart in one. The row declares three columns here, so every child beyond
   the third lands in an implicit fourth and steals width from the name:
   adding the trend line silently did exactly that, and short pseudonyms like
   "Daniel" started truncating. The line is the first thing to drop, for the
   same reason it drops from the table: the row already carries the points
   change it is a picture of. */
@media (max-width: 760px) {
  .board { padding: var(--space-md); }
  .board-row { grid-template-columns: 2.2em 1fr auto; }
  .board-row__move { min-width: 0; grid-column: 1 / -1; justify-content: flex-start; }
}

/* ---------------------------------------------------------------------------
   The round's two extremes, on the podium list and on the wall
   ---------------------------------------------------------------------------
   These reuse .podium__place rather than inventing a card pair, so the top
   three and the round's movers read as one object above one table. The rule
   above the pair is the only separation they need: the question changes from
   "who is winning the season" to "what happened last round", and the eye
   should notice that without being shouted at. */

.podium__place--win {
  border-top: 1px solid var(--border-loud);
  margin-top: var(--space-md);
}

.podium__place--win .podium__medal  { color: var(--felt-lift); }
.podium__place--win .podium__points { color: var(--felt-lift); }
.podium__place--loss .podium__medal  { color: var(--clay-lift); }
.podium__place--loss .podium__points { color: var(--clay-lift); }

.board__movers {
  display: flex;
  align-items: baseline;
  gap: 2.4vmin;
  margin-left: auto;
  margin-right: 2vmin;
  font-family: var(--font-mono);
  /* Sized up towards the title: on a wall this is the line somebody reads
     out, and the header has nothing else competing for the right-hand side. */
  font-size: clamp(0.75rem, 1.75vmin, 1.5rem);
  min-width: 0;
}

.board__mover {
  display: flex;
  align-items: baseline;
  gap: 0.6em;
  white-space: nowrap;
  min-width: 0;
}

.board__mover-label {
  color: var(--cream-mute);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.board__mover-name {
  font-family: var(--font-sans);
  font-size: 1.15em;
  color: var(--cream);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A phone holding this open has no width for them, and the same pair is
   already on the page behind it. */
@media (max-width: 760px) {
  .board__movers { display: none; }
}

/* ===========================================================================
   The trend line: a ticker for one player's season
   ===========================================================================
   Three layers and a dot, coloured by whether the season is up or down. The
   area fill is what makes it read as a ticker rather than a stray squiggle,
   and it is kept faint so that fifty of them stacked in a table are texture
   rather than noise.

   The stroke widths are all set with non-scaling-stroke in the markup, so
   these numbers are real pixels at any column width, on a phone and on the
   projector alike. Without that the same line is a hairline in the table and
   a fat crayon on the wall, because the box is stretched, not scaled. */

.spark {
  display: block;
  width: 100%;
  height: 30px;
  overflow: visible;
}

.spark__line {
  fill: none;
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.spark__area { stroke: none; }

/* Drawn as a zero-length round-capped stroke so it stays circular however
   the box is stretched. See the note in js/app.js sparkline(). */
.spark__dot { fill: none; stroke-width: 4.2; }

/* The line you started the season on. Dashed and dim: it is a reference, and
   the moment it competes with the balance itself the chart is ruined. */
.spark__open {
  stroke: var(--border-loud);
  stroke-width: 1;
  stroke-dasharray: 2 3;
}

.spark--up   .spark__line { stroke: var(--felt-lift); }
.spark--up   .spark__area { fill: var(--felt-lift); fill-opacity: 0.12; }
.spark--up   .spark__dot  { stroke: var(--felt-lift); }

.spark--down .spark__line { stroke: var(--clay-lift); }
.spark--down .spark__area { fill: var(--clay-lift); fill-opacity: 0.12; }
.spark--down .spark__dot  { stroke: var(--clay-lift); }

.spark--level .spark__line { stroke: var(--cream-mute); }
.spark--level .spark__area { fill: var(--cream-mute); fill-opacity: 0.08; }
.spark--level .spark__dot  { stroke: var(--cream-mute); }

/* Table column. Narrow enough that Points and Nights keep their weight, wide
   enough that four or five nights are still a shape and not a zigzag. */
.table th.trend,
.table td.trend {
  width: 120px;
  min-width: 120px;
  padding-top: var(--space-sm);
  padding-bottom: var(--space-sm);
}

.table th.trend {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--cream-mute);
}

/* On the wall it sits between the points and the movement marks, which is
   where a ticker puts it: the number, the shape, then the change. */
/* Narrow on purpose. The line only has to carry a shape, and every pixel it
   takes comes out of the name beside it, which is what people are scanning
   for. Widening this to 11vmin cut "Sveinung2Swaggy" in half for no gain in
   legibility of the curve. */
.board-row__spark {
  width: 8vmin;
  max-width: 96px;
  display: block;
}

.board-row__spark .spark { height: calc(var(--board-row-h) * 0.62); }

/* Dropped on a narrow window, and it has to be stated AFTER the rule above
   rather than up with the other narrow-width board rules: same specificity,
   so the later declaration wins, and the earlier one was silently losing.
   Without it the row declares three columns and gets five children, the line
   lands in an implicit fourth, and it overlaps the rank. */
@media (max-width: 760px) {
  .board-row__spark { display: none; }
}

/* A phone has no room for a fifth column, and the row already carries the
   points change that the line is a picture of. */
@media (max-width: 640px) {
  .table th.trend,
  .table td.trend { display: none; }
}
