/* ==========================================================================
   CartaTM Wedding Cup
   Dark, neon-purple, motorsport-flavoured. Hand-written CSS, no framework.
   ========================================================================== */

/* --- Fonts ---------------------------------------------------------------- */

/* Rock Salt is used only for the event wordmark. */
@font-face {
  font-family: 'Rock Salt';
  src: url('/fonts/rock-salt-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* Chakra Petch carries every heading and all the numerics. Latin and Latin
   Extended are split so browsers only download the range a page actually uses. */
@font-face {
  font-family: 'Chakra Petch';
  src: url('/fonts/chakra-petch-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  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;
}
@font-face {
  font-family: 'Chakra Petch';
  src: url('/fonts/chakra-petch-600-latin-ext.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  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;
}
@font-face {
  font-family: 'Chakra Petch';
  src: url('/fonts/chakra-petch-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  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;
}
@font-face {
  font-family: 'Chakra Petch';
  src: url('/fonts/chakra-petch-700-latin-ext.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  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;
}

/* Azeret Mono carries every number: times, points, ranks and countdown digits.
   Chakra Petch has no tabular figures - its "1" is roughly 40% narrower than its
   "0" - so columns of numbers would not line up and the countdown would twitch on
   every tick. Azeret was picked over the usual code monos (JetBrains, Roboto)
   because its zero is plain rather than slashed, which suits a scoreboard. */
@font-face {
  font-family: 'Azeret Mono';
  src: url('/fonts/azeret-mono-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  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;
}
@font-face {
  font-family: 'Azeret Mono';
  src: url('/fonts/azeret-mono-500-latin-ext.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  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;
}
@font-face {
  font-family: 'Azeret Mono';
  src: url('/fonts/azeret-mono-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  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;
}
@font-face {
  font-family: 'Azeret Mono';
  src: url('/fonts/azeret-mono-700-latin-ext.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  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;
}

/* --- Tokens --------------------------------------------------------------- */

:root {
  --accent: #680ef9;
  --accent-bright: #8b4dff;
  --accent-soft: #a87bff;
  --accent-ink: #d7c4ff;
  --accent-glow: rgba(104, 14, 249, 0.55);

  /*
   * Every one of these sits a step above where it started: the theme is still
   * near-black, but the old values were dark enough that the surfaces barely
   * separated from the page. The ladder bg -> chrome -> surface -> surface-2 ->
   * surface-3 is what gives the page its depth, so they move together.
   *
   * NOTE: .hero__grade hard-codes --bg as rgba() literals, because a gradient
   * stop cannot take an alpha of a var(). If --bg changes, change those too or
   * the hero stops meeting the page cleanly.
   */
  --bg: #0a0913;
  --bg-2: #0e0b1a;
  /* Header and footer. A step above --bg, roughly two thirds of the way to
     --surface, so the chrome reads as a frame around the page rather than more
     page - but not so far that it competes with the panels sitting on it. */
  --chrome: #110e1f;
  --surface: #151027;
  --surface-2: #1c1434;
  --surface-3: #241a41;

  /* Edges do a lot of the lifting on a dark theme - a border you can actually
     see reads as depth, where one you cannot reads as flat. */
  --line: rgba(255, 255, 255, 0.11);
  --line-strong: rgba(168, 123, 255, 0.36);

  --text: #edeaf6;
  --muted: #a49dbd;
  /* Both muted tones were lifted alongside the surfaces, by more than the
     surfaces moved, so contrast went up rather than down: --muted-dim is now
     4.65:1 at worst (on --surface-3), where it used to fall to 4.25:1 and miss
     WCAG AA for the small text it carries. */
  --muted-dim: #8b86a3;

  --gold: #ffce4a;
  --silver: #cdd5e5;
  --bronze: #e08a46;
  --up: #3ed598;
  --down: #ff5c7a;

  --font-display: 'Chakra Petch', 'Segoe UI', system-ui, sans-serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
    Arial, sans-serif;
  --font-wordmark: 'Rock Salt', 'Brush Script MT', cursive;
  /* Every number on the site. The fallbacks are all monospaced too, so digits
     stay aligned even before the webfont lands. */
  --font-mono: 'Azeret Mono', ui-monospace, 'SF Mono', 'Cascadia Mono',
    'Segoe UI Mono', Consolas, 'Liberation Mono', monospace;

  --radius: 14px;
  --radius-sm: 9px;
  --container: 1240px;
  --header-h: 68px;

  --shadow-lift: 0 18px 40px -22px rgba(0, 0, 0, 0.95);
  --shadow-glow: 0 0 32px -8px var(--accent-glow);
}

/* --- Base ----------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  /* `clip`, not `hidden`: `overflow-x: hidden` turns <body> into a scroll
     container, which breaks the sticky header and the sticky table head. */
  overflow-x: clip;
}

/* A faint purple haze so the near-black never reads as flat.
   Painted on a fixed layer rather than with background-attachment: fixed, which
   forces the whole page to repaint on every scroll frame. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(80rem 40rem at 50% -10rem, rgba(104, 14, 249, 0.2), transparent 65%),
    radial-gradient(50rem 30rem at 100% 40%, rgba(104, 14, 249, 0.1), transparent 70%);
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.15;
  margin: 0;
  letter-spacing: -0.01em;
}

p {
  margin: 0 0 1em;
}

a {
  color: var(--accent-ink);
  text-decoration-color: rgba(168, 123, 255, 0.4);
  text-underline-offset: 3px;
}

a:hover {
  color: #fff;
}

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

button {
  font: inherit;
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--accent-soft);
  outline-offset: 3px;
  border-radius: 4px;
}

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(16px, 4vw, 32px);
}

.stack > * + * {
  margin-top: clamp(28px, 4vw, 48px);
}

/*
 * The homepage's two-column band. Equal columns, and minmax(0, 1fr) rather than
 * a bare 1fr so a long word or a wide card cannot push a column past its share.
 */
.home-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(24px, 3.5vw, 40px);
  align-items: start;
}

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

.skip-link {
  position: absolute;
  left: 12px;
  top: -60px;
  z-index: 100;
  background: var(--accent);
  color: #fff;
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  transition: top 0.15s ease;
}

.skip-link:focus {
  top: 12px;
}

.accent {
  color: var(--accent-soft);
}

.eyebrow {
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  font-size: 0.75rem;
  color: var(--accent-soft);
  margin-bottom: 0.6em;
}

.section-title {
  font-size: clamp(1.2rem, 2.4vw, 1.55rem);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 0.9rem;
  display: flex;
  align-items: center;
  gap: 14px;
}

.section-title::after {
  content: '';
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--line-strong), transparent);
}

/* --- Buttons -------------------------------------------------------------- */

.btn {
  --btn-bg: var(--accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 11px 20px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--btn-bg);
  color: #fff;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.92rem;
  letter-spacing: 0.02em;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease,
    border-color 0.2s ease;
}

.btn:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: var(--shadow-glow);
  color: #fff;
}

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

.btn:disabled {
  opacity: 0.55;
  cursor: default;
}

.btn--discord {
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-bright) 100%);
  box-shadow: 0 0 0 1px rgba(168, 123, 255, 0.35) inset;
}

/* The header CTA is a secondary action next to the nav, not the hero of the
   page - keep it compact. Below 560px it drops its label entirely: the nav plus
   a full-width button does not fit, and the nav links were wrapping to two
   lines to make room. */
@media (max-width: 560px) {
  .site-header .btn--discord {
    padding: 8px;
    aspect-ratio: 1;
  }

  .site-header .btn--discord [data-discord-label] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .site-header .btn--discord .icon {
    width: 18px;
    height: 18px;
  }

  /* The hero's two watch buttons do the same thing as the header's Discord
     button above, and for the same reason: with their labels they take three
     buttons past one row and the third drops onto a line of its own. The label
     goes to the accessibility tree rather than out of the markup, so the link
     is still "Watch on Twitch" to a screen reader and to a search engine. */
  .hero__watch {
    padding-inline: 11px;
  }

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

.site-header .btn--discord {
  padding: 7px 13px;
  font-size: 0.8rem;
  border-radius: 8px;
}

.site-header .btn--discord .icon {
  width: 15px;
  height: 15px;
}

.btn--ghost {
  background: rgba(255, 255, 255, 0.04);
  border-color: var(--line);
  color: var(--text);
}

.btn--ghost:hover {
  border-color: var(--line-strong);
  background: rgba(104, 14, 249, 0.14);
}

.btn--sm {
  padding: 7px 14px;
  font-size: 0.82rem;
}

.btn--block {
  width: 100%;
  margin-top: 12px;
}

.btn .icon {
  width: 18px;
  height: 18px;
  fill: currentColor;
  flex: none;
}

.btn.is-busy {
  pointer-events: none;
  opacity: 0.75;
}

.linklike {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--accent-ink);
  font: inherit;
}

.linklike:hover {
  color: #fff;
}

/* --- Header --------------------------------------------------------------- */

/* Scrolls away with the page rather than following it down. Nothing passes
   underneath it any more, so the translucent background and its blur are gone
   too - they only ever existed to soften what was sliding past. */
.site-header {
  position: relative;
  z-index: 50;
  height: var(--header-h);
  background: var(--chrome);
  border-bottom: 1px solid var(--line);
}

.site-header__inner {
  height: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(16px, 4vw, 32px);
  display: flex;
  align-items: center;
  gap: clamp(16px, 4vw, 40px);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  text-decoration: none;
  color: var(--text);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.28rem;
  letter-spacing: -0.01em;
}

/* The logo is generated as a transparent cutout by scripts/build-assets.mjs, so
   it needs no blend-mode trickery to sit on the dark page. */
.brand__mark,
.hero__logo {
  border-radius: 50%;
}

.brand__tm {
  color: var(--accent-soft);
}

.site-nav {
  display: flex;
  gap: clamp(14px, 3vw, 28px);
}

/* Holds the right edge on its own, so the header still lays out correctly with
   no nav at all - which is how it currently renders (see NAV_LINKS in
   views/layout.tsx). */
.site-header__cta {
  margin-left: auto;
}

.site-nav a {
  position: relative;
  color: var(--muted);
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.92rem;
  padding: 6px 0;
  white-space: nowrap;
  transition: color 0.18s ease;
}

.site-nav a:hover {
  color: var(--text);
}

.site-nav a.is-active {
  color: var(--text);
}

.site-nav a.is-active::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 2px;
  background: var(--accent);
  box-shadow: 0 0 12px var(--accent-glow);
}

/* --- Hero ----------------------------------------------------------------- */

.hero {
  position: relative;
  min-height: min(78vh, 720px);
  display: grid;
  place-items: center;
  padding: clamp(20px, 4vw, 44px) 0 clamp(56px, 8vw, 88px);
  overflow: hidden;
  isolation: isolate;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
  pointer-events: none;
}

.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 50%;
  /* Lifted well past a gentle nudge so the scene actually reads as a place
     rather than a dark texture. Contrast is pushed with it: brightness alone at
     this strength washes the blacks out, and the purple screen layer above is
     already raising them. Everything that sits over this carries its own shade
     - .hero__grade, and the text shadows on the brand, lede and strip - so the
     copy stays legible against the brighter parts of the image. */
  filter: brightness(1.72) contrast(1.12) saturate(1.12);
}

/*
 * Much lighter than a daytime photo would need: the scene is already nearly
 * black, so this only has to settle the headline and hand the bottom edge over
 * to the page.
 *
 * The bottom of the source measures RGB 0,7,11 against a page background of
 * 7,6,13, so the fade can start late and stay short - the last stop is literally
 * var(--bg) at 100%, which makes the join to the page exact by construction.
 */
.hero__grade {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(48% 46% at 50% 46%, rgba(10, 9, 19, 0.38), transparent 76%),
    linear-gradient(180deg, rgba(10, 9, 19, 0.5) 0%, rgba(10, 9, 19, 0) 30%, rgba(10, 9, 19, 0) 68%, rgba(10, 9, 19, 0.55) 88%, var(--bg) 100%);
}

/* Pushes the scene toward the brand purple without darkening it.
   The source leans teal (mean green 16, blue 23 against red 3), so this carries
   more of the brand purple than it did over the old daytime shot. */
.hero__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(78% 62% at 50% 42%, rgba(139, 77, 255, 0.2), transparent 74%);
  mix-blend-mode: screen;
}

.hero__grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(168, 123, 255, 0.08) 1px, transparent 1px),
    linear-gradient(90deg, rgba(168, 123, 255, 0.08) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(70% 60% at 50% 45%, #000 0%, transparent 75%);
  -webkit-mask-image: radial-gradient(70% 60% at 50% 45%, #000 0%, transparent 75%);
  opacity: 0.55;
}

.hero__content {
  position: relative;
  text-align: center;
  padding-inline: clamp(16px, 4vw, 32px);
  max-width: 900px;
}

/*
 * A pool of shade behind the hero copy.
 *
 * The scene has genuinely bright patches - the car's lights, the signage - and
 * white text crossing one of them stops being readable however heavy its text
 * shadow is. Darkening the photo itself would undo the whole point of lifting
 * its brightness, so this only shades where the words actually are.
 *
 * A pseudo-element rather than a background on .hero__content, because the
 * shade has to spill past the text to fade out, and that box hugs the text
 * exactly. Negative inset percentages resolve against .hero__content's own box,
 * so the spill stays proportional as the hero grows and shrinks, and being
 * absolutely positioned it adds nothing to the layout.
 *
 * z-index -1 sits above .hero__media (-2) and below the copy, which is in
 * normal flow. .hero's isolation: isolate is what keeps both out of the page's
 * stacking context.
 *
 * Only the homepage uses it, and there it sits on the lockup rather than the
 * whole content box: that hero is two columns and only one of them is loose
 * text, so shading the full width would dim the photo behind the cup card for
 * nothing. The cup page needs a different shape of shade entirely - see
 * .hero--cup::after below.
 */
.hero__content--split .hero__lockup::before {
  content: '';
  position: absolute;
  /* Reaches well past the text on every side. The shade has to arrive at
     nothing before it runs out of room, or its own oval edge becomes a shape
     you can see on the photo - which is the whole thing it exists to avoid. */
  inset: -42% -16%;
  z-index: -1;
  pointer-events: none;
  /* Seven stops rather than four, spread to the corner: the falloff is what
     hides the edge, so it is drawn out over the full radius instead of
     dropping to transparent at 88% and leaving a rim. */
  background: radial-gradient(
    ellipse at 50% 50%,
    rgba(7, 6, 13, 0.86) 0%,
    rgba(7, 6, 13, 0.78) 26%,
    rgba(7, 6, 13, 0.6) 44%,
    rgba(7, 6, 13, 0.38) 60%,
    rgba(7, 6, 13, 0.18) 76%,
    rgba(7, 6, 13, 0.06) 90%,
    transparent 100%
  );
}

.hero__brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.9rem, 5.5vw, 3.2rem);
  letter-spacing: -0.02em;
  margin-bottom: 0.1em;
  /* Three layers, and the tight one matters most: a wide soft shadow lifts the
     letters off a busy photo, but only a shadow close to the glyph edge keeps
     the counters readable where a headlight lands behind them. */
  text-shadow:
    0 4px 24px rgba(0, 0, 0, 0.9),
    0 2px 8px rgba(7, 6, 13, 0.85),
    0 1px 2px rgba(7, 6, 13, 0.9);
}

.hero__logo {
  width: clamp(48px, 8vw, 72px);
  height: auto;
}

.hero__title {
  position: relative;
  margin: 0.1em 0 0.55em;
}

.hero__crown {
  display: block;
  width: clamp(34px, 5vw, 48px);
  /* No bottom margin: the wordmark's own negative top margin sets the spacing,
     so the two stay in step as the wordmark scales. */
  margin: 0 auto;
  fill: var(--accent-soft);
  filter: drop-shadow(0 0 14px var(--accent-glow));
}

/* Rock Salt is a light handwritten face, so it gets tightened up and given a
   layered glow to hold its own at display size. */
/* Rock Salt has unusually tall ascenders and deep descenders, and the skew
   throws glyphs past the edges of the box. Because the gradient is clipped to
   the text, any glyph outside that box gets cut off - hence the generous
   line-height and padding rather than the usual tight display sizing. */
.wordmark {
  display: block;
  font-family: var(--font-wordmark);
  font-weight: 400;
  font-size: clamp(1.85rem, 6.8vw, 4.6rem);
  line-height: 2.25;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  /* Applied right to left: skew first for the italic lean, then tilt the whole
     lockup. */
  transform: rotate(-3deg) skewX(-6deg);
  background: linear-gradient(180deg, #ffffff 0%, #e6d9ff 45%, var(--accent-soft) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter:
    drop-shadow(0 0 18px rgba(139, 77, 255, 0.55))
    drop-shadow(0 4px 18px rgba(0, 0, 0, 0.7));
  padding: 0.02em 0.16em;
  /*
   * The tall line box exists for the glyphs, not for spacing: measured at this
   * face, roughly 0.38em above the line box is dead air before the ink starts.
   * The top margin claws most of that back so the crown tucks in close. The
   * bottom claws back nothing at all, on purpose: the -3deg tilt drops the left
   * end of the wordmark by around 18px at full size, and that swallows the
   * clearance the old negative bottom margin used to be able to afford.
   * None of this touches the padding box the text-clipped gradient depends on.
   */
  margin: -0.28em -0.16em 0;
}

.hero__strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, 2.5vw, 20px);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(0.78rem, 1.9vw, 0.98rem);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: #cfc7e4;
  margin-bottom: clamp(28px, 4vw, 44px);
  text-shadow: 0 2px 10px rgba(7, 6, 13, 0.9), 0 0 22px rgba(7, 6, 13, 0.7);
  /*
   * A soft pool of shade so the line holds up over the brighter parts of the
   * scene. Painted as the element's own background rather than a positioned
   * pseudo-element: the background box sits behind the text automatically, with
   * no stacking-order games, and vertical-only padding means it cannot push the
   * items into wrapping on a narrow screen.
   */
  padding-block: 0.7em;
  /* Lighter than it was: .hero__content::before now lays a scrim under the
     whole lockup, and at the old strength the two stacked into a dark band. */
  background: radial-gradient(
    ellipse 55% 140% at 50% 50%,
    rgba(7, 6, 13, 0.38) 0%,
    rgba(7, 6, 13, 0.2) 52%,
    transparent 78%
  );
}

.hero__strip strong {
  color: #fff;
  font-weight: 700;
}

.hero__strip-sep {
  color: var(--accent);
  opacity: 0.7;
}

/*
 * The homepage hero.
 *
 * Same scene and the same treatment, but shorter and with the brand lockup
 * promoted to the h1: the landing page has nothing to count down to, so the
 * hero only has to introduce Carta and then get out of the way of the page.
 */
/* Room to breathe on both sides of the lockup, and more of it underneath: the
   page's first heading begins the moment this section ends, so the bottom
   padding is the only thing between the hero and the content below it. */
.hero--home {
  min-height: min(48vh, 440px);
  padding-top: clamp(28px, 4.5vw, 58px);
  padding-bottom: clamp(52px, 7.5vw, 96px);
}

/*
 * The cup hero's shade.
 *
 * A band across the whole hero rather than a pool behind the copy, because
 * that page's content column is tall and narrow - an ellipse sized to it has
 * to cross the photo somewhere, and however far the falloff is stretched its
 * edge stays visible as a shape on the scene. A band has no side edges to see
 * at all: it runs off both ends of the hero, and only fades vertically, over
 * about a sixth of the height at each end.
 *
 * Its own element rather than another layer on .hero__grade, which would mean
 * repeating that rule's two gradients to add a third.
 */
.hero--cup::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(7, 6, 13, 0) 0%,
    rgba(7, 6, 13, 0.42) 14%,
    rgba(7, 6, 13, 0.66) 38%,
    rgba(7, 6, 13, 0.66) 62%,
    rgba(7, 6, 13, 0.45) 82%,
    rgba(7, 6, 13, 0.15) 95%,
    rgba(7, 6, 13, 0) 100%
  );
}

/*
 * The homepage hero: the brand lockup on the left, the cup featured on the
 * right. Wider than the centred single column it replaces, because it now has
 * two things to hold - and the scrim behind it (.hero__content::before) is
 * sized off this box, so it grows with it.
 */
.hero__lockup {
  position: relative;
}

.hero__content--split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 360px);
  align-items: center;
  gap: clamp(24px, 4vw, 56px);
  width: 100%;
  max-width: var(--container);
  text-align: left;
}

/* Left of the split, the lockup stops being a centred column and lines up on
   its own left edge - including the two things that were centring themselves
   inside it. */
.hero__content--split .hero__brand,
.hero__content--split .hero__actions {
  justify-content: flex-start;
}

.hero__content--split .hero__lede {
  margin-inline: 0;
}

/* The featured card, in the hero rather than under it. Tighter than the panel
   it grew out of: no blurb, a smaller wordmark, and padding cut back, so it
   reads as a badge for the cup beside the brand rather than a second hero. */
.hero__content--split .cup-panel {
  padding: clamp(20px, 2.2vw, 26px) clamp(18px, 2vw, 24px);
  gap: clamp(14px, 1.6vw, 18px);
  box-shadow: var(--shadow-lift);
}

.hero__brand--title {
  margin: 0 0 0.2em;
  font-size: clamp(2.1rem, 6.2vw, 3.6rem);
}

.hero--home .hero__logo {
  width: clamp(56px, 10vw, 96px);
}

/*
 * The hero's two outline buttons sit straight on the photo, where the usual
 * 4%-white fill leaves the label competing with whatever is behind it. Here
 * they are filled with the page's own near-black instead, so they read as
 * buttons at any point of the scene - and they still darken rather than
 * lighten on hover, which is what keeps them secondary to the Discord one.
 */
.hero__actions .btn--ghost {
  background: rgba(7, 6, 13, 0.62);
  border-color: rgba(255, 255, 255, 0.18);
  backdrop-filter: blur(2px);
}

.hero__actions .btn--ghost:hover {
  background: rgba(7, 6, 13, 0.78);
  border-color: var(--line-strong);
}

/* Wide enough to hold the tagline on one line at desktop, and letter-spaced
   less than the cup page's strip because it is carrying a longer string. */
.hero__lede {
  max-width: 56ch;
  margin-inline: auto;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(0.85rem, 2.1vw, 1.05rem);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: #efeafd;
  text-shadow:
    0 2px 10px rgba(7, 6, 13, 0.98),
    0 0 24px rgba(7, 6, 13, 0.92),
    0 1px 2px rgba(7, 6, 13, 0.95);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin-top: clamp(20px, 3vw, 30px);
}

/*
 * The hero's sign-up bar.
 *
 * Sits above the countdown: the clock is the thing that holds still on the
 * page while the sign-up is the thing to act on, so the order reads what this
 * is, how to be in it, and how long is left. One anchor rather than a line of
 * text beside a button - the whole bar is the target, which is what makes it
 * work on a phone.
 */
.hero-register {
  position: relative;
  /* Block-level flex, not inline-flex: the countdown above it is an
     inline-block, and two inline boxes in a centred column would share a line
     and read as one lopsided widget. */
  display: flex;
  width: fit-content;
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px 18px;
  margin: 0 auto clamp(18px, 2.5vw, 24px);
  padding: 13px 15px 13px 24px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: linear-gradient(180deg, rgba(23, 16, 41, 0.92), rgba(11, 8, 19, 0.94));
  box-shadow: var(--shadow-lift);
  color: var(--text);
  text-decoration: none;
  font-size: 1.02rem;
  transition:
    border-color 0.2s ease,
    box-shadow 0.2s ease,
    transform 0.2s ease;
}

.hero-register:hover {
  border-color: var(--accent-soft);
  box-shadow: var(--shadow-lift), var(--shadow-glow);
  transform: translateY(-1px);
}

.hero-register__count strong {
  font-family: var(--font-mono);
  font-size: 1.3rem;
  color: #fff;
}

/* The pill inside the pill: the bar is the hit area, but the eye still wants
   something button-shaped to aim at. */
.hero-register__cta {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 18px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.hero-register__cta svg {
  width: 15px;
  height: 15px;
  flex: none;
  transition: transform 0.2s ease;
}

.hero-register:hover .hero-register__cta svg {
  transform: translateX(3px);
}

/* --- Countdown ------------------------------------------------------------ */

.countdown {
  position: relative;
  display: inline-block;
  padding: clamp(18px, 3vw, 26px) clamp(20px, 4vw, 40px) clamp(16px, 2.5vw, 22px);
  background: linear-gradient(180deg, rgba(23, 16, 41, 0.92), rgba(11, 8, 19, 0.94));
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift), inset 0 1px 0 rgba(255, 255, 255, 0.05);
  overflow: hidden;
  /* Motorsport-livery corner cuts. Shared with the ::after outline below, so
     the two shapes can never drift apart. */
  --cut: 18px;
  /* Where the outline's inner edge meets the top and left sides. A 45 degree
     edge pushed 1px along its own normal travels sqrt(2)px on each axis, so the
     inner corner starts (sqrt(2) - 1)px further out than the outer one - that is
     what keeps the diagonal exactly as thick as the straight sides. */
  --cut-inner: calc(var(--cut) + 0.4142px);
  clip-path: polygon(
    var(--cut) 0,
    100% 0,
    100% calc(100% - var(--cut)),
    calc(100% - var(--cut)) 100%,
    0 100%,
    0 var(--cut)
  );
}

/*
 * The border, carried around the cut corners.
 *
 * `border` alone only ever reaches the four straight sides: the box is painted
 * first and clip-path slices it afterwards, taking the border off with the
 * corners and leaving the two diagonals bare. This traces the same hexagon as a
 * 1px ring - the outer path, then the same path 1px in, with the even-odd fill
 * rule keeping only the band between them.
 *
 * The two clips on the parent then divide the work, which is worth knowing
 * before changing either:
 *
 *   - `overflow: hidden` clips to the PADDING box, so the ring's straight sides
 *     fall outside it and are trimmed. The real `border` draws those, and this
 *     ring only ever shows on the diagonals - which is all it is needed for.
 *   - that same clip is rounded by `border-radius`, which is what stops this
 *     polygon's square top-right and bottom-left corners poking out past the
 *     curve. Do not drop `overflow: hidden` on the strength of clip-path
 *     already containing the sweep: those corners reappear if you do.
 *
 * inset -1px lines the ring up with the border box rather than the padding box,
 * so its diagonals sit flush against the cut edge instead of 1px inside it.
 */
.countdown::after {
  content: '';
  position: absolute;
  inset: -1px;
  pointer-events: none;
  background: var(--line-strong);
  clip-path: polygon(
    evenodd,
    var(--cut) 0,
    100% 0,
    100% calc(100% - var(--cut)),
    calc(100% - var(--cut)) 100%,
    0 100%,
    0 var(--cut),
    var(--cut-inner) 1px,
    calc(100% - 1px) 1px,
    calc(100% - 1px) calc(100% - var(--cut-inner)),
    calc(100% - var(--cut-inner)) calc(100% - 1px),
    1px calc(100% - 1px),
    1px var(--cut-inner)
  );
}

/* Slow accent sweep behind the digits. */
.countdown::before {
  content: '';
  position: absolute;
  inset: -40% -10%;
  background: linear-gradient(115deg, transparent 42%, rgba(139, 77, 255, 0.16) 50%, transparent 58%);
  animation: sweep 7s linear infinite;
  pointer-events: none;
}

@keyframes sweep {
  from {
    transform: translateX(-60%);
  }
  to {
    transform: translateX(60%);
  }
}

/*
 * "Qualifications Started/Finished" - the hour-long banner over the clock.
 *
 * Deliberately the loudest thing in the box: it is the only element here that
 * is news, and it is gone an hour later. Sized in the display face against the
 * digits below it, which are 2.4rem at the same breakpoints.
 */
.countdown__announcement {
  position: relative;
  margin: 0 0 0.7em;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.35rem, 4.2vw, 2rem);
  line-height: 1.05;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 0 24px rgba(139, 77, 255, 0.55);
}

/* --- Twitch stream, in the countdown's place ------------------------------ */

.hero-stream {
  position: relative;
  width: min(880px, 100%);
  margin-inline: auto;
}

.hero-stream__label {
  margin: 0 0 0.7em;
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.24em;
  font-size: 0.72rem;
  color: var(--muted);
}

/* Fixed 16:9 so the box does not jump about while the player loads. */
.hero-stream__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  overflow: hidden;
  background: #0b0813;
  box-shadow: var(--shadow-lift);
}

.hero-stream__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.hero-stream__fallback {
  margin: 10px 0 0;
  font-size: 0.8rem;
  color: var(--muted-dim);
}

.hero-stream__fallback a {
  color: var(--accent-soft);
}

/* --- Final podium --------------------------------------------------------- */

.podium {
  position: relative;
  width: min(620px, 100%);
  margin-inline: auto;
}

/*
 * The podium's heading, and the largest thing the hero ever shows. Deliberately
 * a step above the hour-long announcement banner: that one is news mid-event,
 * this one closes the tournament out, and it has the slot to itself.
 */
.podium__title {
  margin: 0 0 0.8em;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.7rem, 5.4vw, 2.6rem);
  line-height: 1.02;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 0 26px rgba(139, 77, 255, 0.55);
}

.podium__label {
  margin: 0 0 0.35em;
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.24em;
  font-size: 0.72rem;
  color: var(--muted);
}

.podium__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 10px;
}

.podium__place {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  background: linear-gradient(180deg, rgba(23, 16, 41, 0.92), rgba(11, 8, 19, 0.94));
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--radius-sm);
}

.podium__place--1 {
  border-left-color: var(--gold);
}

.podium__place--2 {
  border-left-color: var(--silver);
}

.podium__place--3 {
  border-left-color: var(--bronze);
}

.podium__rank {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  min-width: 3ch;
}

.podium__place--1 .podium__rank {
  color: var(--gold);
}

.podium__place--2 .podium__rank {
  color: var(--silver);
}

.podium__place--3 .podium__rank {
  color: var(--bronze);
}

/*
 * The prize, pushed to the far end of the row. Mono so the amounts line up
 * down the column the way the leaderboard's numbers do, and in the medal colour
 * of the place it belongs to (set below) so it reads as part of that row.
 */
.podium__prize {
  margin-left: auto;
  padding-left: 12px;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: -0.02em;
  color: var(--muted);
  white-space: nowrap;
}

.podium__place--1 .podium__prize {
  color: var(--gold);
  font-size: 1.1rem;
}

.podium__place--2 .podium__prize {
  color: var(--silver);
}

.podium__place--3 .podium__prize {
  color: var(--bronze);
}

/* The winner is the headline; the other two sit a size down. */
.podium__name {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--text);
  min-width: 0;
}

.podium__place--1 .podium__name {
  font-size: clamp(1.2rem, 3.2vw, 1.6rem);
  color: #fff;
}

.countdown__label {
  position: relative;
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.24em;
  font-size: 0.72rem;
  color: var(--muted);
  margin: 0 0 0.9em;
}

.countdown__units {
  position: relative;
  display: flex;
  gap: clamp(8px, 1.8vw, 14px);
  justify-content: center;
}

.countdown__unit {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  min-width: clamp(62px, 12vw, 86px);
}

.countdown__value {
  display: block;
  width: 100%;
  padding: clamp(8px, 1.6vw, 14px) 4px;
  background: linear-gradient(180deg, var(--surface-3), var(--surface));
  border: 1px solid var(--line);
  border-top: 2px solid var(--accent);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: clamp(1.55rem, 4.5vw, 2.45rem);
  line-height: 1;
  color: #fff;
  text-shadow: 0 0 20px rgba(139, 77, 255, 0.5);
  box-shadow: inset 0 -12px 24px -18px var(--accent), 0 6px 18px -12px #000;
}

/* Pulse on each tick, driven by countdown.js toggling the class. */
.countdown__value.is-tick {
  animation: tick 0.42s ease-out;
}

@keyframes tick {
  0% {
    transform: translateY(-2px);
    box-shadow: inset 0 -12px 24px -14px var(--accent-bright), 0 0 22px -4px var(--accent-glow);
  }
  100% {
    transform: translateY(0);
  }
}

.countdown__unit-label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--muted-dim);
}

.countdown__date {
  position: relative;
  margin: 1.1em 0 0;
  font-size: 0.82rem;
  color: var(--muted);
}

/* --- Tabs ----------------------------------------------------------------- */

.tabs {
  margin-top: clamp(-28px, -3vw, -12px);
  position: relative;
  z-index: 2;
  padding-bottom: clamp(56px, 9vw, 100px);
}

.tabs__list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 6px;
  box-shadow: var(--shadow-lift);
}

.tabs__tab {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: clamp(12px, 2vw, 17px) 10px;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(0.72rem, 1.7vw, 0.9rem);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  cursor: pointer;
  position: relative;
  transition: color 0.18s ease, background 0.18s ease;
}

.tabs__tab:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.03);
}

.tabs__tab.is-active {
  color: #fff;
  background: linear-gradient(180deg, rgba(104, 14, 249, 0.22), rgba(104, 14, 249, 0.06));
  box-shadow: inset 0 0 0 1px var(--line-strong);
}

.tabs__tab.is-active::after {
  content: '';
  position: absolute;
  left: 18%;
  right: 18%;
  bottom: 4px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
  box-shadow: 0 0 14px var(--accent-glow);
}

.tabs__icon svg {
  width: 19px;
  height: 19px;
  fill: currentColor;
  display: block;
}

.tabs__panel {
  margin-top: 22px;
}

/* Without JS every panel stays visible, so nothing is trapped behind a script. */
.tabs[data-enhanced] .tabs__panel {
  display: none;
}

.tabs[data-enhanced] .tabs__panel.is-active {
  display: block;
  animation: panel-in 0.3s ease both;
}

@keyframes panel-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* --- Panels --------------------------------------------------------------- */

.panel {
  background: linear-gradient(180deg, var(--surface) 0%, rgba(11, 8, 19, 0.92) 100%);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(20px, 3.5vw, 34px);
  box-shadow: var(--shadow-lift);
}

.panel__head {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 22px;
}

.panel__title {
  display: flex;
  align-items: center;
  gap: 13px;
  font-size: clamp(1.4rem, 3.4vw, 2rem);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.panel__icon {
  width: clamp(28px, 4vw, 36px);
  height: auto;
  fill: var(--accent-soft);
  filter: drop-shadow(0 0 12px var(--accent-glow));
  flex: none;
}

.panel__subtitle {
  color: var(--muted);
  margin: 0.5em 0 0;
  /* No measure cap: these are single-sentence intros and a cap was forcing them
     onto a second line well before the container ran out of room. The container
     width is the only limit they need. */
  max-width: none;
}

/*
 * A second line under a subtitle, for a date or a range that is a caption on
 * the sentence rather than part of it. Dimmer and a touch smaller so the two
 * lines read as heading and detail rather than as one paragraph that wrapped.
 */
.panel__subtitle-line {
  display: block;
  margin-top: 3px;
  font-size: 0.85em;
  color: var(--muted-dim);
}

/*
 * In .panel__head the subtitle is the right-hand item of a space-between row,
 * so the panel's edge is right beside it and the caption was hanging off the
 * left of its own sentence with all the space on the other side. Pushed over
 * to meet that edge, the way .lb-status does in the same slot.
 *
 * Only the caption, not the whole subtitle: the sentence above it is prose, and
 * one long enough to wrap would go ragged-left if it were right-aligned too.
 */
.panel__head .panel__subtitle-line {
  text-align: right;
}


.notice {
  padding: 13px 16px;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.9rem;
  margin-bottom: 18px;
}


.empty {
  padding: 40px 20px;
  text-align: center;
  color: var(--muted);
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
}

.empty--sm {
  padding: 18px;
  font-size: 0.88rem;
}

/* --- Leaderboard controls ------------------------------------------------- */

.lb-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}

.lb-search {
  position: relative;
  flex: 1 1 240px;
  max-width: 340px;
}

.lb-search svg {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 17px;
  height: 17px;
  fill: var(--muted-dim);
  pointer-events: none;
}

.lb-search input {
  width: 100%;
  padding: 10px 14px 10px 38px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  font-size: 0.92rem;
}

.lb-search input::placeholder {
  color: var(--muted-dim);
}

.lb-search input:focus {
  border-color: var(--line-strong);
  outline: none;
  box-shadow: 0 0 0 3px rgba(104, 14, 249, 0.18);
}

/* Hugs the search box; the view toggle, when there is one, keeps the far end. */
.lb-filter {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-right: auto;
  color: var(--muted);
  font-size: 0.88rem;
  cursor: pointer;
  user-select: none;
}

.lb-filter[hidden] {
  display: none;
}

.lb-filter:hover {
  color: var(--text);
}

.lb-filter input {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.segmented {
  display: inline-flex;
  padding: 4px;
  gap: 4px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

.segmented button {
  padding: 7px 15px;
  background: transparent;
  border: 0;
  border-radius: 6px;
  color: var(--muted);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.82rem;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease;
}

.segmented button:hover {
  color: var(--text);
}

.segmented button.is-active {
  background: var(--accent);
  color: #fff;
  box-shadow: 0 0 18px -6px var(--accent-glow);
}

/* Sits opposite the heading in .panel__head, so it reads as a status stamp on
   the box rather than a caption above the table. On narrow screens .panel__head
   turns into a column and this simply wraps underneath. */
.lb-status {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  /* This is a flex row of text runs, so the literal spaces in the markup are
     stripped at each item boundary and `gap` is what separates the words. Keep
     it at a word space (~3.6px for Inter at this size) or the line reads as if
     it has been double-spaced. The dot buys its own wider gap below.

     Row gap is set separately: when this wraps on a narrow screen the lines
     want more air between them than the words want between each other. */
  gap: 6px 4px;
  flex-wrap: wrap;
  font-size: 0.82rem;
  color: var(--muted-dim);
  margin: 0;
  /* Nudged down to sit level with the heading's cap height. */
  padding-top: 8px;
  text-align: right;
}

/* The status line reads as a single state - "Live", "Paused", "Final" - with
   "Updated 5 minutes ago / Next update in 3 minutes" behind a hover. The dotted
   underline and the help cursor are the whole affordance, so keep both. */
.lb-live {
  position: relative;
  border-bottom: 1px dotted currentColor;
  cursor: help;
}

/* Focusable so the detail is reachable without a mouse: tab to it on a
   keyboard, tap it on a touch screen, where :hover never fires. */
.lb-live:focus-visible {
  outline: 2px solid var(--up);
  outline-offset: 3px;
  border-radius: 2px;
}

.lb-live::after {
  content: attr(data-tip);
  position: absolute;
  /* Below rather than above: the line sits at the top of the panel, level with
     the heading, and there is nothing but border up there to open into. */
  top: calc(100% + 9px);
  right: 0;
  z-index: 5;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  box-shadow: var(--shadow-lift);
  color: var(--muted);
  font-size: 0.78rem;
  white-space: nowrap;
  /* visibility, not just opacity, so the hidden tooltip cannot be hovered or
     selected. It snaps while the opacity fades, which is what we want. */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.12s ease;
}

.lb-live:hover::after,
.lb-live:focus-visible::after {
  opacity: 1;
  visibility: visible;
}

.pulse {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--up);
  box-shadow: 0 0 0 0 rgba(62, 213, 152, 0.6);
  animation: pulse 2.4s ease-out infinite;
  flex: none;
  /* On top of .lb-status's word-space gap: the dot is an indicator rather than
     a word, so it wants more air than the gap between "Updated" and the time. */
  margin-right: 3px;
}

/* A board that is not refreshing should not have a heartbeat. Must come AFTER
   .pulse: same specificity, so source order is what decides the animation. */
.pulse--idle {
  background: var(--muted-dim);
  box-shadow: none;
  animation: none;
}

@keyframes pulse {
  0% {
    box-shadow: 0 0 0 0 rgba(62, 213, 152, 0.5);
  }
  70% {
    box-shadow: 0 0 0 8px rgba(62, 213, 152, 0);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(62, 213, 152, 0);
  }
}

/* --- Leaderboard table ---------------------------------------------------- */

.lb-scroll {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-2);
}

.lb {
  width: 100%;
  border-collapse: collapse;
  min-width: 560px;
}

.lb thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface-2);
  padding: 10px 16px;
  text-align: left;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

.lb__th-rank {
  width: 118px;
}

/* `.lb thead th` sets text-align: left and outspecifies a bare class, so these
   have to be qualified or the headings sit over the wrong edge of the numbers. */
.lb thead th.lb__th-time,
.lb thead th.lb__th-points {
  text-align: right;
}

.lb__th-points {
  width: 116px;
}

/*
 * Row height is set here and matched by .lb__rank below, which is a flex box
 * filling the cell - change one and the rank column stops lining up with the
 * rest of the row. 100 rows to a page makes this the single biggest lever on
 * how long the board runs.
 */
.lb tbody td {
  padding: 0 16px;
  height: 44px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.045);
  vertical-align: middle;
}

.lb__row {
  position: relative;
  /* The whole row toggles the per-track splits, not just the chevron. */
  cursor: pointer;
  transition: background 0.15s ease;
}

.lb__row::after {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  background: var(--accent);
  transition: width 0.18s ease;
}

.lb__row:hover {
  background: rgba(104, 14, 249, 0.08);
}

.lb__row:hover::after,
.lb__row.is-open::after {
  width: 3px;
}

.lb__row.is-highlighted {
  background: rgba(104, 14, 249, 0.16);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}

.lb__rank {
  display: flex;
  align-items: center;
  gap: 9px;
  /* Matches .lb tbody td above. */
  height: 44px;
}

.rank {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-width: 30px;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--muted);
}

/* Podium places are marked by colour alone - see RankCell for why there are no
   crowns on a qualifying board. */
.rank--podium {
  font-size: 1.05rem;
}

.rank--1 {
  color: var(--gold);
  text-shadow: 0 0 12px rgba(255, 206, 74, 0.4);
}

.rank--2 {
  color: var(--silver);
}

.rank--3 {
  color: var(--bronze);
}

.lb__row.is-podium-1 {
  background: linear-gradient(90deg, rgba(255, 206, 74, 0.09), transparent 45%);
}

.lb__row.is-podium-2 {
  background: linear-gradient(90deg, rgba(205, 213, 229, 0.07), transparent 45%);
}

.lb__row.is-podium-3 {
  background: linear-gradient(90deg, rgba(224, 138, 70, 0.07), transparent 45%);
}

.move {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.65rem;
  letter-spacing: 0.02em;
  min-width: 30px;
}

.move--up {
  color: var(--up);
}

.move--down {
  color: var(--down);
}

.move--new {
  color: var(--accent-soft);
  font-size: 0.6rem;
  letter-spacing: 0.1em;
}

.lb__expand {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  color: var(--text);
}

/*
 * Flag and name on one line, for a player cell with no button to do it.
 *
 * An inner wrapper rather than flex on the <td> itself, and that distinction
 * matters more than it looks: a table cell with display:flex stops being a
 * table cell, and the browser wraps it - together with any ADJACENT non-cell -
 * in one anonymous cell. .lb__rank is already flex and gets away with it by
 * being the only one; making the cell next to it flex too merged the pair into
 * a single column and took the whole table down to two.
 */
.lb__who {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.lb__name {
  font-weight: 600;
  font-size: 0.98rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The registered tick. Sits straight after the name, ahead of the chevron. */
.lb__reg {
  display: inline-flex;
  flex: none;
  color: var(--up);
}

.lb__reg svg {
  width: 14px;
  height: 14px;
}

.lb__chevron {
  width: 14px;
  height: 14px;
  color: var(--muted-dim);
  flex: none;
  transition: transform 0.2s ease, color 0.2s ease;
}

.lb__row.is-open .lb__chevron {
  transform: rotate(180deg);
  color: var(--accent-soft);
}

.flag {
  width: 21px;
  height: 15px;
  border-radius: 2px;
  flex: none;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14);
  object-fit: cover;
}

.lb__time,
.lb__points {
  text-align: right;
  font-family: var(--font-mono);
  font-weight: 500;
  white-space: nowrap;
}

/* Points decide the standings, so they carry the emphasis; the total time is
   supporting context and is toned down to match. */
.lb__points {
  font-size: 0.98rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: #fff;
}

.lb__time {
  color: var(--muted-dim);
  font-size: 0.82rem;
  letter-spacing: -0.02em;
}

/* Shown when a player has not run every track, so there is no meaningful total. */
/*
 * The em-dash standing in for a total that would mislead. Quieter than a real
 * time, but it still has to be readable: this was --muted-dim at opacity 0.5,
 * which worked out at 1.78:1 against the row behind it.
 */
.lb__time--partial {
  color: var(--muted);
  cursor: help;
}

/*
 * "Secret" stands in for a time Nadeo withhold on the secret qualifier maps.
 * Set in the body face rather than the mono clock face - it is a word, not a
 * number, and letting it keep the tabular figures made it read as a broken
 * time. Muted, because it is the absence of a value, but not as faint as the
 * incomplete dash above: this row did run the track.
 */
.lb__time--secret,
.track-board__time--secret {
  font-family: var(--font-body);
  font-style: italic;
  letter-spacing: 0;
  color: var(--muted);
  cursor: help;
}

.lb__row.is-podium-1 .lb__points {
  color: var(--gold);
}

/* Expanded per-track splits.
   `.lb tbody td` is more specific than a bare `.lb__splits td`, so the padding
   here has to be qualified - otherwise the shorthand there wins and the last
   split ends up flush against the next player's row. Only the block padding is
   set, so the mobile rules keep control of the horizontal padding. */
.lb tbody tr.lb__splits td {
  padding-block: 0 18px;
  height: auto;
  background: rgba(0, 0, 0, 0.28);
}

/* The minimum track has to fit the whole row on one line: index chip + the
   longest "0:00.000 . #100" + the points figure + padding. Too narrow and the
   nowrap time/position text overflows its flex item and paints over the points
   (its own bounding box stays inside, so nothing visibly clips). */
.splits {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(272px, 1fr));
  gap: 8px;
  list-style: none;
  margin: 0;
  padding: 14px 0 0;
}

.splits__item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.015));
  border: 1px solid var(--line);
  border-radius: 8px;
}

/* A numbered chip reads as "track 1 of 5" far better than an accent stripe. */
.splits__index {
  flex: none;
  padding: 3px 6px;
  border-radius: 5px;
  background: rgba(104, 14, 249, 0.2);
  color: var(--accent-ink);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.64rem;
  line-height: 1.35;
}

.splits__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  /* Belt and braces: if this ever does get squeezed below the text width, clip
     rather than letting it run over the points. */
  overflow: hidden;
}

.splits__track {
  font-size: 0.74rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.splits__meta {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.8rem;
  letter-spacing: -0.02em;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.splits__points {
  flex: none;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.84rem;
  letter-spacing: -0.02em;
  color: var(--accent-soft);
  white-space: nowrap;
}

.splits__points small {
  font-size: 0.62rem;
  font-weight: 600;
  color: var(--muted-dim);
  margin-left: 3px;
}

/* A track the player never ran. Dimmed rather than hidden, so it is obvious
   which one is missing and what it cost them. */
.splits__item--missing {
  background: none;
  border-style: dashed;
  opacity: 0.55;
}

.splits__item--missing .splits__index {
  background: rgba(255, 255, 255, 0.06);
  color: var(--muted-dim);
}

.splits__item--missing .splits__meta {
  font-style: italic;
  font-weight: 500;
  color: var(--muted-dim);
}

.splits__item--missing .splits__points {
  color: var(--muted-dim);
}

/* Qualification cutoff divider */
.lb__cutoff td {
  height: auto;
  padding: 0;
  border-bottom: 0;
}

.lb__cutoff span {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 16px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.24em;
  color: var(--accent-soft);
  background: linear-gradient(90deg, rgba(104, 14, 249, 0.22), transparent);
  border-top: 1px dashed var(--accent);
  border-bottom: 1px dashed rgba(104, 14, 249, 0.35);
}

.lb__cutoff span::after {
  content: '';
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--line-strong), transparent);
}

/*
 * Note under the final row explaining the display limit. Deliberately quieter
 * than .lb__cutoff above: that divider marks a result, this one is an aside
 * about the table, and it should not compete with the qualification line.
 */
.lb__limit td {
  padding: 14px 16px;
  border-bottom: 0;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.82rem;
  line-height: 1.5;
  text-align: center;
  white-space: normal;
}

/* --- Pagination ----------------------------------------------------------- */

.lb-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding-top: 18px;
}

.pager {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 5px;
}

.pager button {
  min-width: 36px;
  height: 36px;
  padding: 0 9px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.84rem;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.pager button:hover:not(:disabled):not(.is-current) {
  color: var(--text);
  border-color: var(--line-strong);
}

.pager button:disabled {
  opacity: 0.35;
  cursor: default;
}

.pager__page.is-current {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  box-shadow: 0 0 18px -6px var(--accent-glow);
}

.pager__gap {
  align-self: center;
  color: var(--muted-dim);
  padding: 0 3px;
}

.lb-meta {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted-dim);
}

/* --- Tracks --------------------------------------------------------------- */

.tracks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
  gap: 18px;
}

.track {
  display: flex;
  flex-direction: column;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  transition: border-color 0.2s ease, transform 0.2s ease;
}

.track:hover {
  border-color: var(--line-strong);
  transform: translateY(-2px);
}

.track__media {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--surface-2);
  overflow: hidden;
}

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

.track__media::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, transparent 45%, rgba(10, 8, 19, 0.92) 100%);
}

.track__index {
  position: absolute;
  top: 10px;
  left: 12px;
  z-index: 1;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.5rem;
  color: rgba(255, 255, 255, 0.85);
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.8);
}

.track__body {
  padding: 15px 16px 17px;
  display: flex;
  flex-direction: column;
  flex: 1;
}

.track__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

.track__name {
  font-size: 1.12rem;
  text-transform: uppercase;
  letter-spacing: 0.01em;
}

.track__author {
  margin: 2px 0 0;
  font-size: 0.8rem;
  color: var(--muted-dim);
}

.track-board {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.track-board__row {
  display: grid;
  grid-template-columns: 26px 1fr auto auto;
  align-items: center;
  gap: 10px;
  padding: 7px 8px;
  border-radius: 5px;
  font-size: 0.88rem;
}

.track-board__row:nth-child(odd) {
  background: rgba(255, 255, 255, 0.025);
}

.track-board__pos {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.82rem;
  color: var(--muted-dim);
  text-align: right;
}

.track-board__row.is-podium-1 .track-board__pos {
  color: var(--gold);
}

.track-board__row.is-podium-2 .track-board__pos {
  color: var(--silver);
}

.track-board__row.is-podium-3 .track-board__pos {
  color: var(--bronze);
}

.track-board__player {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.track-board__player span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.track-board__time {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.8rem;
  letter-spacing: -0.02em;
  color: var(--muted);
}

.track-board__points {
  min-width: 52px;
  text-align: right;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: -0.02em;
  color: var(--accent-soft);
}

.track-board.is-expanded {
  max-height: 460px;
  overflow-y: auto;
  scrollbar-width: thin;
}

/* --- FAQ ------------------------------------------------------------------ */

.faq {
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.faq__item {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  transition: border-color 0.2s ease;
}

.faq__item[open] {
  border-color: var(--line-strong);
}

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 15px 18px;
  cursor: pointer;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1rem;
  list-style: none;
}

.faq__q::-webkit-details-marker {
  display: none;
}

.faq__q:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.02);
}

.faq__chevron {
  width: 16px;
  height: 16px;
  flex: none;
  color: var(--accent-soft);
  transition: transform 0.2s ease;
}

.faq__item[open] .faq__chevron {
  transform: rotate(180deg);
}

.faq__a {
  padding: 0 18px 18px;
  color: var(--muted);
  max-width: 72ch;
}

.faq__a p {
  margin-bottom: 0;
}

/* Answers can run to several paragraphs, so they need separating. */
.faq__a p + p {
  margin-top: 0.85em;
}

.faq__a .btn {
  margin-top: 14px;
}

/* The rules list: the FAQ answer and the registration form share it. */
.rules {
  margin: 0;
  padding-left: 1.2em;
  display: grid;
  gap: 0.4em;
}

.reg__rules {
  font-size: 0.88rem;
  color: var(--muted);
  line-height: 1.5;
}

.reg__rules .rules {
  margin-top: 0.5em;
}

/* --- Homepage ------------------------------------------------------------- */

.lede {
  max-width: 62ch;
  color: var(--muted);
  font-size: clamp(0.98rem, 2vw, 1.08rem);
}

/* The Wedding Cup panel.
   The one loud thing on an otherwise quiet page, so it gets the hero's own
   vocabulary - crown, wordmark - at panel size. Two columns: the billing on the
   left, the numbers and the call to action stacked in a stub on the right. */
.cup-panel {
  position: relative;
  overflow: hidden;
  display: grid;
  gap: clamp(20px, 2.5vw, 26px);
  padding: clamp(24px, 3vw, 34px) clamp(20px, 3vw, 38px);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background:
    radial-gradient(85% 130% at 18% 0%, rgba(104, 14, 249, 0.34), transparent 64%),
    linear-gradient(160deg, var(--surface-2), var(--bg-2));
  box-shadow: var(--shadow-lift);
}

/* Quieter while there is nowhere to go, so that flipping the switch on
   config.weddingCup.href visibly lights the panel up. */
.cup-panel--soon {
  background:
    radial-gradient(85% 130% at 18% 0%, rgba(104, 14, 249, 0.2), transparent 64%),
    linear-gradient(160deg, var(--surface), var(--bg-2));
}

/*
 * Decorative only. Unmasked, unlike the hero's: this one runs corner to corner,
 * and the panel's own overflow: hidden trims it to the rounded border.
 *
 * Do NOT give this a grid-area. The panel is a grid container, and an
 * absolutely positioned child with a definite grid placement has its containing
 * block set to that GRID AREA - which lives inside the content box, so inset: 0
 * would then stop a whole panel's padding short of the edge. Left with auto
 * placement, the containing block is the padding box and the grid reaches the
 * border, which is the point of it.
 *
 * pointer-events: none keeps the layer from swallowing clicks on the CTA.
 */
.cup-panel__grid {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(168, 123, 255, 0.085) 1px, transparent 1px),
    linear-gradient(90deg, rgba(168, 123, 255, 0.085) 1px, transparent 1px);
  background-size: 56px 56px;
}

/* Centred as a block of its own, which is what puts the crown over the middle
   of the wordmark - the crown is centred in this column, not against the panel.
   An explicit stacking peer too, so it sits above the grid rather than relying
   on paint order. */
.cup-panel__body {
  position: relative;
  text-align: center;
}

.cup-panel__crown {
  display: block;
  width: clamp(26px, 3.4vw, 34px);
  /* No bottom margin: the wordmark's negative top margin sets the spacing. */
  margin: 0 auto;
  fill: var(--accent-soft);
  filter: drop-shadow(0 0 14px var(--accent-glow));
}

.cup-panel__title {
  margin: 0;
}

/* The wordmark's own margins and padding are tuned to its glyph overhang, so
   only the size is touched here - except at the bottom: that clearance is
   sized for the tilt at hero scale, and at this size the left end drops far
   less, so most of it is dead air and comes back.

   The ceiling is what keeps "Wedding Cup" on one line in the hero's 360px
   column: measured, it wraps at 36px in a column that size and holds at 34.
   Once the hero stacks the card runs the full width and takes the bigger size
   with it. */
.cup-panel .wordmark {
  font-size: clamp(1.4rem, 2.2vw, 1.85rem);
  margin-bottom: -0.12em;
}

/* The right-hand stub. No rule between the columns: the grid behind the panel
   already draws vertical lines, and one more at an arbitrary x just read as a
   grid line that had landed in the wrong place. The gap separates them. */
.cup-panel__aside {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(16px, 2vw, 22px);
}

/* A strip, not a column: three short numbers reading across the foot of the
   card. They were a right-hand column back when the panel ran the full width
   of the page. */
.cup-facts {
  display: grid;
  grid-auto-flow: column;
  justify-content: center;
  gap: clamp(16px, 4vw, 32px);
}

/* Value stacked over label, so three short pairs read as a strip rather than
   six loose words. */
.cup-fact {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  gap: 2px;
}

.cup-fact__value {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.15rem;
  color: #fff;
  line-height: 1.2;
}

.cup-fact__label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--muted-dim);
  white-space: nowrap;
}

/* Deliberately not a button: there is nothing to press yet. The dashed edge is
   the tell - it reads as a placeholder rather than a disabled control, which
   would invite clicking. */
.cup-panel__status {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  margin: 0;
  padding: 11px 24px;
  border: 1px dashed var(--line-strong);
  border-radius: 999px;
  background: rgba(104, 14, 249, 0.1);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--accent-ink);
}

.cup-panel__status::before {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent-soft);
  box-shadow: 0 0 10px var(--accent-glow);
  animation: cup-pulse 2.4s ease-in-out infinite;
}

@keyframes cup-pulse {
  0%,
  100% {
    opacity: 0.35;
  }
  50% {
    opacity: 1;
  }
}

.cup-panel__cta {
  font-size: 0.95rem;
  padding: 13px 26px;
}


.stats {
  display: grid;
  /* Small enough that three tiles stay on one row inside a half-width column
     and on a phone, and auto-fit still spreads a shorter or longer list. */
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  gap: 10px;
  margin-top: clamp(18px, 2.5vw, 26px);
}

.stat {
  padding: 14px 12px;
  background: linear-gradient(180deg, var(--surface), var(--bg-2));
  border: 1px solid var(--line);
  border-top: 2px solid var(--accent);
  border-radius: var(--radius-sm);
  text-align: center;
}

.stat__value {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.5rem, 3.2vw, 1.95rem);
  color: #fff;
  text-shadow: 0 0 22px var(--accent-glow);
  line-height: 1.1;
}

.stat__label {
  display: block;
  margin-top: 5px;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--muted-dim);
}

/* The weekly stream schedule. One row per slot: day, what it is, and roughly
   when - the accent edge on the left is what makes the stack read as a list of
   appointments rather than three loose cards. */
.schedule {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.slot {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 14px;
  padding: 11px 15px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 2px solid var(--accent);
  border-radius: var(--radius-sm);
}

/* Each row is its own grid, so without a floor on this column the names would
   start at a different x on every row. Wide enough for the longest weekday
   there is, which is what keeps the list aligned whichever days are listed. */
.slot__day {
  min-width: 8em;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--accent-soft);
}

.slot__name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
}

.slot__time {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted-dim);
  white-space: nowrap;
}

/*
 * The live row.
 *
 * Green rather than the brand purple, and the same green as the room dot: on
 * this site purple is the colour of "this is ours" and green is the colour of
 * "this is happening now". The left border is what the eye catches first in a
 * list of three otherwise identical rows, so it carries the change too.
 */
.slot--live {
  border-left-color: #35d07f;
  background: linear-gradient(90deg, rgba(53, 208, 127, 0.07), transparent 60%), var(--surface);
}

.slot__live,
.schedule__live a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  white-space: nowrap;
  color: #4ee39a;
  text-decoration: none;
}

.slot__live:hover,
.schedule__live a:hover {
  text-decoration: underline;
}

/* Same dot as the room strip, and deliberately the same rule: two pulsing
   greens on one page that pulsed at different rates would read as two
   different kinds of live. */
.slot__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
  background: #35d07f;
  box-shadow: 0 0 8px rgba(53, 208, 127, 0.9);
  animation: room-pulse 2.4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .slot__dot {
    animation: none;
  }
}

.schedule__live {
  margin: 12px 0 0;
}

.schedule__note {
  margin: 12px 0 0;
  font-size: 0.82rem;
  color: var(--muted-dim);
}

.socials {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 10px;
}

.social {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 15px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--text);
  transition: border-color 0.2s ease, transform 0.2s ease, background 0.2s ease;
}

.social:hover {
  border-color: var(--line-strong);
  background: var(--surface-2);
  transform: translateY(-2px);
  color: var(--text);
}

.social__icon {
  width: 22px;
  height: 22px;
  fill: var(--accent-soft);
  flex: none;
  transition: fill 0.2s ease;
}

.social--youtube:hover .social__icon {
  fill: #ff4b4b;
}
.social--twitch:hover .social__icon {
  fill: #a970ff;
}
.social--tiktok:hover .social__icon {
  fill: #25f4ee;
}
.social--instagram:hover .social__icon {
  fill: #ff5c8a;
}

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

.social__body strong {
  font-family: var(--font-display);
  font-size: 0.95rem;
  display: block;
  line-height: 1.3;
}

.social__handle {
  font-size: 0.78rem;
  color: var(--accent-soft);
}


.social__arrow {
  width: 15px;
  height: 15px;
  color: var(--muted-dim);
  flex: none;
  transition: transform 0.2s ease, color 0.2s ease;
}

.social:hover .social__arrow {
  transform: translate(2px, -2px);
  color: var(--accent-soft);
}

.videos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 12px;
}

.video {
  text-decoration: none;
  color: var(--text);
}

.video__thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--surface-2);
}

.video__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.35s ease;
}

.video:hover .video__thumb img {
  transform: scale(1.05);
}

.video__play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(7, 6, 13, 0.32);
  transition: background 0.2s ease;
}

.video__play svg {
  width: 46px;
  height: 46px;
  fill: #fff;
  filter: drop-shadow(0 0 14px var(--accent-glow));
  transition: transform 0.2s ease;
}

.video:hover .video__play {
  background: rgba(104, 14, 249, 0.26);
}

.video:hover .video__play svg {
  transform: scale(1.12);
}

.video__title {
  margin: 10px 0 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.discord-cta {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: clamp(22px, 3vw, 32px);
  margin-bottom: clamp(40px, 6vw, 72px);
  background: linear-gradient(135deg, rgba(104, 14, 249, 0.22), rgba(16, 12, 28, 0.9));
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  overflow: hidden;
}

.discord-cta__body {
  max-width: 58ch;
}

.discord-cta h2 {
  font-size: clamp(1.3rem, 2.8vw, 1.7rem);
  text-transform: uppercase;
  margin-bottom: 0.5em;
}

.discord-cta p {
  color: var(--muted);
}


.discord-cta__mark {
  width: clamp(70px, 10vw, 110px);
  height: auto;
  fill: var(--accent);
  opacity: 0.22;
  flex: none;
}

/* --- Discord gate feedback ------------------------------------------------ */

.discord-error {
  display: block;
  margin-top: 10px;
  font-size: 0.84rem;
  color: var(--down);
}

/* Turnstile renders here; the managed widget is usually invisible. */
.turnstile-host {
  margin-top: 12px;
}

.turnstile-host:empty {
  margin-top: 0;
}

/* --- Footer --------------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--line);
  /* Opaque, not translucent: it has to land on exactly the same colour as the
     header, and the page's fixed purple haze shows through anything less. */
  background: var(--chrome);
  padding: clamp(20px, 3vw, 28px) 0;
}

/* Three short rows, all starting at the same left edge: the wordmark, the
   links under it, and the one legal line under those. The links sat out on the
   right for a while and the column of space that left in the middle was worse
   than the one it filled. */
.site-footer__inner {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(16px, 4vw, 32px);
  display: grid;
  gap: 14px;
}

.site-footer__brand {
  font-family: var(--font-display);
  font-size: 1.05rem;
}

.site-footer__socials {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px;
  font-size: 0.9rem;
}

.site-footer__socials a,
.site-footer__socials button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  text-decoration: none;
}

.site-footer__socials svg {
  width: 15px;
  height: 15px;
  fill: currentColor;
  flex: none;
}

.site-footer__socials a:hover,
.site-footer__socials button:hover {
  color: var(--accent-soft);
}

.site-footer__legal {
  margin: 0;
  font-size: 0.76rem;
  color: var(--muted-dim);
}

.site-footer__legal a {
  color: var(--muted-dim);
  text-decoration: none;
}

.site-footer__legal a:hover {
  color: var(--text);
  text-decoration: underline;
}

/* Holds the policy link off the end of the disclaimer. Decorative, so it is
   hidden from screen readers in the markup rather than read out as a pipe. */
.site-footer__sep {
  margin-inline: 8px;
  color: var(--line-strong);
}

/* --- Error pages ---------------------------------------------------------- */

.error-page {
  min-height: 70vh;
  display: grid;
  place-content: center;
  text-align: center;
  gap: 8px;
}

.error-page h1 {
  font-size: clamp(2rem, 7vw, 3.4rem);
  text-transform: uppercase;
}

.error-page .btn {
  justify-self: center;
  margin-top: 12px;
}

/* --- Responsive ----------------------------------------------------------- */

/* The hero stacks before its two columns get too narrow to hold what is in
   them - the brand lockup needs more room than the card does, and 1000 is
   where it stops having it. Centred again once stacked, and the card takes
   the full width and a bigger wordmark with it. */
@media (max-width: 1000px) {
  .hero__content--split {
    grid-template-columns: minmax(0, 1fr);
    max-width: 900px;
    text-align: center;
    /* The only gap in this grid is the one between the lockup and the card, so
       widening it here is simply room above the card - which it needs, sitting
       under a row of buttons rather than beside them. */
    gap: clamp(36px, 9vw, 56px);
  }

  /* Sized to its own content rather than stretched across the column: a card
     that stops where its words do still reads as a feature, where one running
     edge to edge reads as another section of the page. */
  .hero__content--split .cup-panel {
    justify-self: center;
    width: fit-content;
    max-width: 100%;
  }

  .hero__content--split .hero__brand,
  .hero__content--split .hero__actions {
    justify-content: center;
  }

  .hero__content--split .hero__lede {
    margin-inline: auto;
  }

  .cup-panel .wordmark {
    font-size: clamp(1.4rem, 4vw, 2.5rem);
  }
}

/* The body band stacks where the rest of the site does. */
@media (max-width: 900px) {
  .home-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 460px) {
  .slot {
    grid-template-columns: auto minmax(0, 1fr);
    gap: 4px 12px;
  }

  .slot__time {
    grid-column: 2;
  }
}

@media (max-width: 860px) {
  .discord-cta {
    flex-direction: column;
    align-items: flex-start;
  }

  .discord-cta__mark {
    align-self: flex-end;
    margin-top: -40px;
  }
}

@media (max-width: 700px) {
  /* The heading and the status stamp stop fitting on one row here, so the head
     stacks and the stamp returns to the left under the heading. */
  .panel__head {
    flex-direction: column;
    gap: 10px;
  }

  .lb-status {
    justify-content: flex-start;
    text-align: left;
    padding-top: 0;
  }

  /* Same reason: the head is a column here, so the subtitle starts at the left
     edge and the caption belongs back under the start of its own sentence. */
  .panel__head .panel__subtitle-line {
    text-align: left;
  }

  /* The line moves to the left edge here, so the hover has to open the other
     way or it hangs off the side of the panel. */
  .lb-live::after {
    right: auto;
    left: 0;
  }

  .site-nav {
    gap: 16px;
  }

  .brand__text {
    display: none;
  }

  /* Keep the tabs as a row rather than stacking them, and join the strip to the
     panel below so the two read as one control instead of three loose buttons
     floating above a card. */
  .tabs__list {
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
    padding: 5px 5px 0;
    border-bottom: 0;
    border-radius: var(--radius) var(--radius) 0 0;
  }

  /* At this size the labels are small and the icons are quiet, so without an
     outline the inactive tabs read as headings rather than something you can
     tap. A faint ring gives each one an edge. */
  .tabs__tab {
    flex-direction: column;
    gap: 5px;
    padding: 9px 4px 10px;
    font-size: 0.6rem;
    letter-spacing: 0.04em;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    background: rgba(255, 255, 255, 0.028);
    box-shadow: inset 0 0 0 1px var(--line);
  }

  .tabs__tab.is-active {
    box-shadow: inset 0 0 0 1px var(--line-strong);
  }

  .tabs__icon svg {
    width: 16px;
    height: 16px;
  }

  /* The active tab's underline sits flush against the panel it opens. */
  .tabs__tab.is-active::after {
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    width: auto;
    border-radius: 0;
  }

  .tabs__panel {
    margin-top: 0;
  }

  .tabs__panel .panel {
    border-top: 0;
    border-radius: 0 0 var(--radius) var(--radius);
  }

  /* Give the table back the space the panel padding was taking. */
  .panel {
    padding-inline: 12px;
  }

  /* Drop the gap column and cap the player name so the table fits the viewport
     without scrolling sideways. Capping the name is the important part: it is
     `white-space: nowrap`, so without a max-width a long name widens the whole
     table. The .lb-scroll container stays as a safety net for very long names
     on very narrow screens. */
  .lb {
    min-width: 0;
    width: 100%;
  }

  /* Points decide the standings, so the total time is what gives way. */
  .lb__th-time,
  .lb__time {
    display: none;
  }

  .lb__th-rank {
    width: 56px;
  }

  /* This cap, not the rendered name, is what sets the player column's width in
     an auto-layout table - so it has to leave room for the rank and time
     columns or the table overflows its container. */
  .lb__name {
    max-width: 30vw;
  }

  .lb__player {
    overflow: hidden;
  }

  .lb__expand {
    min-width: 0;
  }

  .lb thead th,
  .lb tbody td {
    padding-inline: 6px;
  }

  .lb__rank {
    gap: 5px;
  }

  .lb__name {
    font-size: 0.9rem;
  }

  .lb__time {
    font-size: 0.94rem;
  }

  .lb__expand {
    gap: 7px;
  }

  /* The chevron is a nicety; the whole row is tappable anyway. */
  .lb__chevron {
    display: none;
  }

  .move {
    min-width: 0;
    font-size: 0.62rem;
  }

  .countdown {
    display: block;
    width: 100%;
  }

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

@media (max-width: 420px) {
  .hero__strip {
    letter-spacing: 0.1em;
  }

  .countdown__units {
    gap: 6px;
  }
}

/* --- Motion preferences --------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

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

  .hero__media img {
    transform: none !important;
  }
}


/* --- Registration, entrants and the privacy page -------------------------- */

/* A destructive action. Red rather than the accent purple, because the one
   button on the site that permanently deletes something should not look like
   every other button on the site. */
.btn--danger {
  background: #b3261e;
  box-shadow: 0 0 0 1px rgba(255, 120, 110, 0.3) inset;
}

.btn--danger:hover:not(:disabled) {
  background: #c8352c;
  box-shadow: 0 0 22px rgba(179, 38, 30, 0.4);
}

/* The registration flow is a single narrow column - one field, one button, one
   decision per screen. Nothing here benefits from the full page width. */
.reg {
  padding-block: clamp(28px, 6vw, 64px);
}

.reg__panel {
  max-width: 34rem;
  margin-inline: auto;
  padding: clamp(20px, 4vw, 34px);
}

.reg__title {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 4vw, 2rem);
  margin: 0;
}

/* The success tick. --up is the site's existing positive green - the one the
   leaderboard uses for a place gained - rather than a second green invented for
   this one screen. */
.reg__tick {
  display: block;
  width: 54px;
  height: 54px;
  margin: 0 0 16px;
  color: var(--up);
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  transform-origin: 27px 27px;
  animation: reg-tick-in 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
}

/* Circumference at r=23 is ~144.5; the dash is the whole line, offset by its
   own length so it starts invisible and is walked into view. */
.reg__tick-ring {
  stroke-width: 2;
  opacity: 0.55;
  stroke-dasharray: 145;
  stroke-dashoffset: 145;
  animation: reg-tick-draw 0.55s ease-out forwards;
}

/* Same trick, over the check's ~34 units, starting as the ring closes. */
.reg__tick-mark {
  stroke-width: 3.5;
  stroke-dasharray: 34;
  stroke-dashoffset: 34;
  animation: reg-tick-draw 0.3s 0.34s cubic-bezier(0.65, 0, 0.45, 1) forwards;
}

@keyframes reg-tick-draw {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes reg-tick-in {
  from {
    opacity: 0;
    transform: scale(0.8);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* The blanket reduced-motion rule below cuts every duration to nothing but
   leaves delays alone, which would hold the check back a third of a second for
   no reason. Drawn and still, straight away. */
@media (prefers-reduced-motion: reduce) {
  .reg__tick,
  .reg__tick-ring,
  .reg__tick-mark {
    animation: none;
  }

  .reg__tick-ring,
  .reg__tick-mark {
    stroke-dashoffset: 0;
  }
}

.reg__lead {
  color: var(--muted);
  margin: 0.6em 0 1.6em;
}

.reg__form {
  display: grid;
  gap: 20px;
}

/* The register form has no lead paragraph under its title any more, so without
   this the first label sits right against the heading. Adjacency, so the screens
   that do still carry a lead keep its spacing instead. */
.reg__title + .reg__form {
  margin-top: 24px;
}

.reg__aside {
  margin-top: 20px;
  font-size: 0.88rem;
  color: var(--muted-dim);
}

/* The notice between the field and the button: what is collected and why, in
   the flow of the form rather than only behind the privacy link. Small print,
   but read-before-you-submit small print, which is why it is above the button
   and not under it. Margin-less because it is a grid item of .reg__form. */
.reg__privacy {
  margin: 0;
  font-size: 0.88rem;
  color: var(--muted);
  line-height: 1.5;
}

.reg__details {
  margin-top: 22px;
  border-top: 1px solid var(--line);
  padding-top: 16px;
  font-size: 0.9rem;
  color: var(--muted);
}

.reg__details summary {
  cursor: pointer;
  color: var(--text);
  font-weight: 600;
}

.reg__details p {
  margin: 0.8em 0 0;
}

/* What we are about to save, or about to delete. A definition list because that
   is genuinely what it is, and because it stays readable when a Trackmania
   display name turns out to be forty characters of unicode. */
.reg__summary {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 8px 18px;
  margin: 0 0 22px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.03);
}

.reg__summary dt {
  color: var(--muted-dim);
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  align-self: center;
}

.reg__summary dd {
  margin: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* --- Form fields ---------------------------------------------------------- */

.field {
  display: grid;
  gap: 7px;
}

.field__label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.92rem;
}

.field__input {
  width: 100%;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.25);
  color: var(--text);
  font: inherit;
}

.field__input:focus-visible {
  outline: none;
  border-color: var(--line-strong);
  box-shadow: 0 0 0 3px rgba(104, 14, 249, 0.18);
}

.field__input[aria-invalid='true'] {
  border-color: #d4564c;
}

.field__help {
  font-size: 0.84rem;
  color: var(--muted-dim);
  margin: 0;
}

.field__help code {
  background: rgba(255, 255, 255, 0.06);
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 0.95em;
}

.field__error {
  font-size: 0.86rem;
  color: #ff9d94;
  margin: 0;
}

/* --- Entrants list -------------------------------------------------------- */

.entrants__cta {
  margin-bottom: 22px;
}

.panel__count {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 10px;
  border-radius: 999px;
  background: rgba(104, 14, 249, 0.22);
  border: 1px solid var(--line-strong);
  color: var(--accent-ink);
  font-size: 0.78em;
  vertical-align: middle;
}

/* Wraps into as many columns as fit. A few hundred names in one tall column
   would be a scroll nobody finishes. */
.entrants {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 2px 14px;
}

.entrants__row {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 4px;
  border-bottom: 1px solid var(--line);
  min-width: 0;
}

.entrants__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- Long-form prose (the privacy page) ----------------------------------- */

.prose {
  max-width: 44rem;
  padding-block: clamp(28px, 6vw, 64px);
}

.prose h1 {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 5vw, 2.4rem);
  margin: 0;
}

.prose__meta {
  color: var(--muted-dim);
  font-size: 0.86rem;
  margin: 0.5em 0 2.4em;
}

.prose__section {
  margin-bottom: 2.2em;
}

.prose h2 {
  font-family: var(--font-display);
  font-size: 1.15rem;
  margin: 0 0 0.6em;
}

.prose p {
  color: var(--muted);
  margin: 0 0 0.9em;
  line-height: 1.65;
}

.prose__list {
  color: var(--muted);
  margin: 0 0 0.9em;
  padding-left: 1.25em;
  line-height: 1.65;
}

.prose__list li {
  margin-bottom: 0.25em;
}

/* --- Footer legal links --------------------------------------------------- */


/* The line under the leaderboard head that keeps the entrants list and the
   sign-up form one click away once they no longer have a tab of their own. */
.lb-entrants {
  margin: 0 0 16px;
  font-size: 0.88rem;
  color: var(--muted-dim);
}

.lb-entrants a {
  color: var(--muted);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
}

.lb-entrants a:hover {
  color: var(--text);
  border-bottom-color: var(--line-strong);
}

.lb-entrants__cta {
  color: var(--accent-ink) !important;
  font-weight: 600;
}

/* The standalone entrants page: same measure as the panel it holds. */
.entrants-page {
  padding-block: clamp(24px, 5vw, 48px);
}

.entrants-page .eyebrow {
  margin-bottom: 14px;
}

/* The folded disclosures carry several paragraphs now, not one. */
.reg__details p + p {
  margin-top: 0.7em;
}

.entrants__leave {
  margin: 22px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font-size: 0.86rem;
  color: var(--muted-dim);
}

/* --- The club: members, Weekly Shorts, rooms ------------------------------ */

/*
 * The hero photo, carried behind the top of a page.
 *
 * The club pages have no hero of their own - they open straight into a panel -
 * and a flat near-black behind that panel made them read as a different site
 * from the homepage. This puts the same scene back there. Most of it ends up
 * behind the panel, which is the point: what shows is the margin either side
 * and the strip above it, enough to place the page without competing with the
 * table on it.
 *
 * It scrolls with the page rather than being pinned to the viewport: absolute
 * inside this element, the same way .hero__media sits inside .hero.
 *
 * SIZED LIKE AN IMAGE, NOT LIKE A BACKDROP. The photo spans the width and takes
 * whatever height its own ratio gives it - `bottom` is left auto. Stretching it
 * over the full element instead put a 16:9 scene behind a page thousands of
 * pixels tall, and `cover` answered that by blowing the image up until only a
 * cropped sliver of it was left. Below the photo there is simply nothing, which
 * is the page's own dark - the same way the homepage runs out of hero and
 * carries on.
 *
 * The floor under that is the homepage hero's own band, min(78vh, 720px): at
 * phone widths the ratio alone gives about 210px, a strip too thin to read as a
 * place. Where the floor wins, `cover` crops the sides, which is exactly what
 * .hero__media does with object-fit.
 *
 * Both are in ONE `height`, not a height plus a min-height, because min-height
 * outranks max-height - the two together would defeat the cap below. max-height
 * holds the photo inside the element, so a short page (the empty-week board, a
 * search that matched nobody) does not trail it down past the footer. Clipping
 * with overflow would have been the other way to do that, but .member-card is
 * the containing block for an open member card, and a card near the foot of the
 * list would have been clipped with it.
 *
 * Two layers rather than one, because the image needs a filter and the grade
 * over it does not - a single element would brighten the shade along with the
 * photo. They share a box, so the grade lands on the photo and nowhere else.
 *
 * No isolation and no z-index here on purpose: this element must NOT become a
 * stacking context, or its two negative layers would be trapped inside it and
 * the page's purple haze - fixed at -1 in the root - would fall behind the
 * photo instead of lying over it. Left alone, the order stays photo (-3),
 * grade (-2), haze (-1), content, all above <body>'s own colour.
 */
.page-photo {
  position: relative;
}

.page-photo::before,
.page-photo::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  /* 56.28vw is the source's own ratio as a height - 100 / 1.777. All three hero
     variants are within 0.1% of that, so one value covers every breakpoint. */
  height: max(56.28vw, min(78vh, 720px));
  max-height: 100%;
  pointer-events: none;
}

.page-photo::before {
  z-index: -3;
  background: url('/img/hero-lg.webp') center / cover no-repeat;
  /* Near the hero's 1.72, for the same reason: the source is nearly black, so
     anything gentler than this leaves a dark texture rather than a scene. The
     grade below is what keeps it from fighting the panel. */
  filter: brightness(1.6) contrast(1.1) saturate(1.1);
}

@media (max-width: 1400px) {
  .page-photo::before {
    background-image: url('/img/hero-md.webp');
  }
}

@media (max-width: 700px) {
  .page-photo::before {
    background-image: url('/img/hero-sm.webp');
  }
}

/*
 * Settles the photo under the page.
 *
 * Heavy at the top, where the photo meets the header with no panel between, and
 * heavy again at the bottom, where it has to reach the page's own colour: that
 * last stop is what turns the end of the photo into a fade rather than a line
 * ruled across the page. The middle stays light because the panel covers it
 * anyway. The flat wash underneath keeps the whole thing dark enough that a
 * bright patch of the scene never lands behind running text in the margin.
 */
.page-photo::after {
  z-index: -2;
  background:
    linear-gradient(180deg, rgba(7, 6, 13, 0.88) 0%, rgba(7, 6, 13, 0.42) 22%, rgba(7, 6, 13, 0.5) 58%, rgba(7, 6, 13, 0.82) 82%, var(--bg) 100%),
    rgba(7, 6, 13, 0.18);
}


/* Filter chips. Links rather than buttons: every filter is a real URL. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 18px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  border-radius: 999px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 600;
  text-decoration: none;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.chip:hover {
  color: var(--text);
  border-color: var(--line-strong);
}

.chip.is-current {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  box-shadow: 0 0 18px -6px var(--accent-glow);
}

.chip__count {
  font-family: var(--font-mono);
  font-size: 0.78em;
  opacity: 0.75;
}

/*
 * Tag badges: the tag a member holds, under their name.
 *
 * NO PLATE. These were a pill - background, border, its own rounded corner -
 * sitting inside a card that already has all three, and a box inside a box read
 * as two things to look at rather than one. Stripped back to an icon and a
 * word, the tag is the second line of the card instead of an object on it, and
 * every card lost about 8px of height and 16px of width doing nothing.
 *
 * The colour carries what the plate used to: these are the in-game CARTA tags,
 * flattened to one shade each because a five-stop gradient at this size reads
 * as mud. With no background behind them they are the only thing saying which
 * tag this is, so each one is light enough to read on --bg-2 on its own.
 *
 * Sentence case rather than shouted. These used to be uppercase, when the badge
 * was a label on a row among other uppercase labels; directly under a member's
 * own name, "CUP WINNER" read as the louder of the two.
 *
 * Capped at the card's width with an ellipsis: a long tag name must not be what
 * decides how wide a column of the grid is.
 */
.tag-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 100%;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tag-badge svg {
  width: 11px;
  height: 11px;
  flex: none;
  fill: currentColor;
}

.tag-badge--purple {
  color: #b593f5;
}

.tag-badge--gold {
  color: #ffd75e;
}

.tag-badge--black {
  color: #cfcfdd;
}

.chip--gold.is-current {
  background: #b58900;
  border-color: #b58900;
  box-shadow: 0 0 18px -6px rgba(255, 200, 0, 0.7);
}

.chip--black.is-current {
  background: #3a3a46;
  border-color: #5a5a6a;
  box-shadow: 0 0 18px -6px rgba(230, 230, 240, 0.4);
}

.role-badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.68rem;
  font-weight: 600;
  white-space: nowrap;
}

.role-badge--vip {
  color: #ffd75e;
  border-color: rgba(255, 200, 0, 0.35);
}

/* --- The title band on a club page --------------------------------------- */

/*
 * The small hero the club pages open with: eyebrow, the page's name, and a
 * fact or two about it.
 *
 * It sits INSIDE .page-photo rather than above it, so the photo behind it is
 * the same photo that runs on down behind the panel - one scene, not a strip
 * and then a background. Nothing here is painted: the band is transparent and
 * the grade on .page-photo::after is what keeps the type readable, which is why
 * it can be this tall without adding another dark slab to the page.
 *
 * Shared by /members and /shorts. See views/page-head.tsx.
 */
.page-head {
  padding-block: clamp(26px, 5vw, 48px) clamp(16px, 2.6vw, 26px);
}

.page-head__title {
  margin: 0;
  font-size: clamp(2.1rem, 7vw, 3.5rem);
  text-transform: uppercase;
  letter-spacing: 0.005em;
}

/*
 * The facts under the title: how many members, which week.
 *
 * A row rather than a sentence, because they are separate readings rather than
 * clauses - and they wrap onto their own lines on a phone without either one
 * becoming a fragment.
 */
.page-head__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 24px;
  margin: 14px 0 0;
  font-size: 0.98rem;
  color: var(--muted);
}

.page-head__stat {
  display: inline-flex;
  align-items: center;
  gap: 9px;
}

.page-head__stat svg {
  width: 20px;
  height: 20px;
  flex: none;
  fill: var(--accent-soft);
  filter: drop-shadow(0 0 10px var(--accent-glow));
}

/* The band already carries the air above the panel; the container's own top
   padding on top of that was most of a phone screen of nothing. */
.page-head + .entrants-page {
  padding-top: 0;
}

/* --- Members: search, tabs, tools ---------------------------------------- */

/*
 * The search runs the full width of the panel, on its own row above the tabs.
 *
 * It shared that row with the tabs for a while, which fit and read as clutter:
 * a field you type into and a set of things you click are two different moves,
 * and putting them shoulder to shoulder made the top of the panel one busy
 * strip rather than two clear ones. What is left on this row is the first thing
 * somebody does on a page of fourteen hundred names, so it gets the whole width
 * and the height that says so.
 */
.members__search {
  display: flex;
  align-items: stretch;
  gap: 10px;
  margin-bottom: 14px;
}

/* .lb-search is capped at 340px for the leaderboard's controls row, where it
   shares a line with a segmented control. Here it is the row. */
.members__search .lb-search,
.shorts-search .lb-search {
  display: flex;
  flex: 1 1 auto;
  max-width: none;
}

.members__search .lb-search input {
  padding: 14px 16px 14px 46px;
  font-size: 0.98rem;
}

.members__search .lb-search svg {
  left: 17px;
  width: 19px;
  height: 19px;
}

/* Stretch would otherwise make the Clear link full height and top-align it. */
.members__search .linklike {
  align-self: center;
}

/*
 * The shorts board keeps the old borrowed rule: no icon there, so no room
 * reserved for one.
 */
.shorts-search .lb-search input {
  padding: 10px 13px;
}

/*
 * The filter tabs.
 *
 * One tab per EARNED tag plus "All", as a row of equal cells rather than a
 * huddle of chips: the three of them are a single choice, and equal cells say
 * that in a way that three differently-sized pills do not. Each carries its
 * count, which is how the page tells you a Cup Winner tag exists without
 * devoting a heading and a paragraph to twelve people.
 */
.tier-tabs {
  display: grid;
  /* auto-fit rather than a fixed three, so a tag added to config lands in the
     row instead of overflowing it, and so the row folds as it narrows. */
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px;
  margin-bottom: 18px;
}

.tier-tab {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
  color: var(--muted);
  padding: 10px 15px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line);
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.tier-tab:hover {
  border-color: var(--line-strong);
}

.tier-tab svg {
  width: 20px;
  height: 20px;
  flex: none;
  fill: currentColor;
}

.tier-tab__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}

.tier-tab__label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * The tag, in the tab's left rail between the icon and the words.
 *
 * Beside the icon rather than inside the text block, which is what keeps the
 * row aligned: the icon is a fixed 20px and every CARTA tag is the same five
 * characters, so the label starts at the same x in all three tabs and the
 * column of titles reads as a column. Putting it inside the text would have
 * tied that alignment to whatever each tag happened to measure.
 *
 * flex: none, or a narrow tab would squeeze the plate before it squeezed the
 * label - and a squashed picture of a tag is worse than a shortened word.
 *
 * The plate keeps its own colour on the chosen tab as much as on the others,
 * which is the whole point of there being only one plate colour.
 */
.tier-tab__tag {
  display: inline-flex;
  flex: none;
}

.tier-tab__count {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--muted-dim);
}

/*
 * Each tag's icon wears that tag's colour at rest, so the row is scannable
 * before a word of it is read - and these are the badge colours from further
 * down, not a second palette.
 *
 * One rule per tier key. The All tab deliberately has none: it is not a tag, so
 * its icon keeps the muted grey above and says so.
 */
.tier-tab--black svg {
  color: #e6e6f0;
}

.tier-tab--gold svg {
  color: #ffd75e;
}

/*
 * The chosen tab is the site's accent, whichever tag it is.
 *
 * Tempting to make the gold tab gold when it is picked, but then "selected" is
 * a different colour in each cell and stops reading as one state. The tag's own
 * colour is on its icon at rest and on the plate in the note below, which is
 * plenty - and the icon goes white here so it does not fight the purple.
 */
.tier-tab.is-current,
.tier-tab.is-current:hover {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 20px -7px var(--accent-glow);
}

.tier-tab.is-current .tier-tab__label,
.tier-tab.is-current svg {
  color: #fff;
}

.tier-tab.is-current .tier-tab__count {
  color: rgba(255, 255, 255, 0.75);
}

/*
 * THE TABS STAY A ROW AT EVERY WIDTH. They are one choice, and a row is what
 * says so; stacked, they stop being a control and become three links. So as the
 * window narrows the row does not break up - it sheds what is in each cell,
 * in order of what can be spared.
 *
 * First to go is the tag. The left rail costs about 57px of the label's room,
 * and below roughly 860px "Golden Members" starts to lose letters to it - which
 * is a bad trade for a picture the note under a chosen tab shows anyway.
 */
@media (max-width: 860px) {
  .tier-tab__tag {
    display: none;
  }

  /* With the plate gone the label has room again, but not much of it, so it is
     allowed to take a second line rather than be cut. Grid rows size to their
     tallest cell, so a wrapped label leaves the two beside it looking
     deliberate rather than broken. */
  .tier-tab__label {
    white-space: normal;
  }
}

/*
 * On a phone the cell turns on its side: icon over label over count.
 *
 * Three cells across 375px is about 97px each, and beside an icon that leaves
 * roughly 48px of label - narrower than the word "Members". Stacking the icon
 * gives the label the whole cell instead, which is enough for two short lines,
 * and the whole row comes to about the height two of the old stacked tabs did.
 */
@media (max-width: 560px) {
  .tier-tabs {
    /* Explicitly three, not auto-fit: at this width auto-fit would fold them
       into a column, which is the one thing this is here to prevent. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
  }

  .tier-tab {
    flex-direction: column;
    /* Centred, because the grid makes every cell as tall as the tallest and a
       one-line label would otherwise sit at the top of a two-line cell. */
    justify-content: center;
    gap: 4px;
    padding: 9px 6px;
    text-align: center;
  }

  .tier-tab svg {
    width: 17px;
    height: 17px;
  }

  .tier-tab__text {
    align-items: center;
  }

  .tier-tab__label {
    font-size: 0.72rem;
    line-height: 1.25;
    letter-spacing: 0;
  }

  .tier-tab__count {
    font-size: 0.68rem;
  }
}

/*
 * What the chosen tag means, shown only once it has been chosen.
 *
 * The page used to carry all three of these at once, above a list nobody had
 * filtered yet. Now it is an answer: you clicked Cup Winners, so here is what a
 * Cup Winner is and what the tag looks like in game.
 */
.tier-note {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px 16px;
  padding: 13px 16px;
  margin-bottom: 18px;
  border-radius: var(--radius-sm);
  background: rgba(104, 14, 249, 0.1);
  border: 1px solid var(--line-strong);
}

.tier-note__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  min-width: 0;
}

.tier-note__text strong {
  font-family: var(--font-display);
  font-size: 0.92rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.tier-note__text span {
  font-size: 0.85rem;
  color: var(--muted);
}

/*
 * The heading over the list, with the order beside it.
 *
 * Two things, and it stays two things at every width - which is the point. It
 * carried a count and a page-stepper as well, and on a phone those wrapped into
 * a third and fourth line of controls between the tabs and the first member.
 * The count is on the tab that produced the list and the paging is at the foot,
 * so both were saying something twice.
 */
.members__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 20px;
  margin-bottom: 14px;
}

.members__listtitle {
  margin: 0;
  font-size: 1.05rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}


.members__tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
}

.members__sort {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  font-size: 0.8rem;
  color: var(--muted-dim);
}

/*
 * On a phone the words "Sort by" come off, not the menu.
 *
 * The heading and the menu together want about 300px, which is exactly what a
 * 375px screen has inside the panel - and the two words in front of the menu
 * were what pushed it onto a line of its own. The <label> still wraps the
 * <select>, so the menu keeps the same accessible name it had when the words
 * were drawn; only the drawing goes.
 */
@media (max-width: 560px) {
  .members__bar {
    gap: 8px 12px;
  }

  .members__listtitle {
    font-size: 0.95rem;
  }

  .members__sort > span {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  .members__sort select {
    height: 34px;
    padding: 0 30px 0 10px;
    background-position: right 10px center;
    font-size: 0.8rem;
  }
}

/*
 * The sort menu, with the browser's own chrome taken off it.
 *
 * A native <select> reserves room for its arrow inside whatever padding you
 * give it, so `padding: 7px 10px` came out as ten pixels of nothing on the left
 * and an arrow jammed against the right border - the two edges disagreeing by
 * about 8px on a control 150px wide. appearance: none drops the browser's
 * button and the arrow below is drawn where we want it, which is the only way
 * to make both sides measure the same.
 *
 * The arrow is the site's own chevron - the same path as .faq__chevron - as a
 * background image, because a background cannot take currentColor and a
 * pseudo-element cannot be attached to a <select>. Its colour is --muted spelt
 * out; if that token changes, change it here too.
 *
 * Height rather than vertical padding, and 36px of it, so it lines up exactly
 * with the pager steps sitting beside it in .members__tools.
 */
.members__sort select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  height: 36px;
  padding: 0 34px 0 12px;
  border-radius: 7px;
  background: var(--bg-2)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23a49dbd' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E")
    no-repeat right 12px center;
  background-size: 12px 12px;
  border: 1px solid var(--line);
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

.members__sort select:hover {
  border-color: var(--line-strong);
}

/* The menu's own list is drawn by the OS, which on a dark page defaults to
   white-on-white unless the option says otherwise. */
.members__sort option {
  background: var(--surface);
  color: var(--text);
}

/* --- Member list ---------------------------------------------------------- */

/*
 * Members as a grid of small cards rather than one per line.
 *
 * Fourteen hundred people in a one-per-line list is a scroll nobody finishes.
 * At 196px a card, the panel takes five columns on a desktop and folds to two
 * and then one on the way down - about a hundred people a screen, which is one
 * whole page of the list at a time.
 *
 * auto-FILL rather than auto-fit: a last row holding two cards keeps them card
 * sized instead of stretching them across the panel, so the grid stays a grid
 * all the way to the end.
 */
.member-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
  gap: 9px;
}

.member-grid__item {
  min-width: 0;
}

/* The containing block for the open panel, which is why it is the card and not
   the grid that is positioned. */
.member-card {
  position: relative;
}

.member-card__face {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 11px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line);
  cursor: pointer;
  /* Both are needed to lose the disclosure triangle: the standard property and
     the one Safari still wants. */
  list-style: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.member-card__face::-webkit-details-marker {
  display: none;
}

.member-card__face:hover {
  border-color: var(--line-strong);
}

.member-card__face:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.member-card[open] > .member-card__face {
  border-color: var(--accent);
  background: rgba(104, 14, 249, 0.16);
}

/* Bigger than the flag in a table row: it is one of only two things on the
   card, and at 21px wide beside two stacked lines it read as a bullet. */
.member-card__face .flag {
  width: 28px;
  height: 20px;
  border-radius: 3px;
}

.member-card__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* Tight, because the two lines are one label: a name and the tag it holds.
     The 4px that used to sit here was separating the name from a bordered pill
     rather than from a word. */
  gap: 1px;
  flex: 1;
  min-width: 0;
}

/* Truncated rather than wrapped: a second line of name would make one card
   taller than its row, and Trackmania names run long. */
.member-card__name {
  max-width: 100%;
  font-weight: 600;
  font-size: 0.86rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.member-card__chevron {
  width: 14px;
  height: 14px;
  flex: none;
  color: var(--muted-dim);
  transition: transform 0.15s ease;
}

.member-card[open] .member-card__chevron {
  transform: rotate(90deg);
}

@media (prefers-reduced-motion: reduce) {
  .member-card__chevron {
    transition: none;
  }
}

/*
 * The open panel.
 *
 * Absolutely positioned on purpose: opening a member must not reflow the grid
 * around them, or the list rearranges itself under your cursor on every tap.
 * Anchored to its card's left edge, and nudged sideways by js/member-cards.js
 * when that would hang off either edge of the page. The max-width guarantees a
 * position that fits exists, so the nudge always has somewhere to put it.
 */
.member-card__panel {
  position: absolute;
  top: calc(100% + 7px);
  left: 0;
  z-index: 30;
  width: max-content;
  max-width: min(280px, calc(100vw - 40px));
  padding: 14px 16px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line-strong);
  box-shadow: var(--shadow-lift), 0 18px 40px -20px rgba(0, 0, 0, 0.9);
  text-align: left;
  /* Set by js/member-cards.js. Zero until it has something better to say, so
     the panel is correctly placed with the script blocked or still loading. */
  transform: translateX(var(--card-shift, 0px));
}

.member-card__panelname {
  margin: 0 0 10px;
  font-weight: 700;
  font-size: 0.95rem;
  overflow-wrap: anywhere;
}

.member-card__facts {
  display: grid;
  gap: 7px;
  margin: 0 0 12px;
}

.member-card__facts dt {
  margin: 0;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted-dim);
}

.member-card__facts dd {
  margin: 0;
  font-size: 0.86rem;
  color: var(--text);
}

/* --- The club tag, drawn in the game's own colours ------------------------ */

/*
 * ONE plate, for every tag, everywhere.
 *
 * The plate exists because the tag colours are fixed and the background is not:
 * the Cup Winners tag is five shades of near-black and vanishes on this site
 * unless something light sits behind it. It used to be two plates - light or
 * dark, chosen in the view by measuring the tag's brightness - which meant the
 * same tag sat on a different colour in the filter tab than in the note right
 * below it, and the plate started looking like it meant something.
 *
 * WHY IT IS SHADED RATHER THAN FLAT. No single flat colour can give good
 * contrast to all three tags: they run from #111111 to nearly yellow, so
 * anything dark enough for the gold one swallows the black one, and the flat
 * light grey this started as left the middle of the gold gradient - #FFCC22
 * through #FFAA33 - sitting at barely 1.1:1 against it.
 *
 * So the plate is a bar of metal rather than a swatch. The gradient runs bright
 * at both edges and MID-GREY THROUGH THE MIDDLE, where the letters actually
 * are: the light tag now has something darker than itself behind it, the dark
 * tags still have something much lighter, and the bright top and bottom read as
 * the bevel that makes it look like a thing rather than a mistake. Worst-case
 * contrast across all fifteen colour stops goes from about 1.09 to 1.45, and
 * most of them do far better than that.
 *
 * The stripe layer on top is a brushed finish, two thirds of a percent of black
 * and white on a 3px diagonal. It is there for grain, not for contrast - at
 * that strength it is invisible on its own and only shows as texture.
 *
 * Fixed hex rather than tokens, and deliberately not --bg or --surface: this is
 * a picture of something from another piece of software, and it should not
 * restyle itself when the page's theme moves.
 */
.tag-plate {
  display: inline-flex;
  flex: none;
  padding: 7px 13px;
  border-radius: 7px;
  background:
    repeating-linear-gradient(
      115deg,
      rgba(255, 255, 255, 0.055) 0 1px,
      rgba(0, 0, 0, 0.04) 1px 3px
    ),
    linear-gradient(
      180deg,
      #d0d0dd 0%,
      #a9a9bb 26%,
      #86869c 52%,
      #a0a0b3 76%,
      #c5c5d4 100%
    );
  border: 1px solid #76768e;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    0 1px 2px rgba(0, 0, 0, 0.45);
}

.club-tag {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: 0.06em;
  white-space: nowrap;
  /* What makes the gold tag legible on a light plate, and what the tag's own
     `$S` asked for in the first place. Tight and low-opacity, so it defines
     the letter edges without reading as a drop shadow. */
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.65);
}

/*
 * The version that rides inside a filter tab.
 *
 * Small enough to sit on the tab's second line beside the count, which is the
 * only reason it fits: the tab's width is set by its label, and that line had
 * room going spare.
 */
.tag-plate--sm {
  padding: 2px 6px;
  border-radius: 4px;
}

.tag-plate--sm .club-tag {
  font-size: 0.6rem;
  letter-spacing: 0.03em;
  /* Harder than the full-size one. At 0.6rem the gold tag's lightest stop is
     four pixels of near-yellow, and it needs every bit of the edge. */
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.7);
}

@media (max-width: 560px) {
  .tier-note {
    gap: 10px 12px;
    padding: 12px 13px;
  }

  /* The tag is the one thing here that must not shrink to nothing, but at full
     size it was taking half the row before the text got a character. */
  .tier-note .tag-plate {
    padding: 6px 10px;
  }

  .tier-note .club-tag {
    font-size: 0.88rem;
    letter-spacing: 0.05em;
  }
}

/* Pagination as links. Matches .pager button, which these sit alongside. */
.pager--links a,
.pager--links .pager__step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  height: 36px;
  padding: 0 9px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.84rem;
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.pager--links a:hover:not(.is-current) {
  color: var(--text);
  border-color: var(--line-strong);
}

.pager--links .pager__page.is-current {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  box-shadow: 0 0 18px -6px var(--accent-glow);
}

.pager--links .is-disabled {
  opacity: 0.35;
}


/* --- Weekly Shorts -------------------------------------------------------- */

.shorts-maps__link {
  display: inline-block;
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.82rem;
  text-decoration: none;
}

.shorts-maps__link:hover {
  color: var(--text);
  border-color: var(--line-strong);
}

.shorts__status {
  margin-top: 16px;
}

/* Left-aligned, unlike the members one which sits opposite a heading. */
.shorts-search {
  justify-content: flex-start;
  margin-bottom: 14px;
}

/* --- Club rooms ----------------------------------------------------------- */

.rooms {
  padding: 20px clamp(16px, 3vw, 26px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: linear-gradient(180deg, var(--surface) 0%, rgba(11, 8, 19, 0.92) 100%);
}

.rooms__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 14px;
}

.rooms__heading {
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 0;
}

.rooms__meta {
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted-dim);
}

.rooms__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 10px;
}

.room {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  background: var(--bg-2);
  border: 1px solid var(--line);
}

.room--live {
  border-color: rgba(104, 14, 249, 0.5);
}

.room__name {
  font-weight: 600;
  font-size: 0.9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* Room names are short and known; give them the space ahead of the count
     rather than letting a three-digit player cap push them into an ellipsis. */
  flex: 1 1 auto;
  min-width: 0;
}

.room__count {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.88rem;
  color: var(--text);
}

.room__cap {
  color: var(--muted-dim);
  font-weight: 400;
  font-size: 0.78rem;
}

.room__empty {
  color: var(--muted-dim);
  font-family: var(--font-body, inherit);
  font-weight: 400;
  font-size: 0.82rem;
}

/* The one genuinely live thing on the page, so it gets the one pulsing dot. */
.room__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #35d07f;
  box-shadow: 0 0 8px rgba(53, 208, 127, 0.9);
  animation: room-pulse 2.4s ease-in-out infinite;
}

@keyframes room-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

@media (prefers-reduced-motion: reduce) {
  .room__dot {
    animation: none;
  }
}

.rooms__hint {
  margin: 14px 0 0;
  font-size: 0.8rem;
  color: var(--muted-dim);
}


/* --- Weekly Shorts: the two-column layout --------------------------------- */

/*
 * Single column by default - the maps above the board, which is what a phone
 * wants and what the markup order already says. The two-column arrangement is
 * the special case, so it lives in a min-width query rather than being the base
 * that a max-width query has to undo rule by rule.
 *
 * 1080px is where the room actually appears, not a round number: the container
 * is 1240px, and the panel inside it eats ~68px of padding on top of the
 * container's own ~64px. At 1080 that leaves ~950px inside the panel, which is
 * a 272px column of maps, the gap, and a board still wide enough for a rank, a
 * flag, a name and a score.
 */
@media (min-width: 1080px) {
  .shorts-layout {
    display: grid;
    /* Wider where there is width to spare, so the thumbnails have somewhere
       to grow into. The floor is what the 1080px breakpoint was picked
       against; the ceiling is where the board starts giving up more than the
       maps gain. */
    grid-template-columns: clamp(272px, 23vw, 336px) minmax(0, 1fr);
    gap: clamp(22px, 2.4vw, 34px);
    /* start, not the default stretch: the maps column is a few hundred pixels
       and the board is several thousand, and a stretched column cannot be
       sticky inside its own full-height box. */
    align-items: start;
  }

  /* The site header scrolls away rather than sticking, so this only needs
     enough of a gap to not sit flush against the top edge. The column is
     ~450px tall at five maps - well inside any desktop viewport - so it never
     sticks with its own bottom out of reach. */
  .shorts-layout__maps {
    position: sticky;
    top: 20px;
  }

  /*
   * In the column the strip stops being a strip.
   *
   * Sideways scrolling existed to keep the five maps reading as one sequence
   * instead of wrapping into three-and-two; a single column down the page is
   * still one sequence, so the reason goes away and the scrollbar with it.
   */
  .shorts-layout__maps .map-strip {
    flex-direction: column;
    gap: 8px;
    margin-bottom: 0;
    padding-bottom: 0;
    overflow: visible;
    scroll-snap-type: none;
  }

  /* Thumbnail beside the name rather than above it. A 272px-wide square would
     be 272px tall, and five of those is a column too long to keep on screen -
     which is the whole point of the column being sticky. */
  .shorts-layout__maps .map-card {
    flex-direction: row;
    align-items: center;
    gap: 12px;
    width: auto;
  }

  /*
   * Sized against the thing that actually limits it: five of these plus their
   * gaps have to fit in the viewport, or the sticky column sticks with its own
   * bottom permanently out of reach. That budget is vertical, so the size is
   * too - 12vh leaves the column at roughly two thirds of the viewport at every
   * height, with a floor for short windows and a ceiling so a very tall one
   * does not hand the maps the whole column.
   *
   * The percentage is the second half of the guarantee: whatever the vh works
   * out to, the name keeps more than half the card. Without it a tall narrow
   * window pairs the biggest thumbnail with the narrowest column and clips the
   * names to an ellipsis.
   */
  .shorts-layout__maps .map-card__img {
    width: clamp(88px, 12vh, 132px);
    max-width: 44%;
    flex: none;
  }

  .shorts-layout__maps .map-card__name {
    padding: 0 12px 0 0;
  }

  /* Lifting on hover reads as a nudge in a row of cards and as a jitter in a
     stack of them, where the card above is 8px away. */
  .shorts-layout__maps .map-card:hover {
    transform: none;
  }

  /* The pre-thumbnail fallback for archived weeks: a wrapping row of links is
     fine in a column this narrow, it just needs its trailing gap back. */
  .shorts-layout__maps .shorts-maps--plain {
    margin-bottom: 0;
  }
}

/* --- Weekly Shorts: the map strip ----------------------------------------- */

/*
 * Scrolls sideways rather than wrapping.
 *
 * The five maps are a numbered sequence, and a sequence that reflows into a row
 * of three and a row of two stops reading as one - you lose the sense of "map 4
 * comes after map 3". So the cards keep their width and the strip scrolls,
 * which is also how the game presents them.
 */
.map-strip {
  list-style: none;
  display: flex;
  gap: 12px;
  margin: 0 0 18px;
  padding: 0 0 8px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* Thin, unobtrusive scrollbar where the browser offers one. */
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}

.map-strip::-webkit-scrollbar {
  height: 8px;
}

.map-strip::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 999px;
}

.map-strip__item {
  flex: 0 0 auto;
  scroll-snap-align: start;
}

.map-card {
  display: flex;
  flex-direction: column;
  width: 208px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--bg-2);
  border: 1px solid var(--line);
  text-decoration: none;
  color: var(--text);
  transition: border-color 0.15s ease, transform 0.15s ease;
}

.map-card:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.map-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/*
 * Square, because Nadeo's thumbnails are: they come back 1024x1024, being the
 * in-game map preview. A 16:9 box cropped a third of every map away, and the
 * top of a Trackmania preview is where the track is.
 *
 * Fixed here as well as on the tag so the strip does not jump about as the
 * images arrive - the attributes give the browser the ratio before the bytes
 * land, this holds the box whatever the source turns out to be.
 */
.map-card__img {
  display: block;
  width: 100%;
  /* height:auto is load-bearing. The width/height attributes on the tag are
     there so the browser reserves the right box before the image loads, but
     they also act as a presentational height - and an explicit height wins
     over aspect-ratio, which left every card 512px tall. */
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: var(--surface-2);
}

.map-card__img--none {
  /* A map with no thumbnail keeps the same shape rather than collapsing. */
  background: repeating-linear-gradient(
    -45deg,
    var(--surface-2),
    var(--surface-2) 10px,
    rgba(255, 255, 255, 0.03) 10px,
    rgba(255, 255, 255, 0.03) 20px
  );
}

.map-card__name {
  padding: 9px 11px;
  font-size: 0.84rem;
  font-weight: 600;
  line-height: 1.3;
  /* Two lines at most, so every card in the strip is the same height. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The pre-thumbnail fallback for archived weeks. */
.shorts-maps--plain {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 18px;
  padding: 0;
}

/* --- The map dialog ------------------------------------------------------- */

.map-dialog {
  width: min(360px, calc(100vw - 32px));
  padding: 22px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, 0.95);
}

.map-dialog::backdrop {
  background: rgba(6, 4, 12, 0.72);
  backdrop-filter: blur(2px);
}

.map-dialog__close {
  float: right;
  margin: -8px -8px 0 0;
}

.map-dialog__close button {
  width: 32px;
  height: 32px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 6px;
  color: var(--muted);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}

.map-dialog__close button:hover {
  background: var(--bg-2);
  color: var(--text);
}

.map-dialog__eyebrow {
  margin: 0;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--muted-dim);
}

.map-dialog__title {
  margin: 4px 0 16px;
  font-size: 1.1rem;
  line-height: 1.25;
}

.medals {
  display: grid;
  gap: 2px;
  margin: 0 0 18px;
}

.medal {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 8px 10px;
  border-radius: 7px;
  background: var(--bg-2);
}

.medal dt {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  font-size: 0.84rem;
  color: var(--muted);
}

.medal dd {
  margin: 0;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.92rem;
}

/* The in-game medal colours, so the rows are identifiable at a glance. */
.medal__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
}

.medal--author .medal__dot {
  background: #00cd63;
}

.medal--gold .medal__dot {
  background: #f5c518;
}

.medal--silver .medal__dot {
  background: #bfc4cc;
}

.medal--bronze .medal__dot {
  background: #d07a3c;
}

.map-dialog__link {
  display: inline-block;
  font-size: 0.85rem;
  color: var(--accent-ink);
}

@media (max-width: 560px) {
  .map-card {
    width: 168px;
  }
}

/* --- Admin section --------------------------------------------------------
 * admin.cartatm.com, served by src/routes/admin.tsx. A working tool for a
 * handful of organisers, so it borrows the site's tokens, buttons and fields
 * and adds only what a table of registrations and two forms need. */

.admin__bar {
  border-bottom: 1px solid var(--line);
  background: var(--chrome);
}

.admin__bar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 56px;
  flex-wrap: wrap;
}

.admin__brand {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--text);
  text-decoration: none;
}

.admin__brand span {
  color: var(--accent-soft);
  font-weight: 600;
}

.admin__who {
  display: inline-flex;
  gap: 14px;
  align-items: center;
  margin: 0;
  font-size: 0.86rem;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.admin__code {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.3);
  user-select: all;
  overflow-wrap: anywhere;
}

.admin__main {
  padding-block: clamp(24px, 4vw, 40px) 64px;
}

.admin__head {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}

.admin__title {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3vw, 2rem);
  margin: 0 0 12px;
  line-height: 1.2;
}

.admin__steps {
  margin: 0 0 16px;
  padding-left: 1.3em;
  color: var(--muted);
  line-height: 1.6;
}

.admin__steps a {
  color: var(--accent-soft);
}

.admin__head-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.admin__head .admin__title {
  margin: 0;
}

.admin__count {
  color: var(--accent-soft);
  font-variant-numeric: tabular-nums;
}

.admin__subtitle {
  font-family: var(--font-display);
  font-size: 1.1rem;
  margin: 0 0 4px;
}

.admin__hint {
  color: var(--muted);
  font-size: 0.88rem;
  margin: 0 0 14px;
}

.admin__card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(16px, 3vw, 24px);
  margin-bottom: 24px;
}

.admin__confirm {
  max-width: 560px;
}

/*
 * Lined up from the TOP, not the bottom. Password managers inject their own
 * elements into form fields, and with bottom alignment anything added below an
 * input shoved that whole field up out of line with its neighbour. From the
 * top, the labels share one line and the inputs another whatever is added
 * underneath. The button is dropped by exactly one label (its line height plus
 * the field's 7px gap) so it sits level with the inputs, and both are given
 * the same height so their edges match.
 */
.admin__add {
  --admin-label-lh: 1.5;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: 14px;
  align-items: start;
  margin-bottom: 8px;
}

.admin__add .field__label {
  line-height: var(--admin-label-lh);
}

.admin__add .field__input,
.admin__add > .btn {
  height: 48px;
}

.admin__add > .btn {
  margin-top: calc(0.92rem * var(--admin-label-lh) + 7px);
}

@media (max-width: 720px) {
  .admin__add {
    grid-template-columns: minmax(0, 1fr);
  }

  .admin__add > .btn {
    margin-top: 0;
  }
}

.admin__table-wrap {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.admin__table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
}

.admin__table th,
.admin__table td {
  padding: 10px 14px;
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--line);
}

.admin__table th {
  font-family: var(--font-display);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--surface);
}

.admin__table tbody tr:last-child td {
  border-bottom: 0;
}

.admin__table td:last-child {
  text-align: right;
}

.admin__num,
.admin__when {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.admin__player {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.admin__id {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  color: var(--muted-dim);
}

.admin__summary {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 8px 18px;
  margin: 0 0 18px;
}

.admin__summary dt {
  color: var(--muted);
}

.admin__summary dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.admin__summary code {
  font-family: var(--font-mono);
  font-size: 0.85rem;
}

.admin__actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

.admin__notice {
  border-color: rgba(62, 213, 152, 0.4);
  color: var(--text);
}

.admin__section {
  margin-top: clamp(36px, 5vw, 56px);
}

.admin__title--sub {
  font-size: clamp(1.25rem, 2.4vw, 1.6rem);
}

.admin__optional {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 0.82rem;
  color: var(--muted-dim);
}

.admin__note {
  color: var(--muted);
  max-width: 36ch;
  overflow-wrap: anywhere;
}

.admin__note-field {
  margin-bottom: 16px;
}
