/* Matchday Manager marketing/legal site — styled to match the app's design system
   (specs/style-guide.md). Dark base, one red accent, selective glow, Chakra Petch
   display + Sora body. Fonts are self-hosted (see fonts.css) — no third-party CDN,
   so the site stays cookie/tracking-free. The @font-face rules live in fonts.css,
   linked alongside this file in the page <head>. */
:root {
  /* Color tokens — mirror apps/mobile lib/core/theme/tokens.dart (soccer accent). */
  --bg: #05080a;
  --panel: #0c0f12;
  --panel-2: #14181c;
  --line: #242a30;
  --accent: #ff2b3e;
  --accent-dim: #c81020;
  --silver: #c9d2d9;
  --silver-dim: #8b97a1;
  --gold: #ffd23f;
  --text: #f4f7f9;
  --muted: #76828c;
  --up: #3ad17a;
  /* Ink printed ON an accent fill (the primary button, the skip link). A token
     rather than a literal because the two products need different answers: near
     white reads on soccer red, but not on hockey's much lighter ice-blue. See
     the product-theme block below. */
  --on-accent: var(--text);

  /* Type families. */
  --font-display: "Chakra Petch", system-ui, sans-serif;
  --font-body:
    "Sora", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial,
    sans-serif;

  /* Corner radii (style guide). */
  --r-large: 22px;
  --r-button: 16px;
  --r-chip: 14px;
  --r-pill: 30px;

  /* Accent glow — two soft layers around a brighter core (style guide). */
  --glow-accent:
    0 0 12px rgba(255, 43, 62, 0.6), 0 0 30px rgba(255, 43, 62, 0.28);

  /* Accent-derived surfaces. These carry a literal accent colour rather than
     `var(--accent)` on purpose: a custom property is inherited already
     substituted, so a nested scope that redefines --accent does NOT change a
     gradient or an rgba() that was composed up in :root. Every product theme
     therefore restates all four of these, and every rule below uses the token
     instead of writing the colour out. */
  --accent-fill: linear-gradient(180deg, #ff2b3e 0%, #c81020 100%);
  --accent-lift: rgba(255, 43, 62, 0.06); /* radial lift behind the page */
  --accent-wash: rgba(255, 43, 62, 0.1); /* tinted fill behind a current pill */

  --maxw: 64rem;
}

/* ==========================================================================
   Product themes
   ==========================================================================

   Everything above is the shared design system: dark base, glow as signal,
   Chakra Petch + Sora, gold reserved for captain/goalie identity. The ONLY
   sport-divergent brand colour is the accent (specs/hockey/style-notes.md §1),
   so a product page swaps the accent tokens and inherits the rest.

   Scope: `data-product="<key>"` on the element that owns the surface.
   `<body>` on a product cluster page (layouts/base.njk sets it from the page's
   `productKey`, and only for pages that actually live under /<locale>/<key>/),
   `<body>` on that product's invite page (join/join.njk), and the product's own
   card on the umbrella home page. Nothing else changes hands: an attribute
   scope means the football pages, the legal pages and the shared chrome keep
   the default tokens without a single override, and a third product is one
   block here plus its own attribute value.

   Adding a product: restate accent, accent-dim, on-accent, glow-accent,
   accent-fill, accent-lift and accent-wash. Nothing else belongs in the block:
   if a rule needs a colour that is not one of those, add a token first. */

/* Football, red. Byte-for-byte the :root defaults — the shared design system
   was written around the soccer accent and the football pages therefore need no
   override at all. The block exists for the one case the defaults cannot serve:
   a football surface nested INSIDE a themed page, such as the cross-promo at
   the foot of the hockey waitlist, where the game being promoted must wear its
   own colour. Custom properties inherit already substituted, so only a scope
   that restates them can undo an enclosing theme.

   Keep this in lockstep with :root. If the two ever drift, every /<locale>/sl/
   page silently changes: base.njk puts data-product="sl" on their <body>. */
[data-product="sl"] {
  --accent: #ff2b3e;
  --accent-dim: #c81020;
  --on-accent: var(--text);
  --glow-accent:
    0 0 12px rgba(255, 43, 62, 0.6), 0 0 30px rgba(255, 43, 62, 0.28);
  --accent-fill: linear-gradient(180deg, #ff2b3e 0%, #c81020 100%);
  --accent-lift: rgba(255, 43, 62, 0.06);
  --accent-wash: rgba(255, 43, 62, 0.1);
}

/* Hockey, ice-blue. Same glow mechanic (blur 12 at 0.6 alpha + blur 30 at
   0.28), hue swapped; same gold reservation; same dark base, which is why the
   <meta name="theme-color"> (site.themeColor = --bg) is shared and unchanged.

   Contrast on the near-black base is the reason the ink flips. #21c1ff on --bg
   is 10.7:1 and on --panel 10.3:1, so links, focus rings, list markers and the
   numbered steps all clear WCAG AA (and AAA) as the bright tone, with no dim
   variant needed for text (this site is dark, unlike a light-themed one). On
   top of the fill it is the other way round: near-white ink on #21c1ff is
   1.7:1, so the button label becomes the near-black #04121b the hockey mockups
   already use (specs/hockey/*.html), which reads 10.2:1 on the bright end.

   --accent-fill therefore stops short of --accent-dim: the full #21c1ff →
   #0f73b8 sweep drops that dark ink to 3.8:1 at the bottom edge. #199edf is
   those two tokens mixed 55/45, keeps the same top-down darkening and holds the
   label at 6.4:1 across the whole button. --accent-dim stays the pressed /
   deep-end token the style notes define it as. */
[data-product="nl"] {
  --accent: #21c1ff;
  --accent-dim: #0f73b8;
  --on-accent: #04121b;
  --glow-accent:
    0 0 12px rgba(33, 193, 255, 0.6), 0 0 30px rgba(33, 193, 255, 0.28);
  --accent-fill: linear-gradient(180deg, #21c1ff 0%, #199edf 100%);
  --accent-lift: rgba(33, 193, 255, 0.06);
  --accent-wash: rgba(33, 193, 255, 0.1);
}

* {
  box-sizing: border-box;
}

/* The `hidden` attribute is only a UA-stylesheet `display: none`, and ANY author
   `display` declaration outranks it — cascade origin beats specificity. That
   bit the invite page's "open the app" button, which is `.button`
   (`display: inline-block`) and stayed visible while hidden. One `!important`
   here restores what everyone assumes `hidden` means, site-wide. */
[hidden] {
  display: none !important;
}

html {
  font-family: var(--font-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body {
  margin: 0;
  color: var(--text);
  /* Near-black base with a subtle radial lift toward the top, as in the app. */
  background:
    radial-gradient(120% 60% at 50% 0%, var(--accent-lift) 0%, transparent 60%),
    var(--bg);
  min-height: 100vh;
}

a {
  color: var(--accent);
}

h1,
h2,
h3 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.15;
}

.wrap {
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: 1.25rem;
}

.skip-link {
  position: absolute;
  left: -9999px;
}
.skip-link:focus {
  left: 1rem;
  top: 1rem;
  background: var(--accent);
  color: var(--on-accent);
  padding: 0.5rem 0.75rem;
  border-radius: var(--r-chip);
  z-index: 10;
}

/* Header */
.site-header {
  border-bottom: 1px solid var(--line);
  background: rgba(5, 8, 10, 0.85);
  backdrop-filter: blur(8px);
  position: sticky;
  top: 0;
  z-index: 5;
}
.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding-block: 1rem;
}
/* Brand lockup: the umbrella wordmark plus a localized country stamp
   (partials/header.njk, and the same markup in join/join.njk).

   The wordmark is one piece of artwork in all four languages — it is what the
   app icon, the social cards and the store listings carry. The public NAME is
   not: it ends in a country word («Matchday Manager Schweiz» and its three
   siblings) because the bare wordmark is also a UK fantasy game, and that
   suffix is the whole of what tells the two apart in a search box. So the
   suffix is set as a stamp rather than typeset into the wordmark: uppercase,
   letterspaced, inside a thin rule, nudged up and rotated a few degrees, the
   way a rubber stamp or a hand-written amendment sits ON a finished letterhead
   instead of being part of the printing.

   A WRAPPING flex row, not one inline run. The two spans have to sit tight
   against each other with no whitespace in the markup (a newline inside the <a>
   would collapse to a leading space and push the wordmark off the header's left
   edge), and with no whitespace there is no break opportunity — an inline run
   would simply overflow the viewport rather than wrap. `flex-wrap` gives the
   stamp a line of its own when it does not fit, which is the honest failure
   mode on the narrowest phones. The rotation is a transform, so it takes no
   layout space of its own and cannot make the sticky header taller.

   INK, NOT ACCENT — deliberate, and please keep it that way. `--accent` is the
   one token the two products swap (see "Product themes" above) and the header
   is shared chrome rendered on every page of both clusters. An accent stamp
   would change colour as a visitor walks from /sl/ to /nl/, which reads as the
   brand changing rather than the section, and it would make the umbrella
   wordmark look like a product label in the one place that has to keep saying
   "umbrella". Faded slate is the neutral answer: it is the same ink under both
   themes, it clears AA on the near-black base, and it needs no per-product
   override. */
.brand {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 0.5em;
  row-gap: 0.2em;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.35rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text);
  text-decoration: none;
}
.brand-stamp {
  /* Baseline-aligned by the flex container, then lifted: it should ride a
     little above the wordmark, like something added after the fact. The lift is
     part of the same transform as the rotation so neither costs layout. */
  transform: translateY(-0.14em) rotate(-5deg);
  font-size: 0.55em;
  /* 600 and 700 are the two Chakra Petch weights that are actually self-hosted
     (see fonts.css); the lighter of them keeps the stamp under the wordmark. */
  font-weight: 600;
  letter-spacing: 0.16em;
  /* Asymmetric on purpose: the tracking above adds a trailing space after the
     last letter, so the left padding is the larger one and the ink still sits
     centred between the two rules. */
  padding: 0.14em 0.28em 0.14em 0.44em;
  border: 1px solid currentColor;
  border-radius: 3px;
  color: var(--silver-dim);
  white-space: nowrap;
  transition: color 0.15s ease;
}
.brand:hover .brand-stamp,
.brand:focus-visible .brand-stamp {
  color: var(--silver);
}
/* The stamp's rotation is static — nothing here animates position. The only
   transition is the hover ink, and it is dropped for the same reason the FAQ
   marker's is further down: a stated preference for no motion is taken as a
   preference for no gratuitous change at all. The hover state itself stays. */
@media (prefers-reduced-motion: reduce) {
  .brand-stamp {
    transition: none;
  }
}
/* Narrow phones: the lockup is the widest thing in the header row, and the
   stamp adds roughly half the wordmark's width to it — the longest suffix,
   "SWITZERLAND", is nearly as long as "MANAGER". Stepping the whole lockup down
   keeps both on one line on a 360px screen; the stamp is sized in `em`, so it
   follows without a rule of its own. Below that the flex row wraps and the
   stamp takes a second line, which is why nothing here needs a hard minimum. */
@media (max-width: 26rem) {
  .brand {
    font-size: 1rem;
  }
}
.site-nav {
  display: flex;
  gap: 1.25rem;
  flex-wrap: wrap;
}
.site-nav a {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.95rem;
  transition: color 0.15s ease;
}
.site-nav a:hover {
  color: var(--text);
}

/* Hero + sections */
main section {
  padding-block: 2.5rem;
}
.hero {
  padding-top: 3.5rem;
}
.hero h1 {
  font-size: clamp(2rem, 5vw, 3.4rem);
  letter-spacing: 0.01em;
  text-transform: uppercase;
  margin-bottom: 0.75rem;
  /* The big headline gets the app's accent glow — emphasis, used sparingly. */
  text-shadow: var(--glow-accent);
}
.lead {
  font-size: 1.2rem;
  color: var(--silver);
  max-width: 42rem;
}

/* Section labels (Sora, uppercase, wide tracking — the app's pill/label style). */
main h2 {
  font-size: 1.5rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* Product cards */
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1.25rem;
  margin-top: 1.5rem;
}
.product-card {
  /* 1px line border + subtle top-down gradient (panel-2 → panel), per style guide. */
  background: linear-gradient(180deg, var(--panel-2) 0%, var(--panel) 100%);
  border: 1px solid var(--line);
  border-radius: var(--r-large);
  padding: 1.75rem;
  transition:
    border-color 0.15s ease,
    transform 0.15s ease;
}
.product-card:hover {
  border-color: #333b45;
  transform: translateY(-2px);
}
.product-card h3 {
  margin-top: 0;
  font-size: 1.35rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.feature-list {
  margin-top: 1rem;
  padding-left: 1.1rem;
}
.feature-list li {
  margin-bottom: 0.4rem;
  color: var(--silver);
}
.feature-list li::marker {
  color: var(--accent);
}

.button {
  display: inline-block;
  background: var(--accent-fill);
  color: var(--on-accent);
  padding: 0.7rem 1.25rem;
  border-radius: var(--r-button);
  text-decoration: none;
  font-family: var(--font-body);
  font-weight: 700;
  letter-spacing: 0.02em;
  margin-top: 0.5rem;
  box-shadow: var(--glow-accent);
  transition: transform 0.15s ease;
}
.button:hover {
  transform: translateY(-1px);
}
.button:active {
  transform: scale(0.97);
}
/* Official App Store / Google Play badges (partials/store-badges.njk).

   The two files are the stores' own artwork, trimmed to the badge itself, so
   one height lines them up: Apple's box is 2.99:1 and Google's 3.36:1, which is
   how the stores draw them — matching *widths* instead would leave the Apple
   badge visibly taller.

   `gap` is the clear space both stores require around their mark (Apple: a
   tenth of the badge height; Google: a quarter — 0.75rem covers the stricter of
   the two at the 44px height below), and the block margin keeps the same
   distance from the copy above and below. `line-height: 0` on the link stops
   the inline box from adding a descender's worth of extra space under the
   image, which would break that symmetry.

   Height is deliberately in px, not rem: these are fixed-proportion legal marks
   rather than type, and 44px is also the iOS/Android minimum touch target. */
.store-badges {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-block: 1.25rem;
}
.store-badge {
  display: inline-block;
  line-height: 0;
  transition: transform 0.15s ease;
}
.store-badge img {
  height: 44px;
  width: auto;
}
.store-badge:hover {
  transform: translateY(-1px);
}
.store-badge:active {
  transform: scale(0.97);
}
/* Apple's and Google's trade-mark attributions, under the download section.
   One paragraph per store — each notice stands alone, and only the stores whose
   badge the page actually shows get a line. */
.trademarks {
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1.5;
}
.trademarks + .trademarks {
  margin-top: 0.35rem;
}
/* Primary call to action under the home hero copy. `.button` already carries a
   margin-top; this only opens the gap from the lead paragraph above it. */
.hero-cta {
  margin-top: 1.25rem;
}

.coming-soon {
  color: var(--muted);
  font-style: italic;
}

/* Legal / text pages.
   `.prose` is the same measure and rhythm under a name that is not "legal", so
   marketing long-form (rules, FAQ, leagues) does not have to borrow a class
   named after the privacy policy. One rule set, two selectors — they are meant
   to stay identical. */
.legal,
.prose {
  max-width: 48rem;
  padding-block: 2.5rem;
}
.legal h2,
.prose h2 {
  margin-top: 2rem;
  font-size: 1.4rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}
.prose h3 {
  margin-top: 1.75rem;
  font-size: 1.1rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--silver);
}
.legal-placeholder {
  background: var(--panel);
  border-left: 3px solid var(--accent);
  padding: 0.75rem 1rem;
  color: var(--muted);
  border-radius: var(--r-chip);
}
.legal .last-updated,
.prose .last-updated {
  color: var(--muted);
  font-size: 0.9rem;
  margin-bottom: 2rem;
}
.legal ul,
.prose ul,
.prose ol {
  padding-left: 1.25rem;
}
.legal li,
.prose li {
  margin-block: 0.4rem;
}
/* Stand-in shown where a prose partial or the FAQ array is still empty. */
.content-placeholder {
  color: var(--muted);
  font-style: italic;
}
/* Short highlighted note inside prose — an exception or a gotcha. */
.callout {
  background: var(--panel);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-chip);
  padding: 0.75rem 1rem;
  color: var(--silver);
}

/* ==========================================================================
   Product cluster: sub-nav, screenshots, tables, disclosure lists.
   Everything below is CSS-only — the site ships no JavaScript and must not
   start. Where a component would normally want script (accordion, lightbox,
   scrollspy), the markup does the work instead: <details>/<summary>, a static
   grid, a plain anchor list.
   ========================================================================== */

/* Sub-navigation pill row, shown on a product's hub and its children. Wraps
   rather than scrolling, so all four items stay reachable at 320px. */
.product-nav {
  border-bottom: 1px solid var(--line);
  background: var(--panel);
}
.product-nav ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
  padding-block: 0.75rem;
}
.product-nav a,
.product-nav [aria-current="page"] {
  display: inline-block;
  padding: 0.35rem 0.9rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  font-size: 0.9rem;
  text-decoration: none;
  color: var(--muted);
  transition:
    color 0.15s ease,
    border-color 0.15s ease;
}
.product-nav a:hover {
  color: var(--text);
  border-color: #333b45;
}
.product-nav [aria-current="page"] {
  color: var(--text);
  border-color: var(--accent);
  background: var(--accent-wash);
  font-weight: 600;
}

/* Images never overflow their column, and a lazy one reserves a panel-coloured
   box instead of flashing white. */
img {
  max-width: 100%;
  height: auto;
}

/* CSS phone frame. The committed WebP is the bare app screen — the bezel is
   drawn here, so it costs no bytes, scales with the layout and matches the
   design system instead of whatever the capture tool baked in. */
.device {
  /* `width` as well as `max-width`, and a definite width on `.shot` below, is
     the whole fix for the phone-viewport gallery bug — do not drop either.

     `.device img` is `width: 100%`. A percentage width resolves against the
     containing block, and it is *ignored* (treated as `auto` → the image's
     intrinsic width) while the browser computes an ancestor's intrinsic size.
     So wherever an ancestor of the frame is shrink-to-fit — a grid item under
     `justify-items: center`, a float, an inline-block — the chain
     `figure.shot → .device → img` had nothing definite anywhere in it, and a
     `loading="lazy"` image that has not loaded yet has NO intrinsic width.
     Result: 0×0 image, a 14px-wide frame (padding + border), and — because the
     lazy-loading heuristic never sees a box that reaches the viewport — an
     image that never loads at all. Measured on /de/sl/ at 320 and 390 CSS px:
     three 0×0 gallery images, `currentSrc` empty, `.shot-grid` 90px tall.

     Above 48rem it happened to work: `sizes` resolved to a fixed `22rem`, which
     gives the <img> a definite width of its own and hides the missing chain.

     Giving `.shot` `width: 100%` fixes it at the root. A grid *area* is always
     definite, so the percentage resolves there, and every box below it inherits
     a definite containing block. `max-width` then caps it at the frame's design
     width and the alignment property (`justify-items` / `justify-self`) centres
     what is left, exactly as before. */
  width: 100%;
  max-width: 22rem;
  border: 1px solid var(--line);
  border-radius: var(--r-large);
  padding: 6px;
  background: var(--panel);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
  overflow: hidden;
}
.device img {
  display: block;
  width: 100%;
  border-radius: calc(var(--r-large) - 8px);
  background: var(--panel-2);
}
.shot {
  margin: 0;
  /* See `.device`: this is the definite width the frame and the image resolve
     against. It applies to every context the figure is used in — `.shot-grid`,
     `.hero-split`, `.feature-row` and a bare figure in prose — rather than only
     to the one that was observed broken, so a new layout cannot reintroduce
     the bug. */
  width: 100%;
  max-width: 22rem;
}
.shot figcaption {
  margin-top: 0.6rem;
  color: var(--muted);
  font-size: 0.9rem;
  max-width: 22rem;
}

/* Two or three screenshots side by side; one column on a phone.
   `justify-items: center` makes every figure shrink-to-fit; it only works
   because `.shot` carries a definite width — see the note on `.device`. */
.shot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.5rem;
  margin-top: 1.5rem;
  justify-items: center;
}

/* Hero with the first screenshot beside the headline. Below 48rem the image
   drops under the copy; above it, the text keeps the wider column. */
.hero-split {
  display: grid;
  gap: 2rem;
  align-items: center;
}
.hero-split > .shot {
  justify-self: center;
}
@media (min-width: 48rem) {
  .hero-split {
    grid-template-columns: 1.15fr 0.85fr;
  }
}

/* Image/text row for feature sections in the prose partials. Add `.reverse` to
   put the figure on the left; alternating is opt-in per row rather than an
   :nth-of-type rule, because the author controls which element the row is and
   a structural selector silently miscounts as soon as anything else sits
   between two rows. Only the *visual* order flips — the DOM order stays
   text-then-image, so the reading order is unchanged for a screen reader and
   the one-column phone layout is always copy first. */
.feature-row {
  display: grid;
  gap: 1.5rem;
  align-items: center;
  margin-block: 2.5rem;
}
@media (min-width: 48rem) {
  .feature-row {
    grid-template-columns: 1fr 1fr;
  }
  .feature-row.reverse > .shot {
    grid-column: 1;
    grid-row: 1;
  }
}

/* Numbered "how it works" list with accent numerals. */
.steps {
  list-style: none;
  counter-reset: step;
  padding: 0;
  margin-top: 1.5rem;
}
.steps > li {
  counter-increment: step;
  position: relative;
  padding-left: 3rem;
  margin-bottom: 1.25rem;
}
.steps > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 2.1rem;
  height: 2.1rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--accent);
  color: var(--accent);
  font-family: var(--font-display);
  font-weight: 700;
}

/* In-page table of contents. Sticky only where there is room beside the
   column; no scrollspy highlighting, because that would need JavaScript. */
.toc {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-large);
  padding: 1rem 1.25rem;
  margin-block: 1.5rem;
}
.toc ul {
  list-style: none;
  padding: 0;
  margin: 0;
  columns: 2;
  column-gap: 1.5rem;
}
.toc li {
  margin-block: 0.3rem;
  break-inside: avoid;
}
@media (min-width: 64rem) {
  .toc {
    position: sticky;
    top: 5rem;
  }
}
@media (max-width: 30rem) {
  .toc ul {
    columns: 1;
  }
}

/* Scoring / rules tables. The wrapper is what keeps a wide table from blowing
   out the page on a 320px phone: the table scrolls inside its own box while the
   document itself never scrolls sideways. Give the wrapper `tabindex="0"`,
   `role="region"` and an `aria-label` in the markup so the scroll area is
   reachable by keyboard and announced — see the example in the rules prose
   stub. */
.table-scroll {
  overflow-x: auto;
  margin-block: 1.25rem;
  border: 1px solid var(--line);
  border-radius: var(--r-chip);
}
.scoring-table {
  border-collapse: collapse;
  width: 100%;
  min-width: 30rem;
  font-size: 0.95rem;
}
.scoring-table caption {
  caption-side: top;
  text-align: left;
  padding: 0.75rem 1rem;
  color: var(--silver-dim);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}
.scoring-table th,
.scoring-table td {
  padding: 0.55rem 1rem;
  border-top: 1px solid var(--line);
  text-align: left;
}
.scoring-table thead th {
  border-top: 0;
  color: var(--silver);
  font-family: var(--font-body);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
}
.scoring-table tbody th {
  font-weight: 400;
  color: var(--text);
}
.scoring-table tbody tr:nth-child(even) {
  background: var(--panel);
}
/* Signed point values: right-aligned, fixed-width digits so columns line up. */
.scoring-table .num,
.scoring-table th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* FAQ disclosure list — native <details>, no script. */
.faq {
  margin-top: 1.5rem;
  border-top: 1px solid var(--line);
}
.faq details {
  border-bottom: 1px solid var(--line);
}
.faq summary {
  cursor: pointer;
  padding: 0.9rem 2rem 0.9rem 0;
  position: relative;
  font-weight: 600;
  color: var(--text);
  list-style: none;
}
.faq summary::-webkit-details-marker {
  display: none;
}
/* Custom marker: a chevron that rotates when the disclosure opens. */
.faq summary::after {
  content: "";
  position: absolute;
  right: 0.4rem;
  top: 1.35rem;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
}
.faq details[open] summary::after {
  transform: rotate(-135deg);
}
.faq summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-chip);
}
.faq-answer {
  padding-bottom: 1rem;
  color: var(--silver);
}
.faq-answer > :first-child {
  margin-top: 0;
}
@media (prefers-reduced-motion: reduce) {
  .faq summary::after {
    transition: none;
  }
}

/* Beta framing under a TestFlight button, and the non-affiliation line. */
.note {
  color: var(--muted);
  font-size: 0.9rem;
}
.disclaimer {
  color: var(--muted);
  font-size: 0.85rem;
  max-width: 48rem;
}

/* Footer */
.site-footer {
  border-top: 1px solid var(--line);
  margin-top: 3rem;
  padding-block: 2rem;
  color: var(--muted);
}
.footer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: 1.5rem;
}
.site-footer h2 {
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--silver-dim);
}
.site-footer ul {
  list-style: none;
  padding: 0;
  margin: 0;
}
.site-footer li {
  margin-bottom: 0.35rem;
}
.site-footer a {
  color: var(--muted);
  text-decoration: none;
  transition: color 0.15s ease;
}
.site-footer a:hover {
  color: var(--text);
}
.lang-switcher [aria-current="true"] {
  color: var(--text);
  font-weight: 600;
}
.copyright {
  margin-top: 1.5rem;
  font-size: 0.85rem;
}

/* Invite landing page (/sl/join/, /nl/join/ — see src/join/join.njk).

   One page, four languages, no locale in the URL, so its header carries the
   language switcher instead of the site nav and its footer is a short legal
   row rather than the four-column grid. Everything else reuses the tokens and
   components above: `.hero`, `.button`, `.store-badges`, `.steps`, `.note`. */
.join-langs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}
.join-langs button {
  background: none;
  border: 0;
  padding: 0.25rem 0;
  font: inherit;
  font-size: 0.95rem;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.15s ease;
}
.join-langs button:hover {
  color: var(--text);
}

/* `.button` is written for an <a>; as a real <button> it also has to shed the
   user-agent border and inherit the page's type. The element has to be a
   button — it performs an action (opening the app), it is not a destination. */
button.button {
  border: 0;
  font: inherit;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
}

.join-hero {
  padding-bottom: 1rem;
}
.join-hero h1 {
  /* One notch smaller than the marketing hero: this headline carries a
     user-chosen league name that can be 40 characters long. */
  font-size: clamp(1.7rem, 4.2vw, 2.6rem);
  overflow-wrap: anywhere;
}
.join-meta {
  color: var(--silver);
  font-family: var(--font-display);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-top: -0.25rem;
}

/* The code block. It is the one thing on the page that has to survive the
   visitor switching apps to paste it, so it is big, monospaced-by-tracking and
   a single tap target. */
.invite-code {
  background: linear-gradient(180deg, var(--panel-2) 0%, var(--panel) 100%);
  border: 1px solid var(--line);
  border-radius: var(--r-large);
  padding: 1.25rem 1.5rem;
  margin-block: 1.5rem;
  max-width: 26rem;
}
.invite-code-label {
  margin: 0 0 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--silver-dim);
}
.invite-code-value {
  display: block;
  width: 100%;
  background: none;
  border: 0;
  padding: 0;
  text-align: left;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2rem, 9vw, 2.8rem);
  letter-spacing: 0.14em;
  line-height: 1.1;
  color: var(--text);
  text-shadow: var(--glow-accent);
  cursor: pointer;
  /* Long-press to select still works; this only stops the double-tap-to-zoom
     selection flicker while tapping to copy. */
  -webkit-user-select: all;
  user-select: all;
}
.invite-code-hint,
.invite-code-status {
  margin: 0.6rem 0 0;
  font-size: 0.85rem;
  color: var(--muted);
}
/* Deliberately NOT hidden while empty: it is an aria-live region, and a region
   that is display:none at the moment its text arrives is not reliably
   announced. An empty paragraph's worth of space is the price of the
   confirmation being heard. */
.invite-code-status[data-state="ok"] {
  color: var(--up);
}
.invite-code-status[data-state="error"] {
  color: var(--gold);
}
.join-invalid {
  color: var(--gold);
  max-width: 42rem;
}
.join-cta {
  margin-top: 0.5rem;
}
.join-legal {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
}

/* Visible keyboard focus for the page's three interactive controls. The rest
   of the site is links only, which the browser's own ring already covers. */
.join-langs button:focus-visible,
.invite-code-value:focus-visible,
button.button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-chip);
}

/* Waitlist mode on a product hub (partials/waitlist.njk + assets/js/waitlist.js).

   The only form on the site, so this is the only place that styles an input —
   everything else reuses the components above: `.button` for the submit and the
   two calls to action, `.store-badges` and `.trademarks` for the football
   cross-promo, `.note` for the <noscript> line. */
.waitlist {
  /* Containing block for the visually hidden honeypot below. */
  position: relative;
  margin-top: 1.5rem;
  max-width: 32rem;
}
.waitlist-label {
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--silver-dim);
  margin-bottom: 0.4rem;
}
/* Input and button side by side once there is room for both; stacked on a
   phone, where a full-width tap target beats a cramped row. */
.waitlist-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: stretch;
}
.waitlist-input {
  flex: 1 1 14rem;
  min-width: 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-button);
  color: var(--text);
  font: inherit;
  /* 16px minimum: iOS Safari zooms the viewport on focus for anything smaller,
     and never zooms back out. */
  font-size: 1rem;
  padding: 0.7rem 1rem;
}
.waitlist-input::placeholder {
  color: var(--muted);
}
.waitlist-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-color: transparent;
}
/* `.button` carries a margin-top written for a block under a paragraph; in this
   row it would push the button out of line with the input. */
.waitlist-row .button {
  margin-top: 0;
  border: 0;
  cursor: pointer;
}
.waitlist-row .button[disabled] {
  opacity: 0.55;
  box-shadow: none;
  cursor: not-allowed;
}
/* The honeypot. Visually hidden the accessible way rather than with
   `display: none` or an off-viewport offset: a bot that reads computed styles
   skips the former, and the latter can extend the scrollable area. Humans never
   reach it — it is out of the tab order and aria-hidden in the markup. */
.waitlist-hp {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
/* Deliberately NOT hidden while empty, for the same reason as the invite page's
   copy confirmation: an aria-live region that is display:none at the moment its
   text arrives is not reliably announced. */
.waitlist-status {
  margin-top: 0.9rem;
  min-height: 1.5rem;
  max-width: 42rem;
}
.waitlist-status[data-state="sent"],
.waitlist-status[data-state="confirmed"] {
  color: var(--up);
}
.waitlist-status[data-state="withdrawn"],
.waitlist-status[data-state="invalid"],
.waitlist-status[data-state="error"] {
  color: var(--gold);
}
.waitlist-privacy {
  margin-top: 0.9rem;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.5;
}
.waitlist-privacy a {
  color: var(--silver);
}
/* The football cross-promo under the form. Its own section rather than part of
   the download block, because it survives into the `sent`/`confirmed` states
   where there is no form left above it. */
.waitlist-cross {
  border-top: 1px solid var(--line);
  padding-top: 2rem;
}
/* "Warteliste" next to a product card's heading on the home page. Sized off the
   heading it sits in, so it stays a badge rather than a second title. */
.waitlist-badge {
  display: inline-block;
  vertical-align: middle;
  margin-left: 0.5rem;
  padding: 0.15em 0.6em;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--panel-2);
  color: var(--silver-dim);
  font-family: var(--font-body);
  font-size: 0.6em;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Home hero: one call to action per game (src/pages/home.njk).

   With two products the hero lists them side by side instead of pointing at an
   anchor further down the page, so the row needs a gap of its own: `.hero-cta`
   above only opens the space over a single button. It wraps rather than
   shrinking: on a 360px screen the two labels stack, each still a full-size
   touch target.

   `.button-ghost` is the same component with the fill taken off, used for every
   product after the first. The border it gains is paid for out of the padding,
   so both buttons keep the exact same box, and the label stays `--text`: the
   emphasis drops, the contrast does not. */
.hero-games {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}
.button-ghost {
  background: none;
  border: 1px solid var(--line);
  padding: calc(0.7rem - 1px) calc(1.25rem - 1px);
  box-shadow: none;
  /* Explicit, not inherited from `.button`: that one now paints `--on-accent`,
     the ink meant for a filled accent box, which under a product theme is a
     near-black that would vanish on the page background once the fill is
     removed. */
  color: var(--text);
}
.button-ghost:hover {
  border-color: var(--accent);
}
/* The rest of the site leaves link focus to the browser's own ring, which is
   fine on a text link. These are filled boxes, where the default ring hugs the
   text instead of the button, so they get the same explicit outline the invite
   page's controls use. */
.hero-games .button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* The waitlist badge is sized off the card heading it normally sits in; inside
   a button that em would land at 0.6rem, small even for a pill. */
.hero-games .waitlist-badge {
  margin-left: 0.4rem;
  font-size: 0.7em;
  color: var(--silver);
}
