/*
 * Holocron Rating Index — Design System
 * Dark theme, holocron crystal aesthetic.
 */

/* ============================================================
   DESIGN TOKENS
   ============================================================ */

:root {
  /* Colors — from Pencil design (design/hri-design.pen) */
  --bg:              #0B1120;
  --surface:         #111827;
  --card:            #1A2332;
  --card-hover:      #1E2A3D;
  --input:           #0F1A2A;
  --border:          #1E3A5F;
  --text-primary:    #F1F5F9;
  --text-secondary:  #CBD5E1;
  --text-tertiary:   #94A3B8;
  --accent:          #0EA5E9;
  --accent-hover:    #38BDF8;
  --accent-surface:  var(--accent-subtle);
  --accent-glow:     rgba(56, 189, 248, 0.2);

  /* Accent alpha scale. Use these instead of inline rgba() calls so
     hover/active/surface tints stay consistent across the stylesheet.
       wash    — almost-there tint (backgrounds, grid, hovers on dark cards)
       subtle  — chip bg, surface tint
       soft    — callout bg, active-filter bg, header underline
       mid     — focus ring halo, medium glow
       strong  — primary border, strong chip border
       hot     — reserved for #1 rank, peak states */
  --accent-wash:     rgba(14, 165, 233, 0.04);
  --accent-subtle:   rgba(14, 165, 233, 0.08);
  --accent-soft:     rgba(14, 165, 233, 0.18);
  --accent-mid:      rgba(14, 165, 233, 0.25);
  --accent-strong:   rgba(14, 165, 233, 0.4);
  --accent-hot:      rgba(14, 165, 233, 0.6);
  --positive:        #22C55E;
  --negative:        #EF4444;
  --warning:         #F59E0B;

  /* Tier colors — distinct hues for instant recognition.
     The base token is the filled-background/solid-chip color. The -hi
     variant for galactic exists because #7C3AED is too dark against
     the app surface when used as text or a thin border without a
     filled background — the other three tiers are bright enough that
     the base token works in both contexts. Use --tier-galactic for
     fills (chip backgrounds, dots) and --tier-galactic-hi for border-
     only or text-only treatments. */
  --tier-planetary:    #22C55E;
  --tier-sector:       #F59E0B;
  --tier-regional:     #0EA5E9;
  --tier-galactic:     #7C3AED;
  --tier-galactic-hi:  #A78BFA;

  /* Rank medal colors */
  --rank-gold:       #F59E0B;
  --rank-silver:     #CBD5E1;
  --rank-bronze:     #D97706;

  /* Typography — Geist family from Pencil design.
     Inter Fallback is a metric-matched local Arial defined further down in
     the @font-face block; listing it here means the page lays out at near-
     final metrics on first paint, eliminating the layout shift when Inter
     finishes loading over the Google Fonts CDN. */
  --font-display: 'Geist', system-ui, sans-serif;
  --font-body:    'Inter', 'Inter Fallback', system-ui, sans-serif;
  --font-data:    'Geist Mono', 'JetBrains Mono', monospace;

  /* Type scale (rem-based; derived from DESIGN.md). Rem so user font-size
     preferences and browser zoom scale the whole UI proportionally — px
     values would freeze the scale at 16px regardless of accessibility
     settings. The 9 main steps cover everything from nav labels to the
     page hero; the two micro tokens cover sub-readable contexts (tier
     badges, deltas in tight tables) where dropping below body size is
     intentional, not accidental. */
  --text-2xs:    0.6875rem; /* 11px — column headers, tier tag labels */
  --text-xs:     0.75rem;   /* 12px — small caption, secondary metadata */
  --text-sm:     0.8125rem; /* 13px — secondary body, table small */
  --text-base:   0.875rem;  /* 14px — body default in app contexts */
  --text-md:     1rem;      /* 16px — rank numbers, large body */
  --text-lg:     1.125rem;  /* 18px — subheadings, callout body */
  --text-xl:     1.25rem;   /* 20px — section headings */
  --text-2xl:    1.375rem;  /* 22px — page subtitles, brand mark */
  --text-3xl:    1.75rem;   /* 28px — card stat values, page H1 */
  --text-4xl:    2rem;      /* 32px — wide hero secondary */
  --text-5xl:    2.25rem;   /* 36px — profile rating display */
  --text-6xl:    2.5rem;    /* 40px — page-level hero title */
  --text-7xl:    3rem;      /* 48px — oversize stat */
  --text-8xl:    4rem;      /* 64px — mega hero (rare) */
  /* Sub-readable micro tier — for tier badges, deltas in compact rows,
     other contextual labels. Never use for paragraphs. */
  --text-micro:  0.625rem;  /* 10px */
  --text-nano:   0.5625rem; /*  9px */

  /* Line heights — semantic names, not values. Pair with type-scale tokens:
       tight    — single-line display numbers, hero ratings
       snug     — headings (h1-h3)
       normal   — UI labels, table cells, dense secondary text
       relaxed  — body paragraphs
       loose    — long-form prose (/about, /how-it-works) */
  --leading-tight:   1;
  --leading-snug:    1.2;
  --leading-normal:  1.4;
  --leading-relaxed: 1.5;
  --leading-loose:   1.7;

  /* Letter spacing — semantic. Display headings tighten slightly so the big
     letterforms feel sculpted; uppercase mono labels (column headers, tier
     tags, microcopy in caps) open up so they don't look smushed. Normal
     flow text leaves it at the font's default. */
  --tracking-tight:  -0.02em;
  --tracking-normal: 0;
  --tracking-wide:   0.06em;
  --tracking-wider:  0.08em;

  /* Spacing (8px base) */
  --space-2xs:  4px;
  --space-xs:   8px;
  --space-sm:   12px;
  --space-md:   16px;
  --space-lg:   24px;
  --space-xl:   32px;
  --space-2xl:  48px;
  --space-3xl:  64px;

  /* Border radii */
  --radius-sm:   4px;
  --radius-md:   8px;
  --radius-lg:   12px;
  --radius-full: 20px;
}

/* ============================================================
   WEB FONT FALLBACKS
   ============================================================ */

/* Metric-matched fallback for Inter. Google Fonts loads Inter asynchronously
   (display=swap) — without this, the initial paint uses Arial at Arial's
   natural metrics, and the page reflows when Inter finishes loading. The
   overrides below tune local('Arial') to match Inter's x-height, cap-height,
   and advance widths, so the shift on swap is near-invisible.

   Values published by Vercel (next/font's built-in Inter fallback config);
   verified against Inter 4.x on macOS. Geist and Geist Mono don't get a
   matched fallback here because publishing tuned values for them would need
   empirical measurement this commit hasn't done — they keep the naive
   system-ui / monospace fallback, which means a small shift on first load
   for display and data text. Acceptable tradeoff: body prose is the main
   reading surface and it's the one most hurt by layout shift. */
@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

/* ============================================================
   RESET / BASE
   ============================================================ */

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

/* tabular-nums is an inherited property and applies to whichever font
   actually renders the digits — so setting it on data-heavy page wrappers
   gives every number inside (rating, RD, win%, rank, deltas) tabular
   figures automatically, without re-declaring the rule per-cell. Wrappers
   listed are the leaderboard table, profile/audit/H2H pages, and both
   tournament views. New data-dense surfaces should join this list rather
   than re-declaring on individual cells. */
.table-container,
.leaderboards-show,
.profile-page,
.player-vs,
.player-audit,
.tournaments-index,
.tournament-show {
  font-variant-numeric: tabular-nums;
}

html {
  /* 100% (not 16px) so the whole rem-based type scale honors the user's
     browser font-size preference. Users who've bumped their default to
     20px in browser settings get the entire UI scaled up 25%; users on
     default 16px see the nominal sizes. Pure accessibility win with no
     visual cost for default-preference users.

     color-scheme: dark opts the whole document into dark UA styling so
     native controls (scrollbars, <select> chevrons, autofill flash,
     datepickers) render with dark form factors instead of fighting the
     surface — DESIGN.md is explicit that this is a dark-only product. */
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
  color-scheme: dark;
}

body {
  font-family: var(--font-body);
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  background-color: var(--bg);
  color: var(--text-primary);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  isolation: isolate;
  /* overflow-x: clip prevents absolute-positioned .has-tooltip pseudo-
     elements (320px max-width) near the right edge of narrow viewports
     from inflating the document's scrollable width. clip, not hidden, so
     no scroll container is created (sticky headers + position:sticky still
     work against the viewport). Without this, iPhone widths (390px) get a
     phantom 260px of horizontal scroll where tooltip tails extend past the
     page edge. */
  overflow-x: clip;
  /* Inter OpenType features. cv11 swaps the default double-story 'a' for a
     single-story humanist form — tiny lift that takes Inter from
     generic-SaaS to a touch more distinctive. ss03 is Inter's "rounder
     quotes" set; leaving it off for now but easy to add. Cost: zero,
     since OTF features are encoded in the font itself. */
  font-feature-settings: 'cv11';
  font-kerning: normal;
}

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

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

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

table {
  border-collapse: collapse;
}

/* Global :focus-visible baseline. Keyboard focus is safety gear — it should
   look the same everywhere unless a component has a specific reason to
   override. 2px accent outline with 2px offset gives strong visibility on
   the dark surface without competing with interactive colors; inheriting
   the component's border-radius keeps square corners on pills/chips and
   rounded corners on buttons. Individual rules that deviate from this
   baseline should be rare and commented to explain why. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: inherit;
}
:focus:not(:focus-visible) { outline: none; }

/* ============================================================
   BACKGROUND SURFACE — schematic grid
   Faint tactical-display grid fixed to the viewport, masked
   to fade from top-center outward. Sits behind all content.
   ============================================================ */

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(var(--accent-subtle) 1px, transparent 1px),
    linear-gradient(90deg, var(--accent-subtle) 1px, transparent 1px);
  background-size: 48px 48px;
  -webkit-mask-image: radial-gradient(ellipse at top, black 0%, transparent 70%);
          mask-image: radial-gradient(ellipse at top, black 0%, transparent 70%);
}

/* ============================================================
   LAYOUT
   ============================================================ */

.container {
  width: 100%;
  max-width: 1440px;
  margin-inline: auto;
  padding-inline: 32px;
}

main {
  flex: 1;
}

.content-area {
  padding: 32px 80px 48px;
}

/* ============================================================
   SITE HEADER (matches Pencil NavBar component)
   ============================================================ */

/* Skip-link: pushed off-screen until focused, then anchors to the top-left
   of the viewport in the accent color so it's impossible to miss. Uses
   visually-hidden-until-focus pattern instead of display:none so screen
   readers always announce it. */
.skip-link {
  position: absolute;
  top: -999px;
  left: var(--space-xs);
  z-index: 200;
  padding: var(--space-xs) var(--space-md);
  background-color: var(--accent);
  color: var(--bg);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
  border-radius: var(--radius-md);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
.skip-link:focus,
.skip-link:focus-visible {
  top: var(--space-xs);
  outline: 2px solid var(--accent-hover);
  outline-offset: 2px;
}

/* <main> receives programmatic focus when the skip-link fires. Suppress the
   default focus ring — the ring belongs on the skip-link, not on the whole
   content region. */
main:focus {
  outline: none;
}

.site-header {
  background-color: var(--surface);
  position: sticky;
  top: 0;
  z-index: 100;
}

.site-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 56px;
  padding: 0 32px;
}

.nav-left { display: flex; align-items: center; }

.site-logo {
  display: flex;
  align-items: center;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  transition: color 0.15s ease;
}

.site-logo:hover {
  color: var(--accent);
}

.nav-logo-img {
  height: 36px;
  width: auto;
  transition: opacity 0.15s ease;
}

.site-logo:hover .nav-logo-img { opacity: 0.85; }

.nav-links {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
}

.nav-link {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-secondary);
  transition: color 0.15s ease;
  /* Desktop: vertical padding on the nav link expands the tap target to a
     comfortable hit box without inflating the visual label. Mobile scales
     this up further (see media query below). */
  padding-block: var(--space-sm);
}

.nav-link:hover {
  color: var(--text-primary);
}

.nav-link--active {
  color: var(--accent);
  font-weight: 600;
}

/* ---------- Overflow nav (mobile) ---------------------------------- */
/* At desktop width, the <details> overflow is hidden — "How It Works",
   "Changelog", and "Feedback" appear inline in .nav-links. Below 640px
   those three collapse behind a "More ▾" disclosure so the flat row on a
   390px iPhone stays at Logo + Rankings + Tournaments + More, fitting
   without horizontal overflow. The <details> pattern matches the site's
   other disclosures (weekly drawer, bracket, format notice). */
.nav-overflow { display: none; position: relative; }
.nav-overflow > summary { list-style: none; cursor: pointer; }
.nav-overflow > summary::-webkit-details-marker { display: none; }

.nav-overflow__trigger {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-secondary);
  padding-block: var(--space-sm);
  padding-inline: var(--space-xs);
  border-radius: var(--radius-sm);
  transition: color 0.15s ease, background-color 0.15s ease;
  min-height: 44px;
  line-height: 1;
}
.nav-overflow__trigger:hover,
.nav-overflow[open] .nav-overflow__trigger {
  color: var(--text-primary);
  background-color: color-mix(in oklch, var(--border) 40%, transparent);
}
.nav-overflow__caret {
  font-size: var(--text-xs);
  transition: transform 0.15s ease;
}
.nav-overflow[open] .nav-overflow__caret { transform: rotate(180deg); }

.nav-overflow__panel {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  min-width: 200px;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
  padding: var(--space-2xs);
  z-index: 110;
}

.nav-overflow__item {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 10px var(--space-sm);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-secondary);
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-align: left;
  min-height: 44px;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.nav-overflow__item:hover {
  color: var(--text-primary);
  background-color: color-mix(in oklch, var(--border) 40%, transparent);
}
.nav-overflow__item--active { color: var(--accent); font-weight: 600; }

/* Mobile tap-target discipline. Below 640px the primary nav, format pills,
   and pagination controls all need at least 44px of hit surface per
   WCAG/Apple HIG. We add it via padding instead of fixed box sizes so the
   visual rhythm of the labels is preserved; touch surfaces grow, visual
   chrome doesn't. */
@media (max-width: 640px) {
  .site-nav { padding: 0 var(--space-md); }
  .nav-links { gap: var(--space-sm); }
  .nav-link {
    padding-block: var(--space-sm);
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  /* Hide the flat-row secondary links on mobile — they live in .nav-overflow. */
  .nav-link--secondary { display: none; }
  .nav-overflow { display: inline-block; }
  .chip {
    min-height: 40px;
    padding-inline: var(--space-md);
  }
  .page-btn {
    min-width: 40px;
    height: 40px;
  }
}

/* ============================================================
   SITE FOOTER
   ============================================================ */

.site-footer {
  background-color: var(--surface);
  border-top: 1px solid var(--border);
  padding-block: var(--space-lg);
  margin-top: auto;
}

.footer-text {
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  text-align: center;
  line-height: var(--leading-relaxed);
}

.footer-link {
  color: var(--text-secondary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.footer-link:hover {
  color: var(--text-primary);
}

/* ============================================================
   TYPOGRAPHY
   ============================================================ */

.heading-xl {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 700;
  color: var(--text-primary);
  line-height: var(--leading-snug);
  letter-spacing: -0.01em;
}

.heading-lg {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--text-primary);
  line-height: var(--leading-snug);
}

.label {
  font-family: var(--font-body);
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--text-tertiary);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
}

/* ============================================================
   DATA TYPOGRAPHY
   ============================================================ */

.data {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--text-primary);
}

.data-lg {
  font-family: var(--font-data);
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--text-primary);
}

/* ============================================================
   RATING NUMBERS
   Glow ONLY on hero and table rating — no glow elsewhere.
   ============================================================ */

.rating-number {
  font-family: var(--font-data);
  font-weight: 700;
  color: var(--accent);
  line-height: var(--leading-tight);
}

/* Hero: large glow (profile page, featured display) */
.rating-number--hero {
  font-size: var(--text-7xl);  /* 48px */
  text-shadow: 0 0 12px var(--accent-glow);
}

/* Table: subtle glow in the ratings column */
.rating-number--table {
  font-size: var(--text-sm);
  text-shadow: 0 0 8px var(--accent-glow);
}

/* ============================================================
   RATING CHANGE
   ============================================================ */

.rating-change {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-weight: 500;
}

.rating-change--positive {
  color: var(--positive);
}

.rating-change--negative {
  color: var(--negative);
}

/* ============================================================
   RATING DEVIATION
   ============================================================ */

.rd {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
}

/* ============================================================
   HERO SECTION (Rankings page top)
   ============================================================ */

/* Vertical stack: logo anchors top, title + subtitle as a lockup below,
   stats strip at the bottom. Three clear tiers with generous gap between
   them (--space-xl = 32px) so the hero reads as a composition, not a
   single crowded row. */
.hero-section {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xl);
  padding: 48px 80px 40px;
}

/* Bottom divider inset to match the content-area's horizontal padding,
   so the line aligns with the visible edges of the table rows below
   instead of running edge-to-edge of the hero's own 1280px box. Media
   queries below track the responsive padding changes. */
.hero-section::after {
  content: '';
  position: absolute;
  left: 80px;
  right: 80px;
  bottom: 0;
  height: 1px;
  background: var(--border);
}

/* Recency status line under the stats strip. Reads like a terminal
   heartbeat — tiny pulse dot + "Last sync · <date> · N days ago" in
   mono. Lichess/chess.com have a similar "engine online" trust cue;
   this is HRI's version. */
.system-status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  margin: 0;
  padding: 6px var(--space-sm);
  font-family: var(--font-data);
  /* 12px (not 11px) so the combined recency + stats line stays readable
     post-tournament when players are scanning the whole row at once. The
     two-tone treatment (tertiary labels + secondary values) carries the
     hierarchy so the density stays tight. */
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  color: var(--text-tertiary);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: color-mix(in oklch, var(--card) 60%, transparent);
}

.system-status__pulse {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--positive);
  box-shadow: 0 0 6px color-mix(in oklch, var(--positive) 60%, transparent);
  animation: system-status-pulse 2s ease-in-out infinite;
}

.system-status__label { text-transform: uppercase; }
.system-status__value { color: var(--text-secondary); font-weight: 600; }
.system-status__ago { opacity: 0.8; }
/* Scope segments (players / tournaments / matches) fused onto the same
   system-status line. Mono, tertiary. The bare "·" separators get a
   lighter tint so the three counts feel like one row of data, not three
   separate chips. */
.system-status__sep { opacity: 0.5; }
.system-status__stat { color: var(--text-secondary); font-weight: 500; }

@keyframes system-status-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.55; transform: scale(0.9); }
}

.hero-lockup {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  min-width: 0;
  text-align: center;
}

/* Restored brand mark above the hero lockup. Sized tighter than the
   original (96px vs 128px) and stripped of the cyan drop-shadow halo so
   it reads as "brand crest, once per site" rather than the illustrated
   fan-art moment it had before. Purely decorative — aria-hidden so
   screen readers skip straight to the H1. */
.hero-logo {
  height: 96px;
  width: auto;
  flex-shrink: 0;
  display: block;
}

.hero-title {
  font-family: var(--font-display);
  font-size: var(--text-5xl);
  font-weight: 700;
  letter-spacing: -0.03em;
  color: var(--text-primary);
  margin: 0;
  line-height: var(--leading-tight);
  text-wrap: balance;
}

.hero-sub {
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: 400;
  color: var(--text-secondary);
  margin: 0;
  letter-spacing: 0.005em;
  line-height: var(--leading-normal);
}

.hero-sub strong {
  color: var(--accent);
  font-weight: 600;
}

/* ============================================================
   TOURNAMENTS INDEX — layout rhythm
   Scoped overrides to cap content width, restore hero hierarchy,
   and stack filter concerns vertically so the page has a proper
   heading / controls / table / footer rhythm instead of a single
   undifferentiated slab of rows.
   ============================================================ */

/* Cap the table column at a comfortable reading width on 1440+ screens.
   Below 1280px the rule is a no-op (max-width kicks in only above the
   cap); above it, the row text stops running the full monitor width. */
.tournaments-index {
  max-width: 1280px;
  margin-inline: auto;
}

/* Hero strip: the title earns real display-size weight; the count
   sits immediately next to it like a dateline ("Tournaments · 799
   rated events"), not stretched to the far edge where it floats
   disconnected. The shared .section-header default is space-between
   (leaderboard pairs title with a right-aligned control); on this
   page the count is a supporting stat for the title, so flush-start. */
.tournaments-index .section-header {
  justify-content: flex-start;
  align-items: baseline;
  gap: var(--space-md);
  margin-top: var(--space-xl);
  margin-bottom: var(--space-xl);
}

.section-header__title {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 700;
  color: var(--text-primary);
  line-height: var(--leading-tight);
  letter-spacing: -0.01em;
  text-wrap: balance;
}

.section-header__meta {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
}

.section-header__count {
  font-family: var(--font-data);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-secondary);
  letter-spacing: 0.01em;
}

/* (filtered) is a link to clear every filter in one shot. The dotted
   underline appears only on hover so the default state reads as a quiet
   status marker, not a blue web link. Chrome becomes functionality. */
.section-header__filtered {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  margin-left: var(--space-2xs);
  text-decoration: none;
  border-bottom: 1px dotted transparent;
  transition: border-color 150ms ease;
}

a.section-header__filtered:hover,
a.section-header__filtered:focus-visible {
  border-bottom-color: color-mix(in oklch, var(--accent) 60%, transparent);
  outline: none;
}

/* Search input wrap — holds the field and the `/` keyboard-shortcut
   badge. Position: relative so the badge can absolute-position at the
   right edge without knocking the input's own padding. The badge stays
   out of the way of typed input via right-side padding on the input. */
.search-input-wrap {
  position: relative;
  width: 100%;
  max-width: 480px;
}

.search-input-wrap .search-input {
  width: 100%;
  padding-right: 36px;
}

/* Leading-icon variant: pairs a magnifying-glass SVG on the left side of
   the input with the standard wrap. Used on the leaderboard search so the
   input has a persistent visual label (the icon) in addition to aria-label
   — fixes the placeholder-as-only-label anti-pattern. */
.search-input-wrap--leading .search-input__icon {
  position: absolute;
  top: 50%;
  left: var(--space-sm);
  transform: translateY(-50%);
  color: var(--text-tertiary);
  pointer-events: none;
  transition: color 150ms ease;
}
.search-input-wrap--leading:focus-within .search-input__icon {
  color: var(--accent);
}
.search-input.search-input--with-leading {
  padding-left: 38px;
}

.search-input__hint {
  position: absolute;
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--text-tertiary);
  background: color-mix(in oklch, var(--border) 60%, transparent);
  border: 1px solid var(--border);
  border-radius: 4px;
  pointer-events: none;
  opacity: 0.8;
  transition: opacity 150ms ease, color 150ms ease;
}

.search-input-wrap:focus-within .search-input__hint {
  opacity: 0;
}

/* Hide the hint on small screens — mobile users don't have a keyboard
   and the badge would just eat input padding. */
@media (max-width: 640px) {
  .search-input__hint { display: none; }
  .search-input-wrap .search-input { padding-right: 16px; }
}


/* Filter stack — search + two labeled groups stacked vertically.
   Related rows sit 12px apart; the whole stack separates from the
   table below by a generous 32px so the table reads as a distinct
   block with real whitespace above and below it. */
.filter-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-bottom: var(--space-xl);
}

.filter-stack__search .search-input {
  width: 100%;
  max-width: 480px;
}

.filter-stack__group {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.filter-stack__label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  min-width: 56px;
}

/* Row height matches DESIGN.md spec for TournamentRow (52px).
   The shared .rank-row base is 56px for the leaderboard where the
   rating number wants more presence; tournaments are a denser list
   and earn the 4px back. Also adds an inter-column gap so adjacent
   cells (Players/Winner, Winner/Date, Tier/Format) don't collide —
   numeric and text values had been butting up against each other
   at the cell boundary with no visual breath. Header matches so
   column labels stay perfectly aligned with row cells. Mobile
   override below restores the tighter 2-line card gaps so the
   compact card layout keeps its density. */
.tournaments-index .tournament-row,
.tournaments-index .table-header {
  gap: var(--space-md);
}

.tournaments-index .tournament-row {
  height: 52px;
}

/* Mobile: release the fixed 52px desktop height so the 2-3 line wrap
   card can grow to fit its content. The base .tournament-row mobile
   rule (below, in the main responsive block) sets height: auto too,
   but this scoped selector has higher specificity and wins — without
   this override, long winner usernames overflow the row and collide
   with the next card's title. Gaps pulled in here so the whole mobile
   behavior for tournaments-index is visible in one place. */
@media (max-width: 640px) {
  .tournaments-index .tournament-row {
    height: auto;
    row-gap: 6px;
    column-gap: 10px;
  }
}

/* Pagination breathing room — reads as a page footer, not as row 51. */
.tournaments-index .pagination {
  padding-top: var(--space-xl);
  margin-top: var(--space-sm);
}

/* Small-screen stack: the filter-stack__group wraps its label above
   the chip row so chips still get a full horizontal scroll lane.
   Mobile table-header sticky offset is handled elsewhere. */
@media (max-width: 640px) {
  .filter-stack__group {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2xs);
  }
  .tournaments-index .section-header {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2xs);
    margin-top: var(--space-lg);
    margin-bottom: var(--space-lg);
  }
  .section-header__title {
    font-size: var(--text-2xl);
  }
}

/* ============================================================
   FILTER ROW (search + chips) — legacy, still used elsewhere
   ============================================================ */

.filter-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 20px;
}

.filter-row .search-input {
  width: 480px;
  max-width: 480px;
}

.filter-chips {
  display: flex;
  gap: 8px;
  align-items: center;
}

.chip {
  display: inline-flex;
  align-items: center;
  padding: 6px 16px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background-color: var(--card);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  /* Narrowed from `all` to the three properties that actually change on
     hover/active so the browser doesn't have to recompute width/height
     every frame — the detector's layout-transition flag was tripping
     here. transform is separate because the 60ms active-press ramp has a
     different duration. */
  transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease;
  white-space: nowrap;
}

.chip:hover {
  border-color: var(--accent);
  color: var(--text-primary);
}

/* Tactile press — chip scales down a hair on click-and-hold and springs
   back. 60ms is under the perceptual threshold of "laggy" but long enough
   to feel the click. Respects prefers-reduced-motion. */
.chip:active {
  transform: scale(0.96);
  transition: transform 0.06s ease;
}

@media (prefers-reduced-motion: reduce) {
  .chip:active { transform: none; }
}

.chip--active {
  border-color: var(--accent);
  background-color: var(--accent);
  color: var(--bg);
  font-weight: 600;
}

/* "Clear all" escape hatch — only rendered when at least one filter is
   active (search, follow, non-default sort). Visually quiet since the
   row already signals active state via the Following chip's gold glow
   and the active format chip's blue fill; the Clear chip is just the
   opt-out. */
.chip--clear {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--text-tertiary);
}
.chip--clear:hover {
  border-color: var(--negative);
  color: var(--negative);
}
.chip--clear__x {
  font-family: var(--font-data);
  font-size: var(--text-md);
  line-height: 1;
  opacity: 0.75;
  transition: opacity 150ms ease;
}
.chip--clear:hover .chip--clear__x { opacity: 1; }

/* ============================================================
   PINNED ROW — "Following" hero pill for single-follower users
   ============================================================
   Compact data pill above the leaderboard answering "where am I?" the
   moment the page loads. Renders only when the Following cookie holds
   exactly one slug — 80% case of the user following themselves. Mono
   numerals keep it visually aligned with the table; gold accent ties it
   to the Follow system's existing gold rail (matches the chip--follow
   star treatment below). Links to the pinned player's profile. */
.pinned-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  margin: 0 0 var(--space-md);
  padding: 12px var(--space-md);
  background: color-mix(in oklch, var(--rank-gold) 6%, var(--card));
  border: 1px solid color-mix(in oklch, var(--rank-gold) 25%, var(--border));
  border-left: 3px solid var(--rank-gold);
  border-radius: var(--radius-md);
  color: inherit;
  text-decoration: none;
  transition: background-color 150ms ease, border-color 150ms ease;
}
.pinned-row:hover {
  background: color-mix(in oklch, var(--rank-gold) 10%, var(--card));
  border-color: color-mix(in oklch, var(--rank-gold) 45%, var(--border));
}

.pinned-row__label {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 700;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--rank-gold);
}

.pinned-row__rank {
  font-family: var(--font-data);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.pinned-row__name {
  flex: 1;
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pinned-row__rating {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  font-family: var(--font-data);
}
.pinned-row__rating-value {
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.pinned-row__rating-ci {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

.pinned-row__format {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  color: var(--text-tertiary);
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

@media (max-width: 640px) {
  .pinned-row { gap: 10px; flex-wrap: wrap; }
  .pinned-row__name { flex-basis: 100%; order: 3; }
  .pinned-row__rating { margin-left: auto; }
}

/* Help affordance that sits immediately to the left of the Overall chip.
   Quiet outline info icon (stroke-based SVG matching the search icon and
   the Following chip's star) — reads as "universal info button" at a
   glance, doesn't compete with the four format filters for attention.
   Opens the rankings info-dialog. */
.chip-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-tertiary);
  cursor: help;
  border-radius: 999px;
  transition: color 150ms ease, background-color 150ms ease;
}
.chip-help:hover,
.chip-help:focus-visible {
  color: var(--accent);
  background: color-mix(in oklch, var(--accent) 10%, transparent);
  outline: none;
}
.chip-help svg { display: block; }
.chip-help:active { transform: scale(0.92); }
@media (prefers-reduced-motion: reduce) {
  .chip-help:active { transform: none; }
}

/* Tier-colored chip variants. Inactive state uses the tier hue for text +
   hover border; active state fills with the tier color (matching the same
   tier's badge on the tournament rows below). */
.chip--tier-planetary { color: var(--tier-planetary); }
.chip--tier-planetary:hover { border-color: var(--tier-planetary); color: var(--tier-planetary); }
.chip--tier-planetary.chip--active { background-color: var(--tier-planetary); border-color: var(--tier-planetary); color: var(--bg); }

.chip--tier-sector { color: var(--tier-sector); }
.chip--tier-sector:hover { border-color: var(--tier-sector); color: var(--tier-sector); }
.chip--tier-sector.chip--active { background-color: var(--tier-sector); border-color: var(--tier-sector); color: var(--bg); }

.chip--tier-regional { color: var(--tier-regional); }
.chip--tier-regional:hover { border-color: var(--tier-regional); color: var(--tier-regional); }
.chip--tier-regional.chip--active { background-color: var(--tier-regional); border-color: var(--tier-regional); color: var(--bg); }

.chip--tier-galactic { color: var(--tier-galactic); }
.chip--tier-galactic:hover { border-color: var(--tier-galactic); color: var(--tier-galactic); }
.chip--tier-galactic.chip--active { background-color: var(--tier-galactic); border-color: var(--tier-galactic); color: var(--bg); }

/* Format chips are intentionally neutral — see leaderboards/show.html.erb
   for the rationale. The .chip--fmt-* color variants and the --fmt-*
   tokens were removed: tier chips do the colored-chip work, and a
   single-color-per-row policy keeps the page calm. */

/* FOLLOW FILTER CHIP
   Gold-accented "Following (N)" toggle that scopes the leaderboard to the
   player's locally-stored follow list. Lives next to the format chips
   visually but does a different job (scope, not format switch), so it
   gets its own color (gold) matching the ★ Follow button on profile
   pages and the gold rail on followed rows. Inactive state is muted so
   the gold only appears when the filter is actually on. */
.chip--follow {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text-secondary);
}
.chip--follow:hover {
  border-color: var(--rank-gold);
  color: var(--rank-gold);
}
.chip--follow__star {
  flex-shrink: 0;
  color: var(--rank-gold);
  opacity: 0.75;
  transition: opacity 150ms ease;
}
.chip--follow:hover .chip--follow__star,
.chip--follow-active .chip--follow__star {
  opacity: 1;
}
.chip--follow__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 700;
  background: color-mix(in oklch, var(--border) 60%, transparent);
  border-radius: 9px;
  color: var(--text-tertiary);
}

/* Active — filter on. Gold fill, dark text, count pill inverts. Matches
   the .chip--active treatment used elsewhere but keyed to the gold token
   since we're in follow-signal land, not rating-format land. */
.chip--follow-active {
  background: color-mix(in oklch, var(--rank-gold) 18%, var(--card));
  border-color: var(--rank-gold);
  color: var(--rank-gold);
}
.chip--follow-active .chip--follow__star {
  opacity: 1;
  text-shadow: 0 0 8px color-mix(in oklch, var(--rank-gold) 70%, transparent);
}
.chip--follow-active .chip--follow__count {
  background: var(--rank-gold);
  color: var(--bg);
}

/* Subtle separator between the follow toggle and the format chips — reads
   as a row divider without adding a visual element when the follow chip
   isn't present. */
.filter-chips__divider {
  display: inline-block;
  width: 1px;
  height: 20px;
  background: var(--border);
  margin-inline: var(--space-2xs);
  opacity: 0.6;
}

/* ============================================================
   SEARCH INPUT
   ============================================================ */

.search-input {
  display: block;
  padding: 0 16px;
  background-color: var(--input);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-size: var(--text-md);
  color: var(--text-primary);
  outline: none;
  transition: border-color 0.15s ease;
  height: 44px;
}

.search-input::placeholder { color: var(--text-tertiary); }
.search-input:focus { border-color: var(--accent); }

/* ============================================================
   TABLE CONTAINER (flex-based rows, not <table>)
   Matches Pencil RankRow / TournRow components
   ============================================================ */

.table-container {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.table-header {
  display: flex;
  align-items: center;
  /* Column breathing room. Data columns (Rating, Record, Winrate, Events,
     Confidence) sit flush against each other without this — the right edge
     of the table reads as one cramped data block. 16px gives each metric
     its own visual cell while staying tight enough for scanning down a
     column. The same gap is set on .rank-row below so headers and data
     align column-for-column. */
  gap: 16px;
  height: 40px;
  padding: 0 20px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: 0.5px;
  text-transform: uppercase;
  /* Sticks directly below the fixed site nav (56px desktop, 52px mobile) so
     column labels survive scrolls past rank ~15. Background matches --bg so
     the header doesn't render transparent when the sticky kicks in. Mobile
     top offset is handled in the <=640px breakpoint below. */
  position: sticky;
  top: 56px;
  z-index: 10;
  background: var(--bg);
}

@media (max-width: 640px) {
  .table-header { top: 52px; }
}

/* Profile pages already have their own sticky header (.profile-sticky) in
   the same vertical slot — stacking another sticky layer under it just
   obscures the column labels. Drop the sticky behavior here; profile
   tables are paginated to 10 rows so the column labels are rarely
   scrolled out of view anyway. */
.profile-page .table-header {
  position: static;
  top: auto;
}

/* Content-width cap — same 1280px envelope as .tournaments-index and
   .tournament-show. Keeps every top-level user-facing view optically
   aligned on wide monitors: rankings, profile, per-event audit, and both
   tournament pages all center on the same vertical column. Below 1280px
   the rule is a no-op (max-width only kicks in when the viewport is
   wider). Tables inside (all on the shared .table-container primitive)
   inherit the constraint without any per-component width changes.

   The leaderboard's .hero-section sits OUTSIDE .content-area in the DOM,
   so it gets its own matching cap — otherwise the banner would run
   full-bleed while the filter/table below it sat in the middle, with
   mismatched left edges. Capping it here keeps the whole page on one
   centerline. */
.leaderboards-show,
.profile-page,
.player-audit,
.player-vs {
  max-width: 1280px;
  margin-inline: auto;
}

.hero-section {
  max-width: 1280px;
  margin-inline: auto;
}

/* Rank row (leaderboard) */
.rank-row {
  display: flex;
  align-items: center;
  /* Match the header's column gap so cells line up vertically — see
     .table-header for the rationale. */
  gap: 16px;
  height: 56px;
  padding: 0 20px;
  background: var(--card);
  border-radius: 6px;
  text-decoration: none;
  transition: background 0.1s ease;
  color: inherit;
}

.rank-row:hover { background: var(--card-hover); }

.rank-row--empty {
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 4px;
  height: auto;
  min-height: 72px;
  padding: 16px 20px;
  color: var(--text-tertiary);
  font-size: var(--text-sm);
  text-align: center;
}

.rank-row--empty:hover { background: var(--card); }

.rank-row--empty__headline {
  margin: 0;
  color: var(--text-secondary);
  font-weight: 500;
}

.rank-row--empty__recovery {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

.rank-row--empty__link {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px dotted color-mix(in oklch, var(--accent) 40%, transparent);
  transition: border-color 150ms ease;
}

.rank-row--empty__link:hover,
.rank-row--empty__link:focus-visible {
  border-bottom-color: var(--accent);
  outline: none;
}

/* On desktop, .rank-meta and .tourn-meta are invisible to layout — their
   children participate in the row's flex container directly, matching the
   table-header column widths. On mobile they collapse into a second row. */
.rank-meta, .tourn-meta { display: contents; }

/* Tournament row (profile) */
.tourn-row {
  display: flex;
  align-items: center;
  /* Match the 16px column gap on .table-header so every column in the data
     row lines up vertically with its header above. Without this, tier /
     record / rating / +/- / date drifted far right of their labels because
     the header had gaps between columns and the row did not. */
  gap: 16px;
  height: 52px;
  padding: 0 20px;
  background: var(--card);
  border-radius: 6px;
}

/* Column widths — rank table.
   Rank column sized to fit 5 digits at 16px Geist Mono Bold (~48px content)
   plus right-side breathing room before the player name starts. Right-aligned
   so numbers stay visually flush as digit count changes. */
/* Every numeric column right-aligns on desktop so digits line up vertically
   down the table. tabular-nums keeps same-digit counts at the same pixel
   width — critical for the leaderboard where small differences between
   rating numbers should read at a glance. */
.th-rank, .rank-num    { width: 72px; padding-right: 16px; text-align: right; font-variant-numeric: tabular-nums; }
/* Player flexes but caps at 360px so it doesn't gobble all the slack on a
   1280px viewport (handles + usernames rarely exceed 40 chars). The leftover
   space is absorbed by margin-left: auto on .th-rating / .rank-rating below,
   which keeps the data block anchored to the row's right edge instead of
   stranding empty space mid-row. */
.th-player, .rank-player { flex: 1 1 0; min-width: 0; max-width: 360px; }
.th-rating, .rank-rating { width: 80px; margin-left: auto; text-align: right; font-variant-numeric: tabular-nums; }
/* W-L is centered because the hyphen is the natural visual anchor for both
   the header label ("W-L") and the data ("12-5", "100-50") — the digit count
   varies but the dash sits roughly mid-cell either way. Right-align gave the
   cell a hard right edge that visually merged into the next column. */
.th-record, .rank-record { width: 70px; text-align: center; font-variant-numeric: tabular-nums; }
.th-winrate, .rank-winrate { width: 60px; text-align: right; font-variant-numeric: tabular-nums; }
.th-form, .rank-form {
  width: 56px;
  text-align: center;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--text-tertiary);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
}
.th-events, .rank-events { width: 60px; text-align: right; font-variant-numeric: tabular-nums; }
.th-confidence, .rank-confidence { width: 90px; text-align: right; font-variant-numeric: tabular-nums; }

/* Spark-dots inside .rank-form. Each dot is a 5px pill (slightly taller
   than wide so the "strip" reads as vertical marks, not circles). Newest
   event on the right — when the trail slopes green→red the column scans
   as "hot then cold." Only on the rank row, so .th-form stays text. */
.rank-form__dot {
  width: 4px;
  height: 10px;
  border-radius: 2px;
  display: inline-block;
  background: var(--text-tertiary);
  opacity: 0.35;
}
.rank-form__dot--up   { background: var(--positive); opacity: 1; }
.rank-form__dot--down { background: var(--negative); opacity: 1; }
.rank-form__dot--flat { background: var(--text-tertiary); opacity: 0.5; }

/* Sortable header links. Column widths above still govern layout — these
   rules add interactivity (hover, active state, sort indicator) without
   changing dimensions. Headers render as <a> so every sort is bookmarkable,
   keyboard-focusable, and announced as a column header by screen readers. */
.th-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: color 120ms ease;
}
.th-link .th-link__label { display: inline-flex; flex-direction: column; align-items: inherit; line-height: 1; }
/* Tiny annotation under a column header label (e.g. "by lower bound" on
   Rating). Normal-case, tight — reads as a footnote to the label, not a
   second column. Only rendered when the helper is called with a
   subscript: kwarg. */
.th-link__subscript {
  margin-top: 2px;
  font-family: var(--font-data);
  font-size: var(--text-nano);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-tertiary);
  white-space: nowrap;
}
/* Indicator is always rendered so the column width doesn't shift when the
   active column changes. Empty text + reserved width keeps alignment. */
.th-link .th-link__indicator {
  display: inline-block;
  min-width: 10px;
  font-size: var(--text-nano);
  line-height: var(--leading-tight);
  color: var(--accent);
  opacity: 0;
  transition: opacity 120ms ease;
}
.th-link--active .th-link__indicator { opacity: 1; }
.th-link:hover,
.th-link:focus-visible { color: var(--text-primary); }
.th-link--active { color: var(--text-primary); }
.th-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
/* Right-aligned numeric columns put the indicator to the LEFT of the label
   because the column itself is right-aligned — keeps the number/label
   visually flush with the row's number beneath it. */
.th-link.th-rating,
.th-link.th-winrate,
.th-link.th-events,
.th-link.th-confidence { justify-content: flex-end; flex-direction: row-reverse; }
/* Record (W-L) is centered, so indicator sits to the RIGHT of the label
   (natural reading order). Centering the flex container keeps "W-L ▼" as
   one balanced lockup over the centered "12-5" data below. */
.th-link.th-record { justify-content: center; flex-direction: row; }
/* Rank column is right-aligned like the numeric columns — the existing
   16px right-padding on .th-rank still applies; this only reverses the
   flex so the ▲/▼ sits to the LEFT of "Rank". */
.th-link.th-rank { justify-content: flex-end; flex-direction: row-reverse; }

/* Column widths — tournament table */
.th-event, .tourn-event   { flex: 1; }
.th-tier, .tourn-tier     { width: 100px; text-align: center; }
.th-delta, .tourn-delta   { width: 80px; text-align: right; }
.th-date, .tourn-date     { width: 90px; text-align: right; }
.tourn-record             { width: 80px; text-align: center; }
.tourn-rating             { width: 80px; text-align: right; }

/* Profile Tournament History: center every column header except Event, and
   center the matching data cells too so each value sits directly below its
   label. Column widths are unified between header and data row so nothing
   drifts (e.g. th-record was 70px while tourn-record was 80px, shifting the
   header left of the "12-1-2" text). */
.table-container--tourn-history .th-rating,
.table-container--tourn-history .th-delta,
.table-container--tourn-history .th-date,
.table-container--tourn-history .tourn-rating,
.table-container--tourn-history .tourn-delta,
.table-container--tourn-history .tourn-date {
  text-align: center;
}
.table-container--tourn-history .th-rating { margin-left: 0; }
.table-container--tourn-history .th-record { width: 80px; }

/* Global tournaments index columns — reuses .rank-row for the row chrome.
   Winner cell is left-aligned so the gold ★ markers stack into a clean
   podium line down the column instead of drifting around a center point
   as usernames change length. Tier/Format stay center-aligned because
   the badge and tag read as centered chips; Players/Date stay right
   because they're numeric/temporal. */
.th-format, .tourn-format   { width: 80px; text-align: center; font-family: var(--font-data); font-size: var(--text-2xs); color: var(--text-tertiary); text-transform: uppercase; letter-spacing: 0.4px; }
.th-players, .tourn-players { width: 72px; text-align: right; font-family: var(--font-data); font-size: var(--text-sm); color: var(--text-secondary); }
/* Override: the header span inherits .th-players above for layout but
   the brighter text-secondary made it the loudest header on the row,
   competing with the active sort indicator. Header cells across the
   table all use text-tertiary; force this one back into line. */
.table-header .th-players { color: var(--text-tertiary); font-size: var(--text-2xs); }
.th-winner, .tourn-winner   { width: 140px; text-align: left; }

/* Winner cell repeats on every row — glow here would debase the "accent glow
   means rating hero / #1 rank / Galactic tier badge" signal (see DESIGN.md
   glow-restraint principle). Color alone carries the link affordance; row
   hover raises the row background for the interactive feedback. A dotted
   underline fills in on precision-hover, matching the inline-link idiom
   used by .rank-row--empty__link — rewards the user who aimed at the name. */
.tourn-winner__name {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--accent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: inline-block;
  max-width: calc(100% - 20px);
  vertical-align: middle;
  border-bottom: 1px dotted transparent;
  transition: border-color 200ms ease, color 150ms ease;
}

.tourn-winner__name:hover,
.tourn-winner__name:focus-visible {
  border-bottom-color: color-mix(in oklch, var(--accent) 60%, transparent);
  outline: none;
}

/* Gold star before the winner name. Scope is the single event, so the
   winner IS the #1 of that event — same rank-gold token used for the
   overall leaderboard's gold medal. One glyph, screen-reader hidden,
   so the density stays tight and the cell still reads as a player link. */
.tourn-winner__medal {
  display: inline-block;
  margin-right: 5px;
  font-size: var(--text-2xs);
  color: var(--rank-gold);
  vertical-align: baseline;
  line-height: var(--leading-tight);
  pointer-events: none;
  user-select: none;
}

.tourn-winner__empty {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
}

/* Stretched-link pattern: the tournament name is the primary link for the
   row, but its ::before overlay covers the whole card so clicking any
   empty cell still navigates. Other row links (like the winner name) use
   .stretched-link-escape to sit above the overlay and keep their own href. */
.tournament-row {
  position: relative;
  cursor: pointer;
}

.tournament-row .stretched-link::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: auto;
}

.tournament-row .stretched-link-escape {
  position: relative;
  z-index: 1;
}

.tournament-row .tourn-event-name {
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--text-primary);
}

/* Rank number medals */
.rank-num {
  font-family: var(--font-data);
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--text-secondary);
}
/* Podium rank numbers in the leaderboard table.
   #1 promotes to the site accent + a soft glow so it reads as "champion"
   coherently with the new profile rank-badge chip and the
   .rank-rating__number--champion treatment one column over. Amber-gold
   was retired here for the same reason as the profile chip: it doubled
   the Sector tier hue and competed with the sky-blue rating sitting
   immediately to its right. Silver and bronze keep their metal tones
   as a quieter podium tier — only #1 earns the glow.
   The --rank-gold token itself stays available (still used elsewhere
   for tier dots and the audit accent), only this rank number moved. */
.rank--gold   {
  color: var(--accent);
  text-shadow: 0 0 10px color-mix(in oklch, var(--accent) 50%, transparent);
}
.rank--silver { color: var(--rank-silver); }
.rank--bronze { color: var(--rank-bronze); }

/* Player cell with avatar */
.rank-player {
  display: flex;
  align-items: center;
  gap: 12px;
}

.player-avatar {
  width: 34px;
  height: 34px;
  border-radius: 17px;
  background: var(--border);
  flex-shrink: 0;
}

.player-name {
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-primary);
}

.player-username {
  font-size: var(--text-2xs);
  font-weight: 400;
  color: var(--text-tertiary);
  margin-left: 4px;
}

/* Rating column */
.rank-rating {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--accent);
}

/* Record, win rate, events */
.rank-record, .tourn-record {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.rank-winrate {
  font-family: var(--font-data);
  font-size: var(--text-sm);
}

/* Win-rate sign tints — pulled out of an inline style so the view stays
   CSP-friendly and the color semantic lives with the rest of the type.
   The neutral variant renders on zero-match ranked players (rare data
   edge) where "0.0%" in red would falsely imply a losing record. */
.rank-winrate--positive { color: var(--positive); }
.rank-winrate--negative { color: var(--negative); }
.rank-winrate--neutral  { color: var(--text-tertiary); }

/* Sparkbars on the Win% and Confidence columns — thin 2px bar at the
   bottom of each cell, width scaled by the row's --magnitude custom
   property. Same data-density pattern as the tournament-show rating
   shift table: a visual layer on top of the number so the whole column
   is scannable at a glance. Gradient fades from transparent to the
   full signal color, reading as "lit" from the right edge. */
.rank-winrate,
.rank-confidence {
  position: relative;
  overflow: hidden;
}

.rank-winrate::after,
.rank-confidence::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: 0;
  height: 2px;
  width: calc(var(--magnitude, 0) * 100%);
  border-radius: 1px;
  pointer-events: none;
}

.rank-winrate--positive::after {
  background: linear-gradient(to left,
    rgba(34, 197, 94, 0.85),
    rgba(34, 197, 94, 0.15));
}

.rank-winrate--negative::after {
  background: linear-gradient(to left,
    rgba(239, 68, 68, 0.85),
    rgba(239, 68, 68, 0.15));
}

.rank-confidence--established::after {
  background: linear-gradient(to left,
    rgba(34, 197, 94, 0.75),
    rgba(34, 197, 94, 0.15));
}

.rank-confidence--developing::after {
  background: linear-gradient(to left,
    rgba(245, 158, 11, 0.75),
    rgba(245, 158, 11, 0.15));
}

.rank-confidence--provisional::after {
  background: linear-gradient(to left,
    rgba(148, 163, 184, 0.5),
    rgba(148, 163, 184, 0.1));
}

.rank-events {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}


/* Confidence badge */
.confidence-badge {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  cursor: help;
  border-bottom: 1px dotted var(--text-tertiary);
}

/* Sortable column header on the tournaments index (Date). Inherits the
   table-header typography so it reads as part of the row; the arrow is
   the only visual signal that the column is interactive.
   Arrow placement matches the leaderboard convention: arrow LEFT of the
   label for right-aligned columns, so "↓ Date" reads as one lockup
   anchored to the right edge of the column above the right-aligned date
   values below. (Was "Date ↓" — header drifted off-axis from data.) */
.th-date__sort {
  color: inherit;
  text-decoration: none;
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  flex-direction: row-reverse;
}

.th-date__sort:hover .th-date__arrow,
.th-date__sort:focus-visible .th-date__arrow {
  color: var(--accent);
}

.th-date__sort:focus-visible {
  outline: 1px solid color-mix(in oklch, var(--accent) 50%, transparent);
  outline-offset: 2px;
  border-radius: 3px;
}

.th-date__arrow {
  color: var(--text-tertiary);
  font-size: var(--text-xs);
  transition: color 150ms ease;
}

/* Tournament row specifics */
.tourn-event-name {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
}

/* Tournament name in a collapsed history row, made into a link to the
   per-tournament rating-math audit. Inherits .tourn-event-name typography
   so the row looks visually unchanged at rest, then takes the accent
   color + dashed-bottom on hover and a focus ring on keyboard focus.
   Mirrors .h2h-row__name-link in shape so the two "click the entity name
   to drill in" affordances feel like the same idea. */
.tourn-event-name-link {
  text-decoration: none;
  color: inherit;
  border-bottom: 1px dashed transparent;
  transition: color 120ms ease, border-color 120ms ease;
}

.tourn-event-name-link:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.tourn-event-name-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}

.tourn-event-external {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  color: var(--text-tertiary);
  text-decoration: none;
  margin-left: 8px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  transition: color 150ms ease;
  white-space: nowrap;
}

/* Event location as a dim second line under the event name. Search matches
   location server-side, so surfacing it here is how the row tells the user
   "that 'Milwaukee' you typed? matched this." Inter body font keeps it as
   narrative context; Geist Mono would misread as a data field. */
.tourn-event__location {
  display: block;
  margin-top: 2px;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  line-height: var(--leading-snug);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Same location string in the tournament show-page hero meta strip —
   sits inline with tier/format/date/player-count. Uses the meta row's
   existing typography scale; no new tokens introduced. Mirrors the index
   ellipsis cap so an unusually long upstream location string can't blow
   out the meta row on mobile; full string still accessible via Melee. */
.tournament-hero__location {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  max-width: 32ch;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tourn-event-external:hover {
  color: var(--accent);
}

.tourn-event-external::after {
  content: " \2192";
}

/* On the index row, the "→" glyph is hidden at rest (50 rows × an arrow
   would read as chrome) and fades in only on hover or keyboard focus.
   The link still opens Melee in a new tab; the arrow just surfaces as
   an "external" affordance once the user's cursor declares intent. */
.tournament-row .tourn-event-external::after {
  opacity: 0;
  display: inline-block;
  transition: opacity 150ms ease;
}

.tournament-row .tourn-event-external:hover::after,
.tournament-row .tourn-event-external:focus-visible::after {
  opacity: 1;
}

/* Keyboard focus ring — the winner-name link next door gets implicit
   focus via the browser default; this link deserves the same visibility
   so keyboard users can see where Tab landed above the stretched-link
   overlay. Matches the focus treatment used on other accent links. */
.tournament-row .tourn-event-external:focus-visible {
  outline: 1px solid color-mix(in oklch, var(--accent) 60%, transparent);
  outline-offset: 2px;
  border-radius: 3px;
  color: var(--accent);
}

.tourn-date {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
}

.format-label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
  margin-left: 4px;
}

.tourn-rating {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--accent);
}

/* ============================================================
   BADGES (tier indicators — matches Pencil Tag components)
   ============================================================ */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 4px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  white-space: nowrap;
  line-height: var(--leading-tight);
}

.badge--planetary      { color: var(--tier-planetary); background: rgba(34, 197, 94, 0.2); }
.badge--store_showdown { color: var(--text-secondary); background: rgba(148, 163, 184, 0.15); }
.badge--sector         { color: var(--tier-sector);    background: rgba(245, 158, 11, 0.2); }
.badge--regional       { color: var(--tier-regional);  background: var(--accent-mid); }
.badge--galactic       { color: var(--tier-galactic);  background: rgba(124, 58, 237, 0.2); }

/* ============================================================
   BREADCRUMB (profile page)
   ============================================================ */

.breadcrumb {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 24px;
}

.breadcrumb-link {
  font-size: var(--text-sm);
  color: var(--accent);
}

.breadcrumb-sep {
  font-size: var(--text-sm);
  color: var(--text-tertiary);
}

.breadcrumb-current {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/* ============================================================
   PROFILE NAME
   ============================================================ */

.profile-name {
  font-family: var(--font-display);
  font-size: clamp(32px, 3.5vw, 44px);
  font-weight: 800;
  line-height: var(--leading-tight);
  letter-spacing: -0.025em;
  color: var(--text-primary);
  margin: 0;
  text-wrap: balance;
}

.profile-name a {
  color: inherit;
}

.profile-name a:hover {
  color: var(--accent);
}

.profile-username {
  font-family: var(--font-data);
  font-size: var(--text-lg);
  font-weight: 400;
  color: var(--text-tertiary);
  letter-spacing: 0;
}

.profile-meta {
  display: flex;
  align-items: center;
  gap: 12px;
}

.rank-badge {
  font-family: var(--font-data);
  /* Sized to match the .follow-toggle sitting beside it in the profile
     hero — the two chips now read as a matched pair. The smaller
     .rank-badge--format variant (used inside the stats strip below)
     keeps the original 11px so it doesn't overpower the format labels. */
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--card);
  border: 1px solid var(--border);
  padding: 5px 10px;
  border-radius: var(--radius-md);
  letter-spacing: 0.02em;
}

.rank-badge--unranked {
  color: var(--text-tertiary);
}

/* Format qualifier appended to the rank (e.g. "Rank #1 Premier"). Slightly
   softer than the rank number itself so "#1" stays the headline and
   "Premier" reads as context. Uses currentColor inheritance so it tints
   through on gold/silver/bronze medal badges. */
.rank-badge__qualifier {
  font-weight: 400;
  opacity: 0.72;
}

/* Podium chips for ranks 1-3 on the player profile.
   #1 (gold class, accent treatment): the chip earns the site's primary
   accent so it reads with the rating number it celebrates instead of
   fighting it. Amber-gold was retired from this chip because it doubled
   the Sector tier hue and competed with the sky-blue rating in the same
   hero zone. The amber token (--rank-gold) is still available for tier
   dots and the leaderboard row number cell — only the badge moved.
   #2/#3 (silver/bronze) get the matching chip frame so the three ranks
   read as a coherent tier system, not a one-off accent + bare text.
   Only #1 earns motion and a leading rank glyph (DESIGN.md "earn every
   glow"). */
.rank-badge--gold {
  position: relative;
  color: var(--accent);
  background: color-mix(in oklch, var(--accent) 14%, transparent);
  border-color: color-mix(in oklch, var(--accent) 50%, var(--border));
  box-shadow: 0 0 12px color-mix(in oklch, var(--accent) 22%, transparent),
              inset 0 0 0 1px color-mix(in oklch, var(--accent) 14%, transparent);
  letter-spacing: 0.04em;
  animation:
    hri-rank-stamp 520ms cubic-bezier(0.34, 1.56, 0.64, 1) 80ms 1 both,
    hri-rank-pulse 3.5s ease-in-out 600ms infinite;
}

/* Geometric rank glyph — sci-fi command-UI tag, deliberately not a star
   or trophy emoji (DESIGN.md anti-reference: no Star Wars pastiche, no
   gaming chrome). The diamond is a neutral geometric mark that reads
   "tier" without genre baggage. Decorative; aria-hidden via pseudo. */
.rank-badge--gold::before {
  content: "◆";
  display: inline-block;
  margin-right: 6px;
  font-size: 0.82em;
  line-height: 1;
  transform: translateY(-1px);
  opacity: 0.9;
}

.rank-badge--silver {
  color: var(--rank-silver);
  background: color-mix(in oklch, var(--rank-silver) 8%, transparent);
  border-color: color-mix(in oklch, var(--rank-silver) 30%, var(--border));
}

.rank-badge--bronze {
  color: var(--rank-bronze);
  background: color-mix(in oklch, var(--rank-bronze) 10%, transparent);
  border-color: color-mix(in oklch, var(--rank-bronze) 34%, var(--border));
}

/* Stamp: one-shot bounce on first paint, like a service medal being
   pinned on. Reads as "you earned this" because it fires once and
   stops, never ambient. Opacity stays at 1 throughout so a screenshot
   or a slow render never catches the chip half-faded — the bounce IS
   the reveal. */
@keyframes hri-rank-stamp {
  0%   { transform: scale(0.92); }
  55%  { transform: scale(1.08); }
  100% { transform: scale(1); }
}

/* Ambient breathing glow on #1 only. Same cadence as the LIVE changelog
   tag (3.5s ease-in-out infinite) so the design system stays coherent
   across "this is alive" signals. */
@keyframes hri-rank-pulse {
  0%, 100% { box-shadow: 0 0 12px color-mix(in oklch, var(--accent) 18%, transparent),
                         inset 0 0 0 1px color-mix(in oklch, var(--accent) 12%, transparent); }
  50%      { box-shadow: 0 0 18px 1px color-mix(in oklch, var(--accent) 32%, transparent),
                         inset 0 0 0 1px color-mix(in oklch, var(--accent) 22%, transparent); }
}

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

/* Compact variant for the per-format ranks inside the hero stat strip.
   Sits as its own line below the ±RD note so the format's headline rating
   stays the dominant element, but the rank gets the same badge treatment
   as the Overall rank chip above the hero. */
.rank-badge--format {
  align-self: flex-start;
  margin-top: 4px;
  font-size: var(--text-micro);
  padding: 2px 6px;
}


/* ============================================================
   PROFILE HERO — name + rank + dominant rating + stat-strip,
   all in one card so a player's vitals read as a single unit.
   ============================================================ */

.profile-hero {
  display: grid;
  grid-template-columns: 1fr;
  gap: 18px;
  padding: 18px 22px 20px;
  margin-bottom: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
}

/* Anchor name + big rating number on a shared first baseline so the two
   hero elements read as one horizontal line. Subordinate info (rank-badge
   under name, caption + delta under number) stacks below each without
   competing with the headline row. */
.profile-hero__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}

.profile-hero__id {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.profile-hero__rank {
  display: inline-flex;
  align-items: center;
  /* Rank badge and Follow toggle now sit as a matched pair — gap gives
     them breathing room so they read as two distinct actions instead of
     a squished pill strip. */
  gap: var(--space-sm);
  flex-wrap: wrap;
}

/* Power-ranking badges — small mono chips on the hero for auto-earned
   distinctions like RISING, IRONMAN, HOT. Each slug gets its own color
   tone so a glance differentiates them; accent glow reserved for HOT. */
.power-badge {
  display: inline-flex;
  align-items: center;
  min-width: 40px;
  padding: 3px 8px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: color-mix(in oklch, var(--border) 55%, transparent);
  color: var(--text-secondary);
}
.power-badge--rising {
  color: var(--positive);
  border-color: color-mix(in oklch, var(--positive) 35%, transparent);
  background: color-mix(in oklch, var(--positive) 10%, transparent);
}
.power-badge--ironman {
  color: color-mix(in oklch, var(--tier-sector) 85%, var(--text-primary));
  border-color: color-mix(in oklch, var(--tier-sector) 40%, transparent);
  background: color-mix(in oklch, var(--tier-sector) 10%, transparent);
}
.power-badge--hot {
  color: var(--accent);
  border-color: color-mix(in oklch, var(--accent) 45%, transparent);
  background: color-mix(in oklch, var(--accent) 10%, transparent);
  box-shadow: 0 0 8px color-mix(in oklch, var(--accent) 25%, transparent);
}

/* Nemesis chip — surfaces the single rival this player can't beat.
   Muted (not accent-glowing) because the tone is "here's your hard
   problem," not "here's your achievement." Wraps onto a new line under
   the rank+follow row so density stays clean on mobile. */
.nemesis-chip {
  display: inline-flex;
  align-items: baseline;
  align-self: flex-start; /* parent profile-hero__id is a flex column */
  gap: 6px;
  margin-top: var(--space-2xs);
  padding: 4px 10px;
  border: 1px solid color-mix(in oklch, var(--negative) 30%, transparent);
  border-radius: 4px;
  background: color-mix(in oklch, var(--negative) 7%, transparent);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  color: var(--text-secondary);
  text-decoration: none;
  transition: border-color 150ms ease, background 150ms ease, color 150ms ease;
}
.nemesis-chip:hover,
.nemesis-chip:focus-visible {
  border-color: color-mix(in oklch, var(--negative) 55%, transparent);
  background: color-mix(in oklch, var(--negative) 12%, transparent);
  color: var(--text-primary);
}
.nemesis-chip__label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--negative) 80%, var(--text-tertiary));
}
.nemesis-chip__name {
  font-weight: 500;
  color: var(--text-primary);
}
.nemesis-chip__record {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

/* Rivalry temperature dot — precedes the rival name in the H2H table.
   Filled accent for active (≤90d), filled muted for simmering (≤1yr),
   hollow ring for dormant. Small (7px), mono color system, no glow. */
.rivalry-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-right: 7px;
  vertical-align: baseline;
  flex-shrink: 0;
}
.rivalry-dot--active {
  background: var(--accent);
  box-shadow: 0 0 0 1px color-mix(in oklch, var(--accent) 60%, transparent);
}
.rivalry-dot--simmering {
  background: var(--text-tertiary);
}
.rivalry-dot--dormant {
  background: transparent;
  border: 1px solid var(--text-tertiary);
  width: 6px;
  height: 6px;
}

/* Dual rating-trajectory chart — vs page hero visualization. Two hairlines
   for the players plus meeting dots between them. Pure SVG, no chart lib. */
.vs-trajectory {
  margin-bottom: var(--space-lg);
  padding: var(--space-md);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.vs-trajectory--empty {
  text-align: center;
  color: var(--text-tertiary);
  font-size: var(--text-sm);
}
.vs-trajectory__placeholder { margin: 0; }
.vs-trajectory__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-xs);
  flex-wrap: wrap;
}
.vs-trajectory__head-main {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-sm);
}
.vs-trajectory__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  margin: 0;
}
.vs-trajectory__format {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
/* Summary strip — meeting count + date range under the header. Gives
   the chart an at-a-glance context line before the user parses the
   lines themselves. */
.vs-trajectory__summary {
  display: inline-flex;
  gap: 6px;
  align-items: baseline;
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  font-feature-settings: "tnum";
}
.vs-trajectory__summary-sep { opacity: 0.5; }

/* Legend — each side gets name + current rating + delta across the
   rivalry window. The delta is the key new piece: it answers "did this
   matchup help or hurt me over the arc?" without reading the chart. */
.vs-trajectory__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
  margin-bottom: var(--space-sm);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.vs-trajectory__legend-item {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
}
.vs-trajectory__swatch {
  display: inline-block;
  width: 14px;
  height: 2px;
  border-radius: 1px;
  align-self: center;
}
.vs-trajectory__swatch--you  { background: var(--accent); }
.vs-trajectory__swatch--them { background: var(--text-tertiary); }
.vs-trajectory__legend-name {
  color: var(--text-primary);
  font-weight: 600;
}
.vs-trajectory__legend-rating {
  color: var(--text-secondary);
  font-feature-settings: "tnum";
  font-weight: 600;
}
.vs-trajectory__legend-delta {
  font-size: var(--text-2xs);
  font-weight: 700;
  font-feature-settings: "tnum";
  padding: 1px 6px;
  border-radius: var(--radius-sm);
}
.vs-trajectory__legend-delta--up {
  color: var(--positive);
  background: color-mix(in oklch, var(--positive) 12%, transparent);
}
.vs-trajectory__legend-delta--down {
  color: color-mix(in oklch, var(--negative) 80%, var(--text-secondary));
  background: color-mix(in oklch, var(--negative) 10%, transparent);
}

.vs-trajectory__svg {
  display: block;
  width: 100%;
  height: auto;
  min-height: 220px;
}
.vs-trajectory__grid {
  stroke: var(--border);
  stroke-width: 0.5;
  opacity: 0.6;
}
.vs-trajectory__axis-label {
  font-family: var(--font-data);
  font-size: 10px;
  fill: var(--text-tertiary);
}
.vs-trajectory__line {
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.vs-trajectory__line--you  { stroke: var(--accent); }
.vs-trajectory__line--them { stroke: var(--text-tertiary); }

/* Terminal-rating endpoint marker on each line — a small filled dot
   at the last-known rating so the eye anchors on "current" instead of
   a trailing hairline. Ring stroke matches the page background so the
   dot reads as a discrete marker over the line + any crossing meeting
   dots. */
.vs-trajectory__endpoint {
  stroke: var(--bg);
  stroke-width: 2;
}
.vs-trajectory__endpoint--you  { fill: var(--accent); }
.vs-trajectory__endpoint--them { fill: var(--text-tertiary); }

.vs-trajectory__meeting { stroke: var(--bg); stroke-width: 1.5; cursor: default; }
.vs-trajectory__meeting--win  { fill: var(--positive); }
.vs-trajectory__meeting--loss { fill: var(--negative); }
.vs-trajectory__meeting--draw { fill: var(--warning); }

/* What-if counter-factual swing indicator next to a loss's Δ pill. Muted
   accent so it reads as supplemental info, not competing with the actual Δ. */
/* Time Machine — historical leaderboard view. Simple form + table. */
.time-machine__hero { margin-bottom: var(--space-md); }
.time-machine__title {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 800;
  margin: 0 0 8px 0;
}
.time-machine__lede { color: var(--text-secondary); margin: 0; }
.time-machine__form {
  display: flex;
  gap: var(--space-md);
  align-items: center;
  margin-bottom: var(--space-lg);
  flex-wrap: wrap;
}
.time-machine__label {
  display: inline-flex;
  flex-direction: column;
  gap: 4px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.time-machine__date,
.time-machine__format {
  background: var(--input);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  font-family: var(--font-data);
}
.time-machine__empty { color: var(--text-tertiary); font-style: italic; }

/* "This Week" drawer on the homepage. Collapsed by default so the
   leaderboard stays the hero; opens to reveal up to 5 algorithmic
   highlights (upset, climb, fall, event, top performance). Visual
   vocabulary matches the Top-Cut Bracket collapsible on the audit page:
   quiet row when closed, full surface when open. */
.weekly-drawer {
  margin: 0 0 var(--space-lg);
  border: 1px solid color-mix(in oklch, var(--border) 65%, transparent);
  border-radius: var(--radius-md);
  background: transparent;
  transition: background-color 150ms ease, border-color 150ms ease;
}
.weekly-drawer[open] {
  background: var(--surface);
  border-color: color-mix(in oklch, var(--accent) 25%, var(--border));
}

.weekly-drawer__summary {
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding: 12px var(--space-md);
  cursor: pointer;
  list-style: none;
  transition: color 150ms ease, background-color 150ms ease;
}
.weekly-drawer__summary::-webkit-details-marker { display: none; }
.weekly-drawer__summary:hover {
  background: color-mix(in oklch, var(--accent) 4%, transparent);
}
.weekly-drawer__summary:focus-visible {
  outline: 1px solid color-mix(in oklch, var(--accent) 50%, transparent);
  outline-offset: -1px;
  border-radius: var(--radius-md);
}
.weekly-drawer[open] .weekly-drawer__summary {
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--border);
}
.weekly-drawer[open] .weekly-drawer__summary:hover { background: transparent; }

.weekly-drawer__label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--accent-hover);
  flex-shrink: 0;
}
.weekly-drawer__hint {
  flex: 1;
  min-width: 0;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
}
.weekly-drawer__toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  transition: color 150ms ease;
}
.weekly-drawer__toggle::after {
  content: "›";
  font-family: var(--font-display);
  font-size: var(--text-base);
  line-height: 1;
  transition: transform 200ms ease;
}
.weekly-drawer[open] .weekly-drawer__toggle::after { transform: rotate(90deg); }
.weekly-drawer__summary:hover .weekly-drawer__toggle { color: var(--accent); }
.weekly-drawer__toggle-open { display: none; }
.weekly-drawer[open] .weekly-drawer__toggle-open { display: inline; }
.weekly-drawer[open] .weekly-drawer__toggle-closed { display: none; }

.weekly-drawer__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Individual highlight row. Grid: [label | body | link] so labels align
   flush-left, headlines/details sit in the flex middle, "View →" pins
   right. Accent color per highlight kind on the label only — keeps the
   list scannable without painting every row. */
.weekly-highlight {
  display: grid;
  grid-template-columns: 140px 1fr auto;
  align-items: baseline;
  gap: 16px;
  padding: 12px var(--space-md);
  border-top: 1px solid var(--border);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.weekly-highlight:first-child { border-top: none; }

.weekly-highlight__label {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  white-space: nowrap;
}
.weekly-highlight--upset       .weekly-highlight__label { color: var(--positive); }
.weekly-highlight--climb       .weekly-highlight__label { color: var(--positive); }
.weekly-highlight--fall        .weekly-highlight__label { color: var(--negative); }
.weekly-highlight--event       .weekly-highlight__label { color: var(--accent); }
.weekly-highlight--performance .weekly-highlight__label { color: var(--accent); }

.weekly-highlight__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.weekly-highlight__headline {
  font-family: var(--font-body);
  font-size: var(--text-md);
  color: var(--text-primary);
  line-height: var(--leading-tight);
}
.weekly-highlight__headline strong {
  font-weight: 600;
  color: var(--text-primary);
}
.weekly-highlight__detail {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

.weekly-highlight__link {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  text-decoration: none;
  white-space: nowrap;
  transition: color 150ms ease;
}
.weekly-highlight__link:hover { color: var(--accent); }

@media (max-width: 640px) {
  .weekly-highlight {
    grid-template-columns: 1fr;
    gap: 6px;
  }
  .weekly-highlight__link { justify-self: start; }
}

/* Top-cut bracket — CSS grid of matches per round. Winner slot gets a
   filled background, player's own rows get an accent left-border so you
   can follow your own path through the tree. */
.bracket {
  margin: var(--space-md) 0;
  padding: var(--space-md);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.bracket__title {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  margin: 0 0 var(--space-md) 0;
}
.bracket__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-lg);
  align-items: center;
}
.bracket__column {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.bracket__round-label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--space-xs);
}
.bracket__matches {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  justify-content: space-around;
  flex: 1;
}
.bracket__match {
  display: flex;
  flex-direction: column;
  gap: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.bracket__slot {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  padding: 6px 10px;
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  background: color-mix(in oklch, var(--bg) 80%, transparent);
}
.bracket__slot:first-child { border-bottom: 1px solid var(--border); }
.bracket__slot--winner {
  color: var(--text-primary);
  background: color-mix(in oklch, var(--surface) 80%, transparent);
  font-weight: 600;
}
.bracket__slot--you {
  border-left: 2px solid var(--accent);
}
.bracket__name-link {
  color: inherit;
  text-decoration: none;
}
.bracket__name-link:hover { color: var(--accent); }
.bracket__score {
  font-variant-numeric: tabular-nums;
  color: var(--text-tertiary);
  min-width: 1.5em;
  text-align: right;
}
.bracket__slot--winner .bracket__score { color: var(--positive); }

/* Collapsible bracket on the player-audit page. The full bracket lives on
   the tournament show page, so this one defaults closed to keep the audit
   hero + rating math in the first screen.

   Closed state is a quiet one-line divider (transparent bg, muted border)
   so it doesn't compete with the rating-path above or the phase-breakdown
   table below. Open state gets full panel chrome so it reads as a real
   visualization when the reader asks for it. Native <details>/<summary>. */
.bracket--collapsible {
  padding: 0;
  margin-bottom: var(--space-md);
  background: transparent;
  border-color: color-mix(in oklch, var(--border) 55%, transparent);
  transition: background-color 150ms ease, border-color 150ms ease;
}
.bracket--collapsible[open] {
  padding: var(--space-md);
  background: var(--surface);
  border-color: color-mix(in oklch, var(--accent) 25%, var(--border));
}
.bracket__summary {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 10px 14px;
  cursor: pointer;
  list-style: none;
  transition: color 150ms ease, background-color 150ms ease;
}
.bracket__summary::-webkit-details-marker { display: none; }
.bracket__summary:hover { background: color-mix(in oklch, var(--accent) 4%, transparent); }
.bracket__summary:focus-visible {
  outline: 1px solid color-mix(in oklch, var(--accent) 50%, transparent);
  outline-offset: -1px;
  border-radius: var(--radius-md);
}
.bracket--collapsible[open] .bracket__summary {
  padding: 0 0 var(--space-md) 0;
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-md);
}
.bracket--collapsible[open] .bracket__summary:hover { background: transparent; }
.bracket--collapsible .bracket__title {
  margin: 0;
  font-size: var(--text-sm);
  font-family: var(--font-data);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary);
  flex-shrink: 0;
}
.bracket--collapsible[open] .bracket__title {
  font-size: var(--text-base);
  font-family: var(--font-display);
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--text-primary);
}
.bracket__summary-hint {
  flex: 1;
  min-width: 0;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}
.bracket--collapsible[open] .bracket__summary-hint { display: none; }
.bracket__summary-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  flex-shrink: 0;
  margin-left: auto;
  transition: color 150ms ease;
}
.bracket__summary-toggle::after {
  content: "›";
  font-family: var(--font-display);
  font-size: var(--text-base);
  line-height: 1;
  transition: transform 200ms ease;
}
.bracket--collapsible[open] .bracket__summary-toggle::after {
  transform: rotate(90deg);
}
.bracket__summary:hover .bracket__summary-toggle { color: var(--accent); }
.bracket__summary-toggle-open { display: none; }
.bracket--collapsible[open] .bracket__summary-toggle-open { display: inline; }
.bracket--collapsible[open] .bracket__summary-toggle-closed { display: none; }
.bracket__summary-footer {
  margin-top: var(--space-md);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--border);
  text-align: right;
}
.bracket__summary-link {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  text-decoration: none;
  transition: color 150ms ease;
}
.bracket__summary-link:hover { color: var(--accent); }

/* Rating path — horizontal phase-level timeline on the tournament audit.
   Reads as a scoreboard path: Start → post-Swiss → post-QF → post-SF → post-Finals. */
.rating-path {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: var(--space-md);
  padding: var(--space-md);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow-x: auto;
}
.rating-path__step {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  background: color-mix(in oklch, var(--surface) 80%, var(--bg));
  min-width: 80px;
}
.rating-path__step--start {
  border: 1px dashed var(--border);
}
.rating-path__step--up   { border: 1px solid color-mix(in oklch, var(--positive) 35%, transparent); }
.rating-path__step--down { border: 1px solid color-mix(in oklch, var(--negative) 35%, transparent); }
.rating-path__step--flat { border: 1px solid var(--border); }
.rating-path__label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.rating-path__value {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--text-primary);
}
.rating-path__delta {
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
}
.rating-path__step--up .rating-path__delta   { color: var(--positive); }
.rating-path__step--down .rating-path__delta { color: var(--negative); }
.rating-path__step--flat .rating-path__delta { color: var(--text-tertiary); }
.rating-path__arrow {
  align-self: center;
  color: var(--text-tertiary);
  font-family: var(--font-data);
  font-size: var(--text-sm);
}
.rating-path__arrow--up   { color: var(--positive); }
.rating-path__arrow--down { color: var(--negative); }

/* Tournament field-strength context line — sits under the metric strip on
   the audit page. Tight mono block: mean, median, rated count, percentile. */
.field-context {
  margin-top: var(--space-sm);
  padding: 10px var(--space-md);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: color-mix(in oklch, var(--surface) 60%, transparent);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.field-context__label {
  display: inline-block;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-right: 10px;
}
.field-context__body strong { color: var(--text-primary); }
.field-context__percentile {
  display: inline-block;
  margin-left: 10px;
  padding: 2px 10px;
  border: 1px solid color-mix(in oklch, var(--accent) 35%, var(--border));
  border-radius: 999px;
  background: color-mix(in oklch, var(--accent) 6%, transparent);
  color: var(--accent);
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}
.field-context__percentile sup { font-size: 0.7em; }

/* vs The Field — three-bucket record by opponent strength. Tight, mono,
   no bar charts: honest numbers in a table that reads fast. */
.vs-field {
  margin: var(--space-lg) 0;
}
.vs-field__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  margin-bottom: var(--space-md);
}
.vs-field__table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
  /* The featured TOP 10 row reserves a slot on the left for its accent
     dot (via padding on every label cell). fixed-layout keeps the dot
     column from flexing when content widths shift between players. */
  table-layout: auto;
}
.vs-field__head {
  text-align: left;
  padding: 6px 10px 6px 0;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  border-bottom: 1px solid var(--border);
}
.vs-field__head--num { text-align: right; }
.vs-field__head--hint { color: transparent; } /* column header hidden */
.vs-field__row td {
  padding: 8px 10px 8px 0;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.vs-field__row:last-child td { border-bottom: none; }
.vs-field__label {
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--text-secondary);
  width: 1%;   /* shrink to content; numeric and hint cells flex */
}
/* Every label cell renders its label as a flex row with a 7px dot-slot
   in front. The slot is always present (transparent on bucket rows,
   filled + glowing on the featured row) so every label's text starts
   at the same x-coordinate. No zigzag, no markup difference between
   featured and non-featured rows. */
.vs-field__label {
  display: flex;
  align-items: center;
  gap: 8px;
}
.vs-field__label::before {
  content: "";
  display: block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: transparent;
  flex-shrink: 0;
}
.vs-field__wld,
.vs-field__pct {
  text-align: right;
  font-variant-numeric: tabular-nums;
  width: 1%;
  white-space: nowrap;
}
.vs-field__pct-value--up   { color: var(--positive); font-weight: 600; }
.vs-field__pct-value--down { color: color-mix(in oklch, var(--negative) 80%, var(--text-secondary)); font-weight: 600; }
.vs-field__hint {
  padding-left: var(--space-sm);
  color: var(--text-tertiary);
  font-size: var(--text-xs);
}
.vs-field__empty { color: var(--text-tertiary); }

/* Scoreline pattern on the vs page — average game score when each side
   wins the match. Two rows, one per direction. Heading + caption sit
   above so "2.0–0.5" reads as an average, not a literal match score. */
.vs-scouting {
  margin-top: var(--space-lg);
}
.vs-scouting__head {
  margin-bottom: var(--space-sm);
}
.vs-scouting__caption {
  margin: 4px 0 0 0;
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}
.vs-scouting__grid {
  margin: 0;
  padding: 0;
}
.vs-scouting__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-md);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--border);
}
.vs-scouting__row:last-child { border-bottom: none; }
.vs-scouting__label {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin: 0;
}
.vs-scouting__value {
  font-size: var(--text-md);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  margin: 0;
}
/* "avg" tag preceding the numeric pair — signals that the decimals
   aren't a literal scoreline, they're an average across N wins. */
.vs-scouting__avg-tag {
  display: inline-block;
  margin-right: 6px;
  padding: 1px 6px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  background: color-mix(in oklch, var(--border) 45%, transparent);
  border-radius: var(--radius-sm);
  vertical-align: middle;
}
.vs-scouting__count {
  color: var(--text-tertiary);
  font-size: var(--text-xs);
  margin-left: 6px;
}

/* Closest Calls — career-wide biggest upset + costliest loss. Two-column
   grid, accent border on the upset, muted-red on the loss. Mono for math,
   serif-ish weight for the event/opponent names. */
.closest-calls {
  margin: var(--space-lg) 0;
}
.closest-calls__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  margin-bottom: var(--space-md);
}
.closest-calls__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--space-md);
}
.closest-call {
  padding: var(--space-md);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.closest-call--upset {
  border-color: color-mix(in oklch, var(--accent) 35%, var(--border));
}
.closest-call--loss {
  border-color: color-mix(in oklch, var(--negative) 30%, var(--border));
}
.closest-call__label {
  display: inline-block;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: 10px;
}
.closest-call--upset .closest-call__label { color: var(--accent); }
.closest-call--loss  .closest-call__label { color: color-mix(in oklch, var(--negative) 80%, var(--text-secondary)); }
.closest-call__body {
  margin: 0 0 10px 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: var(--leading-relaxed);
}
.closest-call__name {
  color: var(--text-primary);
  font-weight: 600;
  text-decoration: none;
}
.closest-call__name:hover { color: var(--accent); }
.closest-call__event {
  color: var(--text-primary);
  text-decoration: none;
}
.closest-call__event:hover { text-decoration: underline; }
.closest-call__date {
  color: var(--text-tertiary);
  margin-left: 4px;
}
.closest-call__math {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}
.closest-call__verdict--win  { color: var(--positive); }
.closest-call__verdict--loss { color: color-mix(in oklch, var(--negative) 80%, var(--text-secondary)); }

/* Most-improved chip on H2H rivals row — arrows when an opponent's rating
   has shifted ≥150 since the last meeting. */
.h2h-improved {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  border-radius: 3px;
  letter-spacing: 0.04em;
}
.h2h-improved--up {
  color: color-mix(in oklch, var(--negative) 70%, var(--text-secondary));
  background: color-mix(in oklch, var(--negative) 10%, transparent);
  border: 1px solid color-mix(in oklch, var(--negative) 25%, transparent);
}
.h2h-improved--down {
  color: var(--positive);
  background: color-mix(in oklch, var(--positive) 10%, transparent);
  border: 1px solid color-mix(in oklch, var(--positive) 25%, transparent);
}

/* H2H sort-header link — lets the column header stay visually identical to
   non-sortable columns but pick up an active state + arrow on click. */
.h2h-sort-link {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: color 150ms ease;
}
.h2h-sort-link:hover { color: var(--text-primary); }
.h2h-sort-link.is-active { color: var(--accent); }


.profile-hero__rating {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-2xs);
  text-align: right;
  flex-shrink: 0;
}

/* Caption row — format label · ±CI · confidence %, all inline. Reads as
   a scoreboard subtitle under the big rating number rather than three
   separate stacked stats competing for vertical real estate. */
.profile-hero__caption {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  flex-wrap: wrap;
  justify-content: flex-end;
  margin-top: var(--space-2xs);
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

.profile-hero__caption-sep {
  color: var(--text-tertiary);
  opacity: 0.5;
}

.profile-hero__label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
}

/* Wraps the big number + optional PEAK flag so they align on the same
   baseline and the flag floats to the right of the number without
   affecting the hero's right-column grid layout. */
.profile-hero__number-wrap {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-xs);
}

.profile-hero__number {
  font-family: var(--font-data);
  font-size: var(--text-7xl);
  font-weight: 700;
  color: var(--accent);
  line-height: var(--leading-tight);
  text-shadow: 0 0 16px var(--accent-glow);
  letter-spacing: var(--tracking-tight);
}

/* AT-PEAK: when the player's current rating equals their personal best,
   bump the existing glow slightly and nudge the color to accent-hover.
   Restrained — the number's silhouette is unchanged, just a touch more
   voltage. Paired with the .profile-hero__peak chip beside it. */
.profile-hero__number--at-peak {
  color: var(--accent-hover);
  text-shadow: 0 0 20px color-mix(in oklch, var(--accent-hover) 40%, transparent);
}

.profile-hero__peak {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 3px 8px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--rank-gold);
  background: color-mix(in oklch, var(--rank-gold) 14%, transparent);
  border: 1px solid color-mix(in oklch, var(--rank-gold) 50%, transparent);
  border-radius: var(--radius-sm);
  cursor: help;
  position: relative;
  /* align-self: center pulls the chip up to meet the baseline of the big
     numerals. Without it the chip would sit on the number's baseline and
     look dragged-down against 48px digits. */
  align-self: center;
  /* Softly pulses so the flag registers as earned signal, not a sticker.
     Scoped to this exact element only — motion is feedback, not ambient. */
  animation: peak-pulse 3s ease-in-out infinite;
}
.profile-hero__peak-arrow {
  font-weight: 700;
  line-height: 1;
}
@keyframes peak-pulse {
  0%, 100% {
    box-shadow: 0 0 0 0 color-mix(in oklch, var(--rank-gold) 0%, transparent);
  }
  50% {
    box-shadow: 0 0 14px 0 color-mix(in oklch, var(--rank-gold) 35%, transparent);
  }
}
@media (prefers-reduced-motion: reduce) {
  .profile-hero__peak { animation: none; }
}

/* 95% credible interval bound range, sits below the hero number. Reads
   as a stat-line annotation: "95% CI 1,771 – 2,079". Mono numerals,
   muted color so it doesn't compete with the headline rating, but
   visible enough to communicate "the range is part of the rating, not
   an afterthought." */
.profile-hero__range {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  margin-top: 6px;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  align-self: flex-end;
  cursor: help;
}

.profile-hero__range-label {
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-tertiary);
  opacity: 0.75;
}

.profile-hero__range-value {
  font-feature-settings: "tnum";
  color: var(--text-secondary);
}

.profile-hero__number--muted {
  font-family: var(--font-data);
  font-size: var(--text-5xl);
  color: var(--text-secondary);
  text-shadow: none;
  letter-spacing: normal;
}

.profile-hero__conf {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

.profile-hero__conf.confidence-badge {
  border-bottom: 1px dotted var(--text-tertiary);
}

/* Stat-strip lives inside the hero card under the big rating. Tabular
   mono numerals so columns of digits line up between players. Top divider
   reads as a horizontal rule between the hero and the stat row. */
.profile-hero__stats {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 12px 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

.hero-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.hero-stat__label {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.hero-stat__value {
  font-family: var(--font-data);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-primary);
  font-feature-settings: "tnum";
  line-height: var(--leading-tight);
}

.hero-stat__value--muted {
  color: var(--text-tertiary);
  font-weight: 400;
}

.hero-stat__note {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  color: var(--text-tertiary);
}

@media (max-width: 768px) {
  .profile-hero { padding: 14px 16px 16px; }
  .profile-hero__number { font-size: var(--text-6xl); }
  .profile-hero__stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ============================================================
   CAREER HIGHLIGHTS strip (profile hero)
   Tier-colored trophy counters + top-cut rate. Mirrors the stat-
   strip rhythm above it (same top divider, same tabular mono).
   Earned counters pop in their tier color with a soft glow; zero
   counters stay muted so the strip never dominates a fresh profile.
   ============================================================ */

.career-highlights {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr)) minmax(0, 1.4fr);
  gap: var(--space-md);
  padding-top: var(--space-sm);
  margin-top: var(--space-sm);
  border-top: 1px dashed var(--border);
}

.trophy {
  position: relative;
  display: grid;
  grid-template-areas:
    "dot   label"
    "count count"
    "unit  unit";
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: 6px;
  row-gap: 1px;
  /* Slightly tighter padding than v1 — the count went from 28px to 20px
     so the card can breathe at a smaller overall height without losing
     its tap/hover footprint. */
  padding: var(--space-2xs) var(--space-sm);
  border-radius: var(--radius-md);
  background: color-mix(in oklch, var(--card) 55%, transparent);
  border: 1px solid var(--border);
  transition: transform 150ms ease, border-color 150ms ease,
              background 150ms ease, box-shadow 150ms ease;
}

.trophy:hover {
  transform: translateY(-1px);
  border-color: var(--accent-soft);
}

.trophy__dot {
  grid-area: dot;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-tertiary);
  opacity: 0.45;
  flex-shrink: 0;
}

.trophy__label {
  grid-area: label;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.trophy__count {
  grid-area: count;
  font-family: var(--font-data);
  /* Career-highlight counts sit below the primary rating and the format
     stat-strip, so they should NOT compete with either for attention.
     var(--text-xl) = 20px reads as a strong-but-secondary number — the
     hero rating at var(--text-7xl) 48px stays the clear headline. */
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--text-tertiary);
  line-height: var(--leading-tight);
  font-feature-settings: "tnum";
  opacity: 0.4;
}

.trophy__unit {
  grid-area: unit;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  opacity: 0.6;
}

/* EARNED state — tier color takes over the dot, the count, and the glow.
   Scaled per tier: Galactic gets the strongest lift since it's the rarest. */
.trophy--earned .trophy__count { opacity: 1; }
.trophy--earned .trophy__dot   { opacity: 1; }

.trophy--galactic.trophy--earned {
  background: color-mix(in oklch, var(--tier-galactic) 10%, var(--card));
  border-color: color-mix(in oklch, var(--tier-galactic-hi) 60%, var(--border));
  box-shadow: 0 0 20px color-mix(in oklch, var(--tier-galactic-hi) 30%, transparent),
              inset 0 0 0 1px color-mix(in oklch, var(--tier-galactic-hi) 20%, transparent);
  /* Position context for the aurora pseudo-element */
  position: relative;
  overflow: hidden;
}

/* GALACTIC SHIMMER — only fires when the card is earned. Galactic is the
   rarest trophy (most players will never have one), so when someone does
   the card gets a slow diagonal aurora wash that makes it feel different
   from the other three tiers. Restrained: the animation is 8s, the wash
   is low-opacity, and mix-blend-mode lets it tint without stacking on
   top of the count. Respects reduced-motion below. */
.trophy--galactic.trophy--earned::after {
  content: "";
  position: absolute;
  inset: -50%;
  background: conic-gradient(
    from 220deg,
    transparent 0deg,
    color-mix(in oklch, var(--tier-galactic-hi) 35%, transparent) 60deg,
    transparent 120deg,
    transparent 360deg
  );
  mix-blend-mode: screen;
  opacity: 0.6;
  pointer-events: none;
  animation: galactic-aurora 8s linear infinite;
}

/* Sits above the aurora so counts stay crisp and readable. */
.trophy--galactic.trophy--earned > * {
  position: relative;
  z-index: 1;
}

@keyframes galactic-aurora {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .trophy--galactic.trophy--earned::after { animation: none; opacity: 0.4; }
}

.trophy--galactic .trophy__dot   { background: var(--tier-galactic-hi); }
.trophy--galactic.trophy--earned .trophy__dot  {
  box-shadow: 0 0 8px color-mix(in oklch, var(--tier-galactic-hi) 70%, transparent);
}
.trophy--galactic.trophy--earned .trophy__count { color: var(--tier-galactic-hi); }

.trophy--regional.trophy--earned {
  background: color-mix(in oklch, var(--tier-regional) 10%, var(--card));
  border-color: color-mix(in oklch, var(--tier-regional) 50%, var(--border));
}
.trophy--regional .trophy__dot { background: var(--tier-regional); }
.trophy--regional.trophy--earned .trophy__count { color: var(--tier-regional); }

.trophy--sector.trophy--earned {
  background: color-mix(in oklch, var(--tier-sector) 10%, var(--card));
  border-color: color-mix(in oklch, var(--tier-sector) 50%, var(--border));
}
.trophy--sector .trophy__dot { background: var(--tier-sector); }
.trophy--sector.trophy--earned .trophy__count { color: var(--tier-sector); }

.trophy--planetary.trophy--earned {
  background: color-mix(in oklch, var(--tier-planetary) 10%, var(--card));
  border-color: color-mix(in oklch, var(--tier-planetary) 50%, var(--border));
}
.trophy--planetary .trophy__dot { background: var(--tier-planetary); }
.trophy--planetary.trophy--earned .trophy__count { color: var(--tier-planetary); }

/* TOP CUT RATE slot — narrower grid than the trophies because it has a
   progress bar between label and count instead of an icon. Neutral accent
   (sky blue) since Top Cut % isn't tier-bound. */
.trophy--topcut {
  grid-template-areas:
    "label label"
    "prog  prog"
    "count unit";
  grid-template-columns: auto 1fr;
  column-gap: var(--space-sm);
}
.trophy--topcut .trophy__label { grid-area: label; color: var(--accent); }
.trophy--topcut .trophy__count { opacity: 1; color: var(--text-primary); }
/* Match the trophy__count scale above so the top-cut % reads as a peer
   of the four tier counters, not a louder sibling. */
.trophy--topcut .trophy__count--pct { font-size: var(--text-lg); }
.trophy--topcut .trophy__unit  {
  grid-area: unit;
  text-align: right;
  align-self: center;
  opacity: 0.8;
}

.trophy__progress {
  grid-area: prog;
  height: 6px;
  border-radius: 3px;
  background: var(--input);
  overflow: hidden;
  margin-block: var(--space-2xs);
  border: 1px solid var(--border);
}
.trophy__progress-bar {
  display: block;
  height: 100%;
  width: var(--pct, 0%);
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-hover) 100%);
  box-shadow: 0 0 8px color-mix(in oklch, var(--accent) 60%, transparent);
  border-radius: inherit;
  transition: width 900ms ease-out;
}

/* Responsive — strip collapses to 2 columns under the existing 768px
   breakpoint, then 1 column under 480px. The top-cut slot always spans
   the full width of its row so the progress bar stays wide enough to
   read. */
@media (max-width: 768px) {
  .career-highlights {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-sm);
  }
  .trophy--topcut { grid-column: 1 / -1; }
}

@media (max-width: 480px) {
  .career-highlights { grid-template-columns: 1fr; }
  .trophy--topcut { grid-column: 1 / -1; }
}

/* ============================================================
   PER-PLAYER PER-TOURNAMENT AUDIT HERO
   ============================================================ */

.audit-hero {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  padding-bottom: 20px;
  margin-bottom: 20px;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}

.audit-hero__title { flex: 1; min-width: 0; }

.audit-hero__meta {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 8px;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
}

.audit-hero__date { color: var(--text-secondary); }
.audit-hero__record .data,
.audit-hero__field .data { color: var(--text-primary); font-weight: 600; }

.audit-hero__delta {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-shrink: 0;
}

.audit-hero__rating {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
}

.audit-hero__label {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
}

.audit-hero__number {
  font-family: var(--font-data);
  font-size: var(--text-7xl); /* 48px — matches .rating-number--hero on the profile. Reserved glow is earned here: this is THE rating the page exists to show. */
  font-weight: 700;
  color: var(--accent);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  text-shadow: 0 0 12px var(--accent-glow);
}

/* ============================================================
   METRIC STRIP — demoted format ratings + stats
   ============================================================ */

.metric-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px 20px;
  margin-bottom: 32px;
  padding: 4px 0;
}

.metric-strip__item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  white-space: nowrap;
}

.metric-strip__label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
}

.metric-strip__value {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-secondary);
}

.metric-strip__value--warning { color: var(--warning); }

.metric-strip__note {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
}

.metric-strip__sep {
  width: 1px;
  height: 16px;
  background: var(--border);
  align-self: center;
}

/* ============================================================
   SECTION HEADER
   ============================================================ */

.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
  margin-top: 32px;
}

.heading-lg {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--text-primary);
}

/* ============================================================
   PAGINATION
   ============================================================ */

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding-top: 16px;
}

.page-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  height: 36px;
  border-radius: 6px;
  background: var(--card);
  border: 1px solid var(--border);
  font-family: var(--font-data);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  text-decoration: none;
  transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease;
}

.page-btn:hover {
  border-color: var(--accent);
  color: var(--text-primary);
}

.page-btn--active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
  font-weight: 600;
}

.page-dots {
  color: var(--text-tertiary);
  font-size: var(--text-sm);
}

/* ============================================================
   FOOTER NOTE
   ============================================================ */

.footer-note {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
  text-align: center;
  padding-top: 12px;
  margin-top: 20px;
}

/* ============================================================
   ABOUT / HOW IT WORKS PAGE
   ============================================================ */

.about-page { max-width: 1200px; margin: 0 auto; }

.about-title {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 700;
  color: var(--text-primary);
  /* Page title: generous bottom margin so the intro paragraph reads as a
     separate thought, not a descender on the title. 24px > line-height of
     title itself, which is the threshold where "single block" breaks into
     "title + intro." */
  margin-bottom: 24px;
  letter-spacing: -0.01em;
}

/* System Health callout — the clamp-rate receipt, pulled up to the top of
   the page so the system's self-check is visible before the reader commits
   to 2,400 words of methodology. Mono numerals, tertiary framing, one
   dominant percentage with a tooltip for the raw counts. */
.about-health {
  display: grid;
  grid-template-columns: auto auto 1fr;
  gap: 16px 20px;
  align-items: baseline;
  padding: 14px 18px;
  margin-bottom: 40px;
  background: color-mix(in oklch, var(--accent) 6%, var(--card));
  border: 1px solid color-mix(in oklch, var(--accent) 22%, var(--border));
  border-radius: 8px;
  max-width: 68ch;
}

.about-health__label {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
}

.about-health__value {
  font-family: var(--font-data);
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--accent);
  font-feature-settings: "tnum";
  cursor: help;
}

.about-health__desc {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  line-height: var(--leading-relaxed);
  grid-column: 1 / -1;
}

.about-health__desc strong { color: var(--text-primary); }

@media (max-width: 768px) {
  .about-health {
    grid-template-columns: auto 1fr;
  }
  .about-health__label { grid-column: 1; }
  .about-health__value { grid-column: 2; justify-self: end; }
}

/* Two-column layout on desktop: sticky TOC left, main content right. Mobile
   drops back to single column and the TOC is hidden — deep-linking from
   tooltips still works via the anchor IDs on each section. */
.about-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
}

.about-content { min-width: 0; max-width: 800px; }

.about-toc { display: none; }

@media (min-width: 1024px) {
  .about-layout {
    grid-template-columns: 220px minmax(0, 1fr);
    gap: 48px;
  }
  .about-toc {
    display: block;
    position: sticky;
    top: 80px;
    align-self: start;
    max-height: calc(100vh - 100px);
    overflow-y: auto;
  }
}

.about-toc-heading {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  margin: 0 0 12px;
}

.about-toc-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.about-toc-list a {
  display: block;
  padding: 5px 10px;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-secondary);
  text-decoration: none;
  border-radius: 4px;
  transition: color 150ms ease, background-color 150ms ease;
}

.about-toc-list a:hover {
  color: var(--accent);
  background: color-mix(in oklch, var(--accent) 8%, transparent);
}

/* Active-section state — set by active_section_controller.js as the user
   scrolls. The small left border is the key signal: color + background
   alone read as hover; the 2px accent rule gives the currently-active
   section an obvious "you are here" marker in a dense 15-item list.
   Padding nudges right by the rule width so the label doesn't shift. */
.about-toc-list a.toc-active {
  color: var(--accent);
  background: color-mix(in oklch, var(--accent) 10%, transparent);
  box-shadow: inset 2px 0 0 var(--accent);
  padding-left: 12px;
}

.about-toc-list a:focus-visible {
  outline: 1px solid color-mix(in oklch, var(--accent) 50%, transparent);
  outline-offset: 2px;
}

/* Anchor-jump offset so clicked section headings land below the sticky
   site nav (56px desktop, 52px mobile) instead of hiding behind it.
   Vertical rhythm between sections lives here too. */
.about-section {
  scroll-margin-top: 80px;
  margin-bottom: 48px;
}

/* H3 subheading used when an H2 section has internal structure — e.g. the
   three components of Event-Weighted K-Factor. Smaller than .about-heading,
   same display font, visible hierarchy without the extra padding of a new
   section break. Asymmetric margins: generous top (40px) separates it
   from the previous block; tight bottom (16px) binds it to the first
   paragraph below. */
.about-subheading {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
  margin: 40px 0 16px;
  letter-spacing: -0.01em;
}

/* Smaller sub-heading used to group v5.3 sub-topics inside the
   Calibration section. Slightly less prominent than the full
   .about-subheading so the v5.1 bootstrap narrative stays the
   dominant break within Calibration. Same asymmetric-margin idea,
   scaled down. */
.about-subheading--muted {
  font-size: var(--text-base);
  color: var(--text-secondary);
  margin: 28px 0 10px;
  font-weight: 600;
  letter-spacing: 0;
}

.about-intro {
  font-size: var(--text-md);
  color: var(--text-secondary);
  line-height: var(--leading-loose);
  /* 24px matches the rhythm used for body paragraphs (.about-text) —
     consistent block gap down the whole page. The last intro gets an
     extra 16px before the System Health callout / first section to
     create a page-level break. */
  margin-bottom: 24px;
  max-width: 68ch;
}

.about-intro:last-of-type { margin-bottom: 40px; }

.about-intro strong { color: var(--text-primary); }

/* Second intro beat — the technical sentence for the analyst persona who
   skipped the first-timer welcome. Slightly quieter so the welcoming
   first beat stays dominant. */
.about-intro--technical {
  font-size: var(--text-base);
  color: var(--text-tertiary);
}

.about-intro--technical strong { color: var(--text-secondary); }

.about-heading {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--text-primary);
  /* 16px below the H2 — tight enough that the first paragraph feels bound
     to the heading, loose enough that the heading reads as a separate
     line. The top of each H2 is anchored by the previous section's
     margin-bottom: 48px (see .about-section). */
  margin-bottom: 16px;
}

.about-text {
  font-size: var(--text-md);
  color: var(--text-secondary);
  line-height: var(--leading-loose);
  /* 24px between top-level blocks (paragraph → paragraph, paragraph →
     card, paragraph → table). Matches the margin on .about-card /
     .about-example / .about-table below so the whole section reads as a
     consistent rhythm regardless of block type. */
  margin-bottom: 24px;
  max-width: 68ch;
}

.about-text strong { color: var(--text-primary); }
.about-text a { color: var(--accent); }
/* Muted caption beneath a preceding block. 16px top margin sits tighter
   against its anchor than a fresh paragraph would, signalling
   "footnote to the above" rather than "new idea." */
.about-text--muted { color: var(--text-tertiary); font-size: var(--text-sm); margin-top: 16px; margin-bottom: 24px; }

/* Inline accent link used in prose paragraphs (changelog, about-intro). */
.inline-link {
  color: var(--accent);
  border-bottom: 1px solid transparent;
  transition: border-color 150ms ease, color 150ms ease;
}
.inline-link:hover {
  color: var(--accent-hover);
  border-bottom-color: var(--accent-hover);
}

/* Info cards */
.about-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  /* Same 24px block rhythm as .about-text / .about-intro / .about-example
     — consistent gap to the next block regardless of what type it is. */
  margin-bottom: 24px;
}

.about-card-row {
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}

.about-card-row:last-child { border-bottom: none; }

.about-card-label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  margin-bottom: 6px;
}

.about-card-value {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 700;
  color: var(--text-primary);
  margin-bottom: 4px;
  letter-spacing: -0.01em;
}

.about-card-desc {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: var(--leading-relaxed);
}

/* Numbered factors */
.about-factors {
  display: flex;
  flex-direction: column;
  gap: 16px;
  /* Consistent 24px block rhythm after the list, same as cards/paragraphs. */
  margin-bottom: 24px;
}

.about-factor {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}

.about-factor-num {
  width: 36px;
  height: 36px;
  border-radius: 8px;
  background: var(--card);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--text-primary);
  flex-shrink: 0;
}

/* Format-code tints for the P/L/E factor chips in the Formats section.
   Each uses the tier color as background with white text for contrast. */
.about-factor-num--sector   { background: var(--tier-sector); border-color: var(--tier-sector); color: var(--text-primary); }
.about-factor-num--regional { background: var(--tier-regional); border-color: var(--tier-regional); color: var(--text-primary); }
.about-factor-num--galactic { background: var(--tier-galactic); border-color: var(--tier-galactic); color: var(--text-primary); }

.about-factor-content { flex: 1; }

.about-factor-content h3 {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 4px;
}

.about-factor-content p {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: var(--leading-relaxed);
  max-width: 68ch;
}

/* Tier/round tables */
.about-table {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  /* Consistent 24px block rhythm after the table. */
  margin-bottom: 24px;
}

.about-table-header {
  display: flex;
  padding: 10px 20px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: 0.5px;
  text-transform: uppercase;
  border-bottom: 1px solid var(--border);
}

.about-table-row {
  display: flex;
  padding: 12px 20px;
  align-items: center;
  border-bottom: 1px solid var(--border);
}

.about-table-row:last-child { border-bottom: none; }

.about-table-col-tier { width: 140px; flex-shrink: 0; }
.about-table-col-val { width: 100px; flex-shrink: 0; font-weight: 600; color: var(--accent); }

/* Highlighted multiplier values — replaces inline style="color: var(--warning);"
   on the Putting-It-All-Together rows that call out finals-winner totals. */
.about-table-col-val--highlight { color: var(--warning); }
.about-table-col-val--champion  { color: var(--warning); font-weight: 700; }

/* The single "Galactic Finals (champion)" callout row — the one match worth
   the most in the system. Warm tint + slightly heavier border to match its
   semantic weight. Replaces an inline background style. */
.about-table-row--champion {
  background: color-mix(in oklch, var(--warning) 8%, transparent);
}

/* Labeled-list modifier tints for the Qualifying Events card. Replaces
   inline color styles on the Included / Excluded labels. */
.about-card-label--positive { color: var(--positive); }
.about-card-label--muted    { color: var(--text-tertiary); }
.about-table-col-desc { flex: 1; font-size: var(--text-sm); color: var(--text-secondary); }

/* Inline SVG calibration chart. Replaces the old bucket table on
   /how-it-works. The SVG is fully self-contained (no JS); layout and
   colors are hard-coded in the helper to match DESIGN.md tokens. The
   figure wrapper holds the caption and provides the surface/border
   continuity with adjacent .about-card / .about-text blocks. */
.calibration-chart-figure {
  /* 24px block rhythm — matches .about-card / .about-table / .about-example. */
  margin: 0 0 24px;
  padding: 20px 24px 16px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
}

.calibration-chart {
  display: block;
  width: 100%;
  height: auto;
  max-width: 100%;
}

.calibration-chart-caption {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  line-height: var(--leading-relaxed);
  color: var(--text-secondary);
  margin: 12px 0 0;
  max-width: 64ch;
}

/* Screen-reader-only: keeps the per-bucket numeric table accessible to
   assistive tech after the visual chart replaced it. Sighted users see the
   SVG + hover tooltips; screen-reader users get a proper <table>. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Worked example — the one place on the page where the math is walked
   end-to-end instead of stated as a formula. Stepped numbered blocks
   so the reader can follow the order of operations visually. */
.about-example {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 24px;
  /* Consistent 24px block rhythm after the example. */
  margin-bottom: 24px;
}

.about-example__setup {
  font-size: var(--text-base);
  color: var(--text-secondary);
  line-height: var(--leading-loose);
  margin: 0 0 24px;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--border);
}

.about-example__steps {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.about-example__step {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}

.about-example__step-num {
  width: 28px;
  height: 28px;
  border-radius: 14px;
  background: color-mix(in oklch, var(--accent) 18%, var(--card));
  border: 1px solid color-mix(in oklch, var(--accent) 35%, var(--border));
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--accent);
  flex-shrink: 0;
  margin-top: 1px;
}

.about-example__step-body { flex: 1; min-width: 0; }

.about-example__step-heading {
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-primary);
  margin: 0 0 4px;
  letter-spacing: -0.005em;
}

.about-example__step-body p {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: var(--leading-loose);
  margin: 0;
}

.about-example__step-body code {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  background: color-mix(in oklch, var(--accent) 10%, transparent);
  color: var(--text-primary);
  padding: 1px 5px;
  border-radius: 3px;
}

/* ============================================================
   CHANGELOG — reverse-chronological entry list with sticky version
   sidebar. Shares the .content-area.about-page wrapper with how-it-works
   and inherits .about-title / .about-intro typography.
   ============================================================ */

.changelog-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
}

@media (min-width: 1024px) {
  .changelog-layout {
    grid-template-columns: 180px minmax(0, 1fr);
    gap: 48px;
  }
  .changelog-toc {
    position: sticky;
    top: 80px;
    align-self: start;
    max-height: calc(100vh - 100px);
    overflow-y: auto;
  }
}

.changelog-toc { display: none; }
@media (min-width: 1024px) { .changelog-toc { display: block; } }

.changelog-toc__heading {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  margin: 0 0 12px;
}

.changelog-toc__list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.changelog-toc__link {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 10px;
  border-radius: 4px;
  text-decoration: none;
  color: var(--text-secondary);
  transition: color 150ms ease, background-color 150ms ease;
}

.changelog-toc__link:hover {
  color: var(--accent);
  background: color-mix(in oklch, var(--accent) 8%, transparent);
}

/* Active-section state — same 2px left-rule marker as the how-it-works
   TOC. The link stays compact (version + date on one row) but the rule
   gives it a clear "you are here" anchor during scroll. */
.changelog-toc__link.toc-active {
  background: color-mix(in oklch, var(--accent) 10%, transparent);
  box-shadow: inset 2px 0 0 var(--accent);
  padding-left: 12px;
}

.changelog-toc__link.toc-active .changelog-toc__version { color: var(--accent); }

.changelog-toc__link:focus-visible {
  outline: 1px solid color-mix(in oklch, var(--accent) 50%, transparent);
  outline-offset: 2px;
}

.changelog-toc__version {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-weight: 600;
}

.changelog-toc__date {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  color: var(--text-tertiary);
}

.changelog-toc__link--current .changelog-toc__version {
  color: var(--accent);
}

.changelog-content { min-width: 0; max-width: 800px; }

.changelog-entries {
  display: flex;
  flex-direction: column;
  /* 24px gap between version entries — they're major boundaries (each
     is a full release) and 16px made them feel like one continuous log
     rather than a list of distinct shipments. */
  gap: 24px;
}

.changelog-entry {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  /* Slightly more generous padding — 24px on top matches bottom, and the
     extra 4px over the previous 20/24 asymmetric value reads as "this is
     a substantial release note" rather than "compact status update."
     Horizontal 28px gives the prose more breathing room off the border. */
  padding: 24px 28px;
  scroll-margin-top: 80px;
  transition: border-color 150ms ease;
}

.changelog-entry:hover {
  border-color: color-mix(in oklch, var(--accent) 20%, var(--border));
}

.changelog-entry:target,
.changelog-entry--current {
  border-color: color-mix(in oklch, var(--accent) 40%, var(--border));
}

.changelog-entry:target .changelog-entry__permalink,
.changelog-entry:hover .changelog-entry__permalink {
  opacity: 1;
}

.changelog-entry__header {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 12px;
  /* Header/body separator: 16px + 16px = 32px total visual gap across the
     hairline. Was 24px (12+12). The extra breathing room lets the
     release body start as a clean reading surface instead of running
     up against the version + date + tags strip. */
  margin-bottom: 16px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border);
}

.changelog-entry__meta {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.changelog-entry__version {
  font-family: var(--font-data);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: -0.01em;
}

/* Current version gets the accent color as the signal. No glow — glow is
   reserved for the rating hero, #1 rank, table rating cells, and the
   Galactic tier badge (see DESIGN.md glow-restraint principle). The
   paired "Live" tag on the same row carries the freshness signal. */
.changelog-entry--current .changelog-entry__version {
  color: var(--accent);
}

.changelog-entry__date {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
  letter-spacing: 0.04em;
}

.changelog-entry__badges {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
}

/* Small tags categorizing each change. "Rating change" is positive-tinted
   because that's the signal a player cares about when scanning the list.
   Engine / Display are neutral. "Live" is the top-version marker. */
.changelog-entry__tag {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  padding: 3px 8px;
  border-radius: 3px;
  border: 1px solid currentColor;
  white-space: nowrap;
}

.changelog-entry__tag--rating {
  color: var(--positive);
  background: color-mix(in oklch, var(--positive) 10%, transparent);
}

.changelog-entry__tag--engine {
  color: var(--text-secondary);
  background: transparent;
}

/* Overall composite is a specific scope within Engine — same color tier,
   but its own class so the taxonomy reads (engine / overall / display /
   admin / rating / current) rather than overloading --engine to mean
   three different things like the pre-v5 taxonomy did. */
.changelog-entry__tag--overall {
  color: var(--text-secondary);
  background: transparent;
}

/* Admin-tooling tag — muted tint so it doesn't compete for attention
   with rating/engine tags that actually affect player-facing numbers. */
.changelog-entry__tag--admin {
  color: var(--text-tertiary);
  background: transparent;
}

.changelog-entry__tag--display {
  color: var(--text-tertiary);
  background: transparent;
}

.changelog-entry__tag--current {
  color: var(--accent);
  background: color-mix(in oklch, var(--accent) 12%, transparent);
  border-color: color-mix(in oklch, var(--accent) 40%, var(--border));
  /* The one element on the page whose job is "this version is running
     in production right now." 3.5s breathing pulse is slow enough that
     it never reads as chrome noise but unmistakable once you notice —
     earned glow for a signal that actually earns it (see DESIGN.md
     "Earn every glow"). Disabled under prefers-reduced-motion below. */
  animation: hri-live-pulse 3.5s ease-in-out infinite;
}

@keyframes hri-live-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--accent) 0%, transparent); }
  50%      { box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent) 22%, transparent); }
}

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

.changelog-entry__permalink {
  font-family: var(--font-data);
  font-size: var(--text-md);
  font-weight: 400;
  color: var(--text-tertiary);
  text-decoration: none;
  opacity: 0;
  transition: opacity 150ms ease, color 150ms ease;
  padding: 0 4px;
}

.changelog-entry__permalink:hover {
  opacity: 1;
  color: var(--accent);
}

/* Keyboard focus: reveal the glyph AND show a visible ring so the tab
   stop is discoverable. Matches the :focus-visible pattern used by
   `.th-date__sort` and `.about-faq-q` elsewhere in this file. */
.changelog-entry__permalink:focus-visible {
  opacity: 1;
  color: var(--accent);
  outline: 1px solid color-mix(in oklch, var(--accent) 60%, transparent);
  outline-offset: 2px;
  border-radius: 3px;
}

.changelog-entry__body {
  font-size: var(--text-base);
  line-height: var(--leading-loose);
  color: var(--text-secondary);
}

.changelog-entry__headline {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text-primary);
  /* 14px — gives the one-sentence headline breathing room before the
     prose starts. 10px ran the headline into the first paragraph. */
  margin: 0 0 14px;
  letter-spacing: -0.005em;
}

.changelog-entry__prose {
  margin: 0;
}

.changelog-entry__prose code {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  background: color-mix(in oklch, var(--accent) 8%, transparent);
  color: var(--text-primary);
  padding: 1px 5px;
  border-radius: 3px;
}

.changelog-entry__list {
  /* Slightly more air between a parent paragraph and the bullet list it
     introduces. 4px read as the paragraph and list being one continuous
     block; 8px separates them while still keeping the list visually
     bound to its parent. */
  margin: 8px 0 0;
  padding-left: 20px;
  color: var(--text-secondary);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
}

.changelog-entry__list li {
  /* 6px between bullets. Enough to let each point breathe without
     making the list feel like a stack of detached sentences. */
  margin-bottom: 6px;
}

/* Mobile: tags stack below the version+date row when screen is narrow */
@media (max-width: 640px) {
  .changelog-entry__badges { margin-left: 0; }
  .changelog-entry__permalink { display: none; }
}

/* FAQ — native <details> disclosures. Collapsed by default so the 8-item
   block reads as a scannable index of questions; clicking any one opens
   just that answer. No JS, no ARIA, no state management. */
.about-faq {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0;
  margin-bottom: 8px;
  transition: border-color 150ms ease;
}

.about-faq[open] {
  border-color: color-mix(in oklch, var(--accent) 35%, var(--border));
}

.about-faq-q {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 16px 20px;
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-primary);
  cursor: pointer;
  list-style: none;
  transition: color 150ms ease;
}

.about-faq-q::-webkit-details-marker { display: none; }

.about-faq-q::after {
  content: "+";
  margin-left: auto;
  font-family: var(--font-data);
  font-size: var(--text-lg);
  font-weight: 400;
  color: var(--text-tertiary);
  transition: transform 200ms ease, color 150ms ease;
}

.about-faq[open] .about-faq-q::after {
  content: "\2212"; /* minus sign */
  color: var(--accent);
}

.about-faq-q:hover { color: var(--accent); }

.about-faq-q:focus-visible {
  outline: 1px solid color-mix(in oklch, var(--accent) 50%, transparent);
  outline-offset: -1px;
  border-radius: 8px;
}

.about-faq-a {
  padding: 0 20px 18px;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: var(--leading-loose);
  margin: 0;
}

.about-faq-a a { color: var(--accent); }

/* ============================================================
   RESPONSIVE
   ============================================================ */

@media (max-width: 1024px) {
  .content-area { padding: 24px 32px 32px; }
  .hero-section { padding: 36px 32px 28px; gap: 20px 28px; }
  .hero-section::after { left: 32px; right: 32px; }
  .hero-logo { height: 88px; }
  .hero-title { font-size: var(--text-4xl); }
  .hero-sub { font-size: var(--text-base); }
  .rank-confidence, .th-confidence { display: none; }
  .filter-row .search-input { width: 320px; max-width: 320px; }
}

@media (max-width: 640px) {
  .content-area { padding: 20px 16px 24px; }
  .hero-section { padding: 28px 16px 22px; gap: 14px 20px; flex-direction: column; text-align: center; align-items: center; }
  .hero-section::after { left: 16px; right: 16px; }
  .hero-logo { height: 72px; }
  .hero-lockup { align-items: center; }
  .hero-title { font-size: var(--text-3xl); text-align: center; }
  .hero-sub { font-size: var(--text-sm); text-align: center; }
  /* System-status wraps onto multiple lines on narrow screens so the
     4-segment fused line stays readable at 390px without horizontal
     overflow. Each segment keeps its pulse/separator, just stacked. */
  .system-status { flex-wrap: wrap; justify-content: center; row-gap: 2px; }

  .filter-row {
    flex-direction: column;
    align-items: stretch;
  }
  .filter-row .search-input { width: 100%; max-width: none; }
  .filter-chips { flex-wrap: wrap; }

  /* Hide desktop column headers on mobile — rows use stacked layout instead */
  .table-header .th-record,
  .table-header .th-winrate,
  .table-header .th-events,
  .table-header .th-form,
  .table-header .th-confidence,
  .table-header .th-tier,
  .table-header .th-date,
  .table-header .th-format,
  .table-header .th-players,
  .table-header .th-winner,
  .table-header .th-delta { display: none; }

  /* Leaderboard row becomes a 2-line card */
  .rank-row {
    flex-wrap: wrap;
    height: auto;
    min-height: 64px;
    padding: 10px 16px;
    row-gap: 6px;
    column-gap: 12px;
  }
  /* Mobile: left-align so rank hugs the left edge and sits naturally before
     the player name. Wider than desktop's 72px isn't needed here since the
     row-gap of the wrapping flex keeps rank and name separated. 56px fits
     5 digits at 16px Geist Mono Bold with a touch of room. */
  .rank-num { width: 56px; padding-right: 8px; text-align: left; }
  .rank-player { flex: 1; min-width: 0; }
  .rank-rating { width: auto; text-align: right; }

  .rank-meta {
    display: flex;
    flex-basis: 100%;
    align-items: baseline;
    gap: 12px;
    font-size: var(--text-2xs);
    color: var(--text-tertiary);
  }
  .rank-meta > span {
    width: auto;
    text-align: left;
    font-size: var(--text-2xs);
    color: var(--text-tertiary);
  }
  .rank-meta .rank-winrate { font-weight: 600; }
  .rank-meta .rank-record::before { content: "W-L "; color: var(--text-tertiary); opacity: 0.7; }
  .rank-meta .rank-events::before { content: "Events "; color: var(--text-tertiary); opacity: 0.7; }
  .rank-meta .confidence-badge { font-size: var(--text-2xs); color: var(--text-tertiary); }

  /* Tournament row becomes a 2-line card */
  .tourn-row {
    flex-wrap: wrap;
    height: auto;
    min-height: 60px;
    padding: 10px 16px;
    row-gap: 6px;
    column-gap: 12px;
    align-items: baseline;
  }
  .tourn-event { flex: 1; min-width: 0; }

  .tourn-meta {
    display: flex;
    flex-basis: 100%;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px 12px;
    font-size: var(--text-2xs);
    color: var(--text-tertiary);
  }
  .tourn-meta > * {
    width: auto;
    text-align: left;
  }
  .tourn-meta .tourn-rating { font-size: var(--text-sm); }
  .tourn-meta .tourn-delta { margin-left: auto; }

  /* Tournaments index row (.rank-row.tournament-row): on mobile the event
     name takes a full-width first line, then tier/format/players/winner/
     date wrap beneath as a compact meta strip. Without this, the fixed-
     width columns flow awkwardly and the tier badge jams into the middle
     of a wrapping event name. */
  .tournament-row {
    flex-wrap: wrap;
    height: auto;
    min-height: 68px;
    padding: 10px 16px;
    row-gap: 6px;
    column-gap: 10px;
    align-items: baseline;
  }
  .tournament-row .tourn-event {
    flex-basis: 100%;
    width: 100%;
  }
  .tournament-row .tourn-tier,
  .tournament-row .tourn-format,
  .tournament-row .tourn-players,
  .tournament-row .tourn-winner,
  .tournament-row .tourn-date {
    width: auto;
    text-align: left;
    font-size: var(--text-xs);
  }
  .tournament-row .tourn-format,
  .tournament-row .tourn-players,
  .tournament-row .tourn-date {
    color: var(--text-tertiary);
  }
  .tournament-row .tourn-winner { margin-left: auto; }
  .tournament-row .tourn-players::after { content: " players"; opacity: 0.7; }

  .profile-hero {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
  }
  .profile-hero__rating {
    align-items: flex-start;
    text-align: left;
  }
  .profile-hero__number { font-size: var(--text-6xl); }
  .profile-hero__number--muted { font-size: var(--text-4xl); }

  .metric-strip { gap: 8px 16px; }
  .metric-strip__sep { display: none; }

  .site-nav { padding: 0 16px; height: 52px; }
  .site-logo { font-size: var(--text-md); }
  .nav-logo-img { height: 32px; }
}

/* ============================================================
   v3 — Trust layer: cohort toggle, RD bands, credible interval
   ============================================================ */

/* Secondary filter row — sits below the format chips and search */
.filter-row--secondary {
  margin-top: 12px;
  margin-bottom: 8px;
}

/* Overall format is experimental — visually muted so players prefer per-format */
.chip--experimental {
  opacity: 0.75;
  font-style: italic;
}

.chip--experimental.chip--active { opacity: 1; font-style: normal; }

/* Ranked / Provisional cohort toggle */
.cohort-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  padding: 4px;
}

.cohort-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border-radius: 4px;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--text-tertiary);
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease;
}

.cohort-chip:hover { color: var(--text-primary); }

.cohort-chip--active {
  background: var(--accent-surface);
  color: var(--accent);
  font-weight: 600;
}

.cohort-count {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
  background: var(--bg);
  padding: 1px 6px;
  border-radius: 10px;
  min-width: 18px;
  text-align: center;
}

.cohort-chip--active .cohort-count {
  background: var(--accent-soft);
  color: var(--accent);
}

/* Rating cell — number inherits typography from .rank-rating parent;
   only .rank-rating__ci below needs its own override for the smaller,
   dimmer ± credible-interval span. The .rank-row--provisional override
   further down still targets .rank-rating__number by class name to
   desaturate the number in provisional rows. */
.rank-rating__ci {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 400;
  color: var(--text-tertiary);
  margin-left: 6px;
  cursor: help;
}

/* Champion rating — earned glow for the #1 player on any format's
   leaderboard. All rank ratings default to accent blue, so color alone
   isn't the differentiator; the champion gets a two-layer glow + bumped
   weight so the #1 number reads as "THE number." One row per page earns
   this. Matches the tournament-show champion pedestal treatment. */
.rank-rating__number--champion {
  font-weight: 800;
  text-shadow: 0 0 14px var(--accent-hot), 0 0 4px var(--accent-strong);
}

/* Provisional row — visually quieter on the ranked leaderboard if one slips
   in, and always on the Provisional tab. 0.82 was almost imperceptible
   against the already-dim palette; 0.65 actually reads as "still stabilizing"
   at a glance. Accent color loses its glow treatment. Hover restores to full
   so the row is still scannable on intent. */
.rank-row--provisional { opacity: 0.65; }
.rank-row--provisional .rank-rating__number { color: var(--text-secondary); }
.rank-row--provisional:hover { opacity: 1; }

/* ============================================================
   STYLED TOOLTIPS — replaces native title="" on data/help elements.
   Usage: <span class="has-tooltip" data-tooltip="Full sentence here">78%</span>
   Supports an optional position hint via data-tooltip-pos="left|right|bottom".
   Default anchor: above the element, center-aligned.
   ============================================================ */

.has-tooltip {
  position: relative;
  cursor: help;
}

.has-tooltip::before,
.has-tooltip::after {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 300;
}

/* Tooltip body */
.has-tooltip::after {
  content: attr(data-tooltip);
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-primary);
  padding: 10px 14px;
  border-radius: 6px;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 400;
  line-height: var(--leading-relaxed);
  letter-spacing: 0.005em;
  text-align: left;
  text-transform: none;
  width: max-content;
  max-width: 320px;
  white-space: normal;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.55);
}

/* Caret */
.has-tooltip::before {
  content: "";
  bottom: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  border: 6px solid transparent;
  border-top-color: var(--border);
  width: 0;
  height: 0;
}

.has-tooltip:hover::before,
.has-tooltip:hover::after,
.has-tooltip:focus-visible::before,
.has-tooltip:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* Right-anchor variant — for tooltips near the left edge */
.has-tooltip[data-tooltip-pos="right"]::after,
.has-tooltip[data-tooltip-pos="right"]::before {
  left: auto;
  right: 0;
  transform: translateX(0) translateY(4px);
}
.has-tooltip[data-tooltip-pos="right"]::before { right: 10px; }
.has-tooltip[data-tooltip-pos="right"]:hover::after,
.has-tooltip[data-tooltip-pos="right"]:hover::before { transform: translateX(0) translateY(0); }

/* Left-anchor variant — for tooltips near the right edge (leaderboard Confidence column) */
.has-tooltip[data-tooltip-pos="left"]::after,
.has-tooltip[data-tooltip-pos="left"]::before {
  left: 0;
  right: auto;
  transform: translateX(0) translateY(4px);
}
.has-tooltip[data-tooltip-pos="left"]::before { left: 10px; }
.has-tooltip[data-tooltip-pos="left"]:hover::after,
.has-tooltip[data-tooltip-pos="left"]:hover::before { transform: translateX(0) translateY(0); }

/* Bottom-anchor — for tooltips that would clip off the top of the viewport */
.has-tooltip[data-tooltip-pos="bottom"]::after {
  bottom: auto;
  top: calc(100% + 10px);
  transform: translateX(-50%) translateY(-4px);
}
.has-tooltip[data-tooltip-pos="bottom"]::before {
  bottom: auto;
  top: calc(100% + 4px);
  border-top-color: transparent;
  border-bottom-color: var(--border);
  transform: translateX(-50%) translateY(-4px);
}
.has-tooltip[data-tooltip-pos="bottom"]:hover::after,
.has-tooltip[data-tooltip-pos="bottom"]:hover::before { transform: translateX(-50%) translateY(0); }

/* Confidence percentage — leaderboard + profile. Hover reveals raw RD.
   Tier colors come from rd_band so a glance still tells you if a rating
   is established/developing/provisional, but the number is the data. */
.confidence-pct {
  display: inline-flex;
  align-items: baseline;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-secondary);
  cursor: help;
}

.confidence-pct--established { color: var(--positive); }
.confidence-pct--developing  { color: var(--warning); }
.confidence-pct--provisional { color: var(--text-tertiary); }

/* RD-band badge — pairs with the confidence pill on profile hero. Shown only
   when rating is not yet Established (RD > 80), so the player sees "Provisional"
   or "Developing" spelled out next to their rating instead of learning from
   color + tooltip alone. Brand tone: honest about uncertainty, not apologetic. */
.band-badge {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-body);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 10px;
  border: 1px solid transparent;
  cursor: help;
}

.band-badge--established {
  color: var(--positive);
  background: rgba(34, 197, 94, 0.08);
  border-color: rgba(34, 197, 94, 0.25);
}

.band-badge--developing {
  color: var(--warning);
  background: rgba(245, 158, 11, 0.08);
  border-color: rgba(245, 158, 11, 0.25);
}

.band-badge--provisional {
  color: var(--text-tertiary);
  background: rgba(148, 163, 184, 0.08);
  border-color: rgba(148, 163, 184, 0.2);
}

/* Provisional banner on profile pages */
.provisional-banner {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 16px;
  margin-bottom: 20px;
  background: rgba(245, 158, 11, 0.06);
  border: 1px solid rgba(245, 158, 11, 0.25);
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
}

.provisional-banner strong { color: var(--warning); font-weight: 600; }

/* Calm informational banner — tiny data, separate-format context, etc.
   Accent-blue tint instead of the warning-amber provisional banner.
   Collapsed by default as a <details> so the table sits higher on the
   page; summary line stays visible and clickable. */
.format-notice {
  margin-bottom: var(--space-md);
  background: var(--accent-wash);
  border: 1px solid var(--accent-mid);
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
}

.format-notice > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--space-md);
  cursor: pointer;
  list-style: none;
  user-select: none;
}

/* Suppress the native disclosure triangle — we emit our own chevron-style
   cue on the right. webkit targets Safari specifically. */
.format-notice > summary::-webkit-details-marker { display: none; }
.format-notice > summary::marker { display: none; }

.format-notice strong {
  color: var(--accent);
  font-weight: 600;
}

.format-notice__cue {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-tertiary);
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  transition: color 150ms ease, border-color 150ms ease;
}

.format-notice:hover .format-notice__cue,
.format-notice[open] .format-notice__cue {
  color: var(--accent);
  border-color: var(--accent);
}

.format-notice[open] .format-notice__cue::after { content: " ▲"; }
.format-notice:not([open]) .format-notice__cue::after { content: " ▼"; }

.format-notice__body {
  padding: 0 var(--space-md) var(--space-sm);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.format-notice__body p { margin: 0; }

/* Plain-English explainer inside each tournament dropdown on the profile page.
   Answers the community question "why did I get 0 points / so few points at
   a big tournament?" The label sits above the sentence in Geist Mono caps
   so it reads as a data row header, not a generic blurb. */
/* "Why Your Rating Moved" callout — warm amber instead of accent blue so
   it reads as an *insight* layer rather than competing with the site's
   primary accent. Amber = "pay attention, this is annotation," not
   "earned signal." Keeps the brand's accent budget reserved for ratings,
   rankings, and glow treatments. */
.tourn-explainer {
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  background: rgba(245, 158, 11, 0.07);
  border-left: 2px solid var(--warning);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.tourn-explainer__label {
  display: block;
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 700;
  color: var(--warning);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  margin-bottom: var(--space-2xs);
}

.tourn-explainer__text {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--text-secondary);
}

.tourn-explainer__cta {
  margin: var(--space-xs) 0 0;
  font-size: var(--text-xs);
  font-family: var(--font-data);
}


/* Admin surfaces (anomaly review, etc). Intentionally understated — these
   aren't end-user pages, so we lean on the same table/form primitives as
   the main app rather than building a second visual language. */
.admin-count {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

/* Dot separator between sub-count facets ("27 events · Apr 2024 - Mar 2026")
   that also works when inline with text (the wrapping span handles the
   surrounding whitespace so ERB's whitespace-sensitive output doesn't leak). */
.admin-count__sep {
  margin-inline: 4px;
  color: var(--text-tertiary);
  opacity: 0.6;
}

.admin-filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  align-items: flex-end;
  margin: 12px 0 20px;
  padding: 12px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

.admin-filter-row label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--text-micro);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-tertiary);
}

.admin-filter-row input,
.admin-filter-row select {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  color: var(--text-primary);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 6px 8px;
  min-width: 110px;
}

.admin-filter-row button {
  border: none;
  cursor: pointer;
  padding: 7px 14px;
}

/* Tournament detail page ─────────────────────────────────────────────── */

/* Cap show-page content width to match /tournaments index (1280px). Keeps
   both tournament views optically aligned on wide monitors — bracket and
   shift table stop running to the far edge where rows become tiring to
   scan. Below 1280px the rule is a no-op, same as the index's rule. */
.tournament-show {
  max-width: 1280px;
  margin-inline: auto;
}

/* Tournament hero — three tiers: title row (name + tier badge), meta row
   (format · date · location · melee link), and a two-pedestal data block
   (Champion on the left, Field Size on the right). The pedestal gives the
   page a dominant data point that matches the site's "rating is the hero"
   principle without inventing a fake number: champion is the narrative of
   any tournament, field size is the scale. Everything else subordinates. */
.tournament-hero {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-lg) 0;
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-2xl);
}

.tournament-hero__title-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  flex-wrap: wrap;
}

.tournament-hero__name {
  font-family: var(--font-display);
  font-size: clamp(28px, 4vw, 36px);
  font-weight: 700;
  line-height: var(--leading-tight);
  color: var(--text-primary);
  margin: 0;
  flex: 1;
  min-width: 0;
  text-wrap: balance;
}

.tournament-hero__meta {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  flex-wrap: wrap;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

.tournament-hero__meta-item {
  color: var(--text-secondary);
}

.tournament-hero__meta-sep {
  color: var(--text-tertiary);
  opacity: 0.6;
}

.tournament-hero__meta-link {
  color: var(--text-tertiary);
  text-decoration: none;
  border-bottom: 1px dashed transparent;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.tournament-hero__meta-link:hover,
.tournament-hero__meta-link:focus-visible {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* Two-pedestal data block: champion (left) + field size (right). Separator
   above pushes it visually away from the name/meta. Grid with 1fr auto lets
   the champion take available width while field size hugs the right edge. */
.tournament-hero__pedestal {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-xl) var(--space-2xl);
  align-items: end;
  padding-top: var(--space-md);
  margin-top: var(--space-xs);
  border-top: 1px solid var(--border);
}

.tournament-hero__pedestal-label {
  display: block;
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-tertiary);
  margin-bottom: var(--space-2xs);
}

.tournament-hero__champion {
  min-width: 0;
}

.tournament-hero__champion-name {
  font-family: var(--font-data);
  font-size: clamp(22px, 3.2vw, 28px);
  font-weight: 700;
  line-height: var(--leading-snug);
  color: var(--accent);
  text-shadow: 0 0 14px var(--accent-strong);
  text-decoration: none;
  transition: text-shadow 0.15s ease;
}

.tournament-hero__champion-name:hover,
.tournament-hero__champion-name:focus-visible {
  text-shadow: 0 0 22px var(--accent-hot);
}

.tournament-hero__champion-name--empty {
  color: var(--text-tertiary);
  text-shadow: none;
}

.tournament-hero__champion-meta {
  margin-top: var(--space-2xs);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
}

.tournament-hero__runner-up {
  color: var(--text-secondary);
  text-decoration: none;
  border-bottom: 1px dashed transparent;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.tournament-hero__runner-up:hover,
.tournament-hero__runner-up:focus-visible {
  color: var(--text-primary);
  border-bottom-color: var(--text-secondary);
}

.tournament-hero__finals-record {
  font-family: var(--font-data);
  color: var(--text-secondary);
  font-weight: 600;
}

.tournament-hero__field {
  text-align: right;
  line-height: var(--leading-tight);
}

.tournament-hero__field-number {
  display: block;
  font-family: var(--font-data);
  font-size: clamp(32px, 4.8vw, 44px);
  font-weight: 700;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-tight);
}

.tournament-hero__field-unit {
  display: block;
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-tertiary);
  margin-top: var(--space-2xs);
}

/* Narrow viewports: stack the two pedestals so neither gets crushed. */
@media (max-width: 560px) {
  .tournament-hero__pedestal {
    grid-template-columns: 1fr;
    gap: var(--space-lg);
    align-items: start;
  }
  .tournament-hero__field {
    text-align: left;
  }
}

/* Actual bracket: Quarters → Semis → Finals, left to right. Each round is
   its own flexbox column with justify-content: space-around so matches
   stay vertically centered relative to what feeds them. Horizontal
   connector lines extend from every non-final match into the next column. */

.tournament-bracket {
  display: flex;
  align-items: stretch;
  gap: 40px;
  margin-bottom: 32px;
  padding: 4px 12px 12px;
}

.bracket-column {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

.bracket-column__label {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-tertiary);
  text-align: center;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 16px;
}

.bracket-column__matches {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  gap: 8px;
}

.bracket-match {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

/* Horizontal connector extending rightward from every match in all
   columns except the last. Puts a tick-mark that reads as a bracket
   feed without requiring pairing metadata we don't have. */
.bracket-column:not(:last-child) .bracket-match::after {
  content: '';
  position: absolute;
  left: 100%;
  top: 50%;
  width: 40px;
  border-top: 1px solid var(--border);
}

/* Horizontal connector extending leftward into every match after the
   first column, meeting the outgoing line from the previous round. */
.bracket-column:not(:first-child) .bracket-match::before {
  content: '';
  position: absolute;
  right: 100%;
  top: 50%;
  width: 40px;
  border-top: 1px solid var(--border);
}

.bracket-match__player {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: var(--text-sm);
}

.bracket-match__player a {
  color: inherit;
}

.bracket-match__player--winner {
  background: var(--accent-subtle);
  color: var(--text-primary);
  font-weight: 600;
}

.bracket-match__score {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

.bracket-match__player--winner .bracket-match__score {
  color: var(--accent);
  font-weight: 700;
}

/* Bracket crescendo: Quarters are the widest, most muted column (many matches,
   less narrative weight); Semis sit between; Finals gets the heaviest label,
   a slightly tinted surface, and a deeper glow on the winner. The entire
   bracket reads left-to-right as a drumbeat toward the champion. */
.bracket-column--quarterfinals .bracket-column__label {
  color: var(--text-tertiary);
  opacity: 0.75;
}

.bracket-column--semifinals .bracket-column__label {
  color: var(--text-tertiary);
}

.bracket-column--finals .bracket-column__label {
  color: var(--accent);
  font-weight: 700;
  letter-spacing: 1px;
  border-bottom-color: var(--accent);
}

.bracket-column--finals .bracket-match {
  background: var(--card);
  border-color: var(--accent-strong);
}

.bracket-column--finals .bracket-match__player--winner {
  background: var(--accent-soft);
}

.bracket-column--finals .bracket-match__player--winner a {
  color: var(--text-primary);
  text-shadow: 0 0 10px var(--accent-strong);
}

.bracket-column--finals .bracket-match__player--winner .bracket-match__score {
  text-shadow: 0 0 8px var(--accent-strong);
}

/* Responsive fallback: stack columns on narrow viewports. Connectors hide
   since they only make sense horizontally. */
@media (max-width: 768px) {
  .tournament-bracket {
    flex-direction: column;
    gap: var(--space-lg);
    padding: var(--space-xs) 0 var(--space-sm);
  }
  .bracket-column:not(:last-child) .bracket-match::after,
  .bracket-column:not(:first-child) .bracket-match::before {
    display: none;
  }
}

/* Section-header caption — small mono label sitting next to the heading
   in the right slot. Used on /tournaments/:id under "Rating Shift" to
   state the sort dimension. Mirrors .section-header__count/__filtered in
   weight and tone without carrying their specific semantics. */
.section-header__caption {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-tertiary);
}

/* Tournament rating-shift table — reuses .rank-row and the table-header
   column classes. Scoped to .tournament-show so the 52px compact row (same
   as /tournaments index) doesn't leak to the leaderboard which wants 56px
   to give the big rating number presence. Also adds two scoped column
   widths: 'Before' (pre-tournament rating) and a variant RD cell that
   drops the confidence-pill styling. */
.tournament-show .shift-row,
.tournament-show .table-header {
  height: 52px;
  gap: var(--space-md);
}

.tournament-show .table-header {
  height: 40px;
}

.th-shift-before, .shift-before {
  width: 80px;
  text-align: right;
  font-family: var(--font-data);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
}

.shift-row .shift-rd {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  font-weight: 500;
}

/* Delta sparkbar — a 2px bar at the bottom edge of every rating-change
   cell, width scaled to that row's |change| as a fraction of the page's
   biggest swing (--magnitude from the view). Green for gains, red for
   drops. Data-density move, not decoration: at a glance, a player can
   see "how big is this row's swing relative to the biggest on the page"
   without reading every number.

   The cell itself stays tight — the bar sits inside its padding box at
   the absolute bottom so it doesn't push layout or clip the number.
   When --magnitude is 0 (or unset on non-show pages), width is 0 and
   the bar is invisible. */
.tournament-show .tourn-delta {
  position: relative;
  overflow: hidden;
}

.tournament-show .tourn-delta::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: 0;
  height: 2px;
  width: calc(var(--magnitude, 0) * 100%);
  border-radius: 1px;
  transition: width 0.3s ease-out;
  pointer-events: none;
}

.tournament-show .tourn-delta--up::after {
  background: linear-gradient(to left,
    rgba(34, 197, 94, 0.85),
    rgba(34, 197, 94, 0.15));
}

.tournament-show .tourn-delta--down::after {
  background: linear-gradient(to left,
    rgba(239, 68, 68, 0.85),
    rgba(239, 68, 68, 0.15));
}

/* Top Mover chip — awarded to the single player with the biggest |rating
   change| across the whole field (not the page). Lives inline next to the
   player name in the rating-shift table, small and tight. Accent glow is
   reserved — only one row on the page earns it. */
.top-mover-badge {
  display: inline-flex;
  align-items: center;
  margin-left: var(--space-xs);
  padding: 2px 8px;
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--accent);
  background: var(--accent-subtle);
  border: 1px solid var(--accent-mid);
  border-radius: var(--radius-full);
  text-shadow: 0 0 8px var(--accent-strong);
  line-height: var(--leading-normal);
  white-space: nowrap;
}

/* Reduced-motion: count-up controller already bails to the final value,
   but kill the sparkbar width-transition so it doesn't flash in from 0
   on navigation either. Both flourishes are data-decoration and MUST
   not impair the core read for users who've opted out of motion. */
@media (prefers-reduced-motion: reduce) {
  .tournament-show .tourn-delta::after {
    transition: none;
  }
}

/* Rating history chart — dark panel with a fixed height so the Chart.js
   canvas has somewhere to draw. No border beyond the container frame,
   the grid lines carry the structure. */
.rating-chart-wrap {
  height: 220px;
  padding: 12px 12px 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  margin-bottom: 24px;
}

.rating-chart { display: block; width: 100% !important; height: 100% !important; }

/* Head-to-head table — one column per data point (Rival | Win % | Record
   | Rating | Last played). Every column has its own header label. Row-level
   text stays at --text-xs so all data reads at the same weight; Win% keeps
   the favorable/nemesis color tint so vibe is still legible without a chip. */
.table-container--h2h .th-player,
.table-container--h2h .rank-player {
  flex: 1 1 0;
  min-width: 0;
  max-width: none;
}

.th-h2h-winrate,
.h2h-winrate,
.th-h2h-record,
.h2h-record,
.th-h2h-rating,
.h2h-rating,
.th-h2h-delta,
.h2h-delta,
.th-h2h-date,
.h2h-date {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.th-h2h-winrate, .h2h-winrate { width: 64px; }
.th-h2h-record,  .h2h-record  { width: 64px; }
.th-h2h-rating,  .h2h-rating  { width: 72px; }
.th-h2h-delta,   .h2h-delta   { width: 72px; }
/* Last played column anchors the right edge; slightly wider so "Apr 10, 2026"
   sits comfortably and the sort-header caret has room to the right of the
   label without truncating the column title on narrower desktop widths. */
.th-h2h-date,    .h2h-date    { width: 108px; margin-left: auto; }

.h2h-winrate,
.h2h-record,
.h2h-rating,
.h2h-delta,
.h2h-date {
  font-family: var(--font-data);
  font-size: var(--text-xs);
}

.h2h-winrate {
  font-weight: 600;
  color: var(--text-primary);
}
.h2h-row--favorable .h2h-winrate { color: var(--positive); }
.h2h-row--nemesis   .h2h-winrate { color: var(--negative); }

.h2h-record {
  color: var(--text-secondary);
  font-weight: 600;
}

.h2h-rating {
  color: var(--text-tertiary);
}
.h2h-rating__number {
  color: var(--text-secondary);
  font-weight: 600;
}
.h2h-rating__empty {
  color: var(--text-tertiary);
}

.h2h-delta {
  color: var(--text-tertiary);
}
.h2h-delta__none {
  color: var(--text-tertiary);
}

.h2h-date { color: var(--text-tertiary); }

/* Mobile — swap from flex to a two-row grid so Record/Rating/Date stack
   cleanly below the name + Win% headline row. Flex-wrap on a 5-column row
   was overflowing: the name shrunk to zero width and the Win% cell crashed
   into the name text. Grid gives each chunk an explicit cell. */
@media (max-width: 720px) {
  .table-container--h2h .th-h2h-record,
  .table-container--h2h .th-h2h-rating,
  .table-container--h2h .th-h2h-delta,
  .table-container--h2h .th-h2h-date { display: none; }

  .h2h-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    column-gap: 10px;
    row-gap: 6px;
    align-items: center;
  }
  .table-container--h2h .h2h-row > .tourn-row__chevron { grid-column: 1; grid-row: 1; }
  .table-container--h2h .h2h-row > .rank-player        { grid-column: 2 / 4; grid-row: 1; min-width: 0; }
  .table-container--h2h .h2h-row > .h2h-winrate        { grid-column: 4; grid-row: 1; width: auto; }

  .table-container--h2h .h2h-row > .h2h-record {
    grid-column: 1 / 3;
    grid-row: 2;
    width: auto;
    padding-left: 28px;
    text-align: left;
  }
  .table-container--h2h .h2h-row > .h2h-rating {
    grid-column: 3;
    grid-row: 2;
    width: auto;
  }
  .table-container--h2h .h2h-row > .h2h-delta {
    grid-column: 4;
    grid-row: 2;
    width: auto;
  }
  .table-container--h2h .h2h-row > .h2h-date {
    grid-column: 1 / 5;
    grid-row: 3;
    width: auto;
    margin-left: 0;
    padding-left: 28px;
    text-align: left;
  }

  /* Name link truncates instead of overflowing the grid cell. */
  .table-container--h2h .h2h-row__name-link {
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
  }
}

/* ─── H2H inline expansion (details panel under each rival row) ─── */

/* Panel shell — matches .tourn-expand so the H2H expansion and the
   Tournament History expansion read as the same surface grammar. */
.h2h-expand {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: none;
  border-bottom-left-radius: 6px;
  border-bottom-right-radius: 6px;
  overflow: hidden;
}

/* Hero row inside the expansion: horizontal split bar on the left
   (the rivalry at a glance), big win rate on the right (the headline
   number). Mirrors the hero density pattern from the profile. */
/* Expanded-row hero — vertical stack:
     1. Full-width color bar (W/D/L visual snapshot)
     2. Summary row: vibe chip + text legend on the left,
        Win Rate label inline-LEFT of the big percentage on the right
     3. Divider
     4. Full rivalry history link, right-aligned below the divider
   Replaces the old two-column "bar+legend on left / winrate on right"
   layout which split the visual and the number across the row. */
.h2h-expand__hero {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px 20px;
}

.h2h-hero__bar {
  display: flex;
  width: 100%;
  height: 10px;
  border-radius: 5px;
  overflow: hidden;
  background: rgba(148, 163, 184, 0.1);
}
.h2h-hero__bar > span { display: block; transition: width 200ms ease; }
.h2h-hero__bar .split--win  { background: var(--positive); }
.h2h-hero__bar .split--draw { background: var(--warning); opacity: 0.7; }
.h2h-hero__bar .split--loss { background: var(--negative); }

.h2h-hero__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  flex-wrap: wrap;
}
.h2h-hero__summary-left {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}
.h2h-hero__summary-left .h2h-vibe { margin-left: 0; }

.h2h-hero__summary-right {
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.h2h-hero__summary-right .audit-hero__label {
  font-size: var(--text-micro);
  letter-spacing: 1.2px;
  color: var(--text-tertiary);
  text-transform: uppercase;
}
.h2h-hero__summary-right .audit-hero__number {
  font-family: var(--font-data);
  font-size: var(--text-3xl);
  color: var(--accent);
  font-feature-settings: "tnum";
  line-height: 1;
}

.h2h-hero__actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding-top: 12px;
  border-top: 1px solid rgba(148, 163, 184, 0.1);
  flex-wrap: wrap;
}
/* Per-format breakdown on the left of the actions row — reuses the
   metric-strip atoms (label / value / note / sep) for consistent
   typography, but zeroes the strip's default outer spacing since it's
   living inside a new container now. */
.h2h-hero__formats {
  display: flex;
  align-items: baseline;
  gap: 12px 20px;
  flex-wrap: wrap;
}
/* Anchor the link to the right regardless of whether formats render —
   margin-left:auto on the link absorbs slack so it always lands at the
   row's end edge. When formats are present they pack left; when absent
   the link sits at flex-start via margin-auto snapping right. */
.h2h-hero__actions .h2h-expand__full-link { margin-left: auto; }

.h2h-vibe {
  display: inline-block;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 1.2px;
  padding: 2px 8px;
  margin-left: 10px;
  border-radius: 3px;
  border: 1px solid currentColor;
  text-transform: uppercase;
}
.h2h-vibe--favorable { color: var(--positive); }
.h2h-vibe--nemesis   { color: var(--negative); }
.h2h-vibe--even      { color: var(--text-tertiary); }

/* W/D/L legend — compact text form of the bar, sits next to the vibe
   chip on the summary row. Colored square before each value matches
   the bar segment palette for an at-a-glance visual link. */
.h2h-hero__split-legend {
  display: flex;
  gap: 14px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
  letter-spacing: 0.3px;
  text-transform: uppercase;
}
.h2h-hero__split-legend > span::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  margin-right: 6px;
  vertical-align: middle;
}
.h2h-hero__split-legend .legend--win::before  { background: var(--positive); }
.h2h-hero__split-legend .legend--draw::before { background: var(--warning); }
.h2h-hero__split-legend .legend--loss::before { background: var(--negative); }

/* H2H match table: overrides .tourn-matches grid (which is sized for the
   7-column audit view) with column priorities tuned for this page:
   tournament wide (it's the point), rating pills compact. Selector doubles
   up to beat .tourn-matches source-order specificity. */
.tourn-matches.h2h-match-table {
  display: grid;
  width: 100%;
  grid-template-columns:
    96px     /* Date */
    minmax(240px, 1fr)  /* Tournament — the scan anchor */
    56px     /* Round */
    72px     /* Format */
    86px     /* Player rating */
    86px     /* Opponent rating */
    72px;    /* Result */
  padding: 0 20px 16px;
}

.tourn-matches.h2h-match-table .tourn-matches__header > span {
  font-size: var(--text-micro);
  padding: 12px 0 10px;
}

/* Header "Date" aligns with row date values — rows carry 12px left
   padding for the outcome stripe, so the header's first cell has to
   match or the column labels look off. */
.tourn-matches.h2h-match-table .tourn-matches__header > span:first-child {
  padding-left: 15px;
}

/* Player name headers — mono + primary color so the two competitors
   read as column subjects, not dimmed labels. No truncation: when the
   name is longer than the column, it ellipsizes inline. */
.h2h-match-table__name-col {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-primary) !important;
  letter-spacing: 0.3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Per-row outcome accent — 3px left stripe on the first cell of each
   match row. display:contents parent means we can't box-shadow the
   whole row, so we stripe the first span and pad it. Color = outcome,
   matches site-wide grammar. */
.tourn-matches.h2h-match-table .h2h-match-row > span {
  padding: 12px 0;
  border-bottom: 1px solid rgba(148, 163, 184, 0.08);
  align-self: center;
}
.tourn-matches.h2h-match-table .h2h-match-row:last-child > span { border-bottom: none; }

.tourn-matches.h2h-match-table .h2h-match-row > span:first-child {
  padding-left: 12px;
  border-left: 3px solid transparent;
}
.h2h-match-row--win  > span:first-child { border-left-color: var(--positive) !important; }
.h2h-match-row--loss > span:first-child { border-left-color: var(--negative) !important; }
.h2h-match-row--draw > span:first-child { border-left-color: var(--warning)  !important; }

/* Tournament name cell — unconstrained line wrapping off by default,
   so long names truncate gracefully instead of exploding the row. */
.h2h-match-row__tourn {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.h2h-match-row__tourn a {
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  min-width: 0;
}
.h2h-match-row__tourn a:hover { color: var(--accent); }

/* Tier chip next to the tournament name — compact, data-dense
   classification without taking a separate column. */
.h2h-match-row__tier {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  font-family: var(--font-data);
  font-size: var(--text-nano);
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 2px;
}
.h2h-match-row__tier--planetary { color: var(--positive); border: 1px solid var(--positive); }
.h2h-match-row__tier--sector    { color: var(--warning);  border: 1px solid var(--warning);  }
.h2h-match-row__tier--regional  { color: var(--accent);   border: 1px solid var(--accent);   }
.h2h-match-row__tier--galactic  { color: var(--tier-galactic-hi); border: 1px solid var(--tier-galactic-hi); }

/* Date cell — mono, tertiary, single line, right-aligned so numbers
   line up visually across rows. */
.tourn-matches.h2h-match-table .h2h-match-row .h2h-match-row__date {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
  white-space: nowrap;
}

/* Rating pill cells — center the pill so the numbers align across
   the two player rating columns. */
.tourn-matches.h2h-match-table .h2h-match-row > span:nth-child(5),
.tourn-matches.h2h-match-table .h2h-match-row > span:nth-child(6) {
  display: flex;
  justify-content: center;
  align-items: center;
}

@media (max-width: 768px) {
  .tourn-matches.h2h-match-table {
    grid-template-columns: auto 1fr 72px;
    padding: 0 12px;
  }
  .tourn-matches.h2h-match-table .h2h-match-row > span:nth-child(n+3):nth-child(-n+6) {
    display: none;
  }
  .tourn-matches.h2h-match-table .tourn-matches__header > span:nth-child(n+3):nth-child(-n+6) {
    display: none;
  }
}

/* ─── Profile sticky header + section nav ─── */
.profile-sticky {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 10px 0;
  margin-bottom: 20px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  backdrop-filter: blur(6px);
}

/* Kill the breadcrumb's own bottom margin when it's inside the sticky row —
   the sticky row already handles vertical rhythm. */
.profile-sticky .breadcrumb { margin-bottom: 0; }

.profile-section-nav {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
}

.profile-section-nav__link {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-tertiary);
  padding: 5px 10px;
  border-radius: 5px;
  border: 1px solid transparent;
  text-decoration: none;
  transition: border-color 150ms ease, color 150ms ease;
}

.profile-section-nav__link:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* Anchored section targets get scroll-margin so clicks land below the
   sticky bar instead of having the heading tucked behind it. */
.section-anchor {
  scroll-margin-top: 72px;
}

/* Smooth scroll for in-page anchor jumps. Scoped to .motion-ok so the
   reduced-motion block further below can cleanly opt out without a
   scroll-behavior-specific override. */
html { scroll-behavior: smooth; }

/* Global reduced-motion guard. Individual per-component rules elsewhere in
   this file disable their own animations, but transitions scattered across
   the codebase (hovers, chart swaps, turbo-frame swaps, scroll behavior)
   were not all captured. A single kill-switch flattens everything for users
   who asked the OS to reduce motion. Specific durations are near-zero
   rather than 0 to keep transition-end events firing — a handful of
   Stimulus controllers listen for them. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  html { scroll-behavior: auto !important; }
}

/* Styled horizontal rule between major profile sections (Rating History,
   Tournament History, Head-to-Head). Subtle accent highlight in the middle
   so it reads as a data-terminal section break, not a default gray line. */
.profile-section-divider {
  height: 0;
  margin: 36px 0;
  border: 0;
  border-top: 1px solid var(--border);
  background: transparent;
  position: relative;
}

.profile-section-divider::after {
  content: "";
  position: absolute;
  top: -1px;
  left: 50%;
  transform: translateX(-50%);
  width: 72px;
  height: 1px;
  background: var(--accent);
  opacity: 0.6;
}

/* Rating history section — chart wrapper is a block so the canvas height
   behaves. Format chips moved into the unified .profile-format-tabs. */
.rating-history-block { display: block; }
.rating-history-block .section-header { align-items: center; }

/* Empty-state overlay sits dead-center over the canvas when there's no
   data for the selected format. Chart still draws empty axes underneath
   so the layout doesn't reflow on tab switches. */
.rating-chart-wrap { position: relative; }
.rating-chart-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-tertiary);
  pointer-events: none;
  background: linear-gradient(180deg, rgba(11, 17, 32, 0) 0%, rgba(11, 17, 32, 0.55) 50%, rgba(11, 17, 32, 0) 100%);
}

/* ============================================================
   PROFILE FORMAT TABS — single switch driving Rating History,
   Tournament History, and Head-to-Head together. Designed to
   read clearly as a navigation control, not subtle filter chips.
   ============================================================ */

.profile-format-tabs {
  display: flex;
  align-items: stretch;
  gap: 0;
  margin: 0 0 28px;
  padding: 0;
  border-bottom: 1px solid var(--border);
  position: relative;
  flex-wrap: wrap;
}

.profile-format-tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 12px 22px;
  margin-bottom: -1px; /* overlap the bottom border so active tab's underline replaces it */
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
  text-decoration: none;
  border: 1px solid transparent;
  border-bottom: 2px solid transparent;
  background: transparent;
  cursor: pointer;
  transition: color 150ms ease, border-color 150ms ease, background 150ms ease;
}

.profile-format-tab:hover {
  color: var(--text-primary);
  background: var(--accent-wash);
  border-bottom-color: var(--text-tertiary);
}

/* Active tab — accent color + accent underline carry the state signal
   on their own. Text-shadow + gradient background + heavy underline
   glow were all overstated per the impeccable critique: CLAUDE.md's
   glow-restraint rule reserves accent-glow for earned data points
   (hero rating, #1 rank, Galactic badge), not navigation chrome. */
.profile-format-tab--active {
  color: var(--accent);
  background: var(--accent-subtle);
  border-bottom-color: var(--accent);
}

.profile-format-tab--active::after {
  content: "";
  position: absolute;
  bottom: -2px;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--accent);
}

/* Right-side hint that explains the tabs drive every section below.
   Pushed to the right with margin-left:auto so it stays out of the
   way of the tab row but stays informative. */
.profile-format-tabs__hint {
  margin-left: auto;
  align-self: center;
  padding: 0 6px 8px;
  font-family: var(--font-data);
  font-size: var(--text-micro);
  color: var(--text-tertiary);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

@media (max-width: 768px) {
  .profile-format-tab { padding: 10px 14px; font-size: var(--text-xs); }
  .profile-format-tabs__hint { display: none; }
}

/* ============================================================
   PAGINATION NAV — custom CSS, no Tailwind.
   ============================================================ */

.pagy-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 20px 0 8px;
  padding: 12px 0 0;
  font-family: var(--font-data);
}

.pagy-nav__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 10px;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 5px;
  text-decoration: none;
  transition: border-color 150ms ease, color 150ms ease, background 150ms ease;
}

.pagy-nav__link:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.pagy-nav__link--current {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
  box-shadow: 0 0 10px var(--accent-glow);
}

.pagy-nav__link--arrow { padding: 0 12px; }

.pagy-nav__link--disabled,
.pagy-nav__link--disabled:hover {
  color: var(--text-tertiary);
  border-color: var(--border);
  background: transparent;
  cursor: not-allowed;
  box-shadow: none;
}

.pagy-nav__gap {
  color: var(--text-tertiary);
  padding: 0 4px;
}

/* "Showing X–Y of Z" sits centered on its own line below the page links.
   `flex-basis: 100%` forces a wrap so the hint never competes with the
   numbered controls for horizontal space. */
.pagy-nav__hint {
  flex-basis: 100%;
  text-align: center;
  margin-top: 8px;
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
  letter-spacing: 0.04em;
}

/* ============================================================
   H2H EMPTY STATE — terminal "no signal" blank slate.
   Direct copy, no emojis, sci-fi data terminal vibe.
   ============================================================ */

.h2h-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 36px 24px 40px;
  background: var(--surface);
  border: 1px dashed var(--border);
  border-radius: 8px;
  text-align: center;
}

.h2h-empty__reticle {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-data);
  color: var(--text-tertiary);
  font-size: var(--text-sm);
  letter-spacing: 0.4em;
  text-transform: uppercase;
}

.h2h-empty__bracket {
  color: var(--accent);
  font-size: var(--text-lg);
  text-shadow: 0 0 6px var(--accent-glow);
}

.h2h-empty__core {
  position: relative;
}

.h2h-empty__core::after {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-left: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px var(--accent-glow);
  vertical-align: middle;
  animation: h2h-pulse 1.8s ease-in-out infinite;
}

@keyframes h2h-pulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 1; }
}

.h2h-empty__copy {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 480px;
}

.h2h-empty__headline {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text-primary);
}

.h2h-empty__sub {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  line-height: var(--leading-relaxed);
}

/* Profile hero: credible-interval ± width. Sits inline inside the caption
   row alongside the format label and confidence % — flex gap in the parent
   handles spacing, so no local margin needed. 12px matches .profile-hero__conf
   so neither signal dominates; confidence % is the more interpretable of
   the two and leads the caption via document order. */
.profile-hero__ci {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  font-weight: 400;
}

/* Tournament history: expandable per-match audit trail */
.tourn-row-wrapper { display: block; }

.tourn-row { cursor: pointer; }
.tourn-row[aria-expanded="true"] { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }

/* Affordance chevron — sits to the left of every row, points right when
   collapsed and rotates to point down when the <details> is open. The
   native disclosure marker is suppressed so this is the only indicator. */
.tourn-row { list-style: none; }
.tourn-row::-webkit-details-marker { display: none; }

.tourn-row__chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  flex-shrink: 0;
  margin-right: 8px;
  color: var(--text-tertiary);
  transition: transform 150ms ease, color 150ms ease;
}

.tourn-row:hover .tourn-row__chevron { color: var(--accent); }

details[open] > .tourn-row > .tourn-row__chevron,
details[open] > .h2h-row > .tourn-row__chevron {
  transform: rotate(90deg);
  color: var(--accent);
}

/* H2H rows reuse the tourn-row disclosure pattern so the chevron and
   expansion behave identically. The marker is suppressed, the summary
   takes the flex row layout from .rank-row, and the bottom radius
   opens up when the panel expands so the two read as one unit. */
.h2h-row { cursor: pointer; list-style: none; }
.h2h-row::-webkit-details-marker { display: none; }
.h2h-row:hover .tourn-row__chevron { color: var(--accent); }
details[open] > .h2h-row { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }

/* Header alignment column for the chevron — same width + margin as the
   chevron column so the Event header lines up with the event names. */
.th-chevron {
  width: 20px;
  flex-shrink: 0;
  margin-right: 8px;
}

/* Expansion panel — wraps the explainer + metric strip + matches grid so
   the audit info lives on the profile directly. Shares surface + border
   with the summary row so the two read as a single expanded unit. Flex
   column with a uniform gap between children controls vertical rhythm.
   All children sit at 20px inset matching the summary row's padding so
   columns and content align vertically from summary through expand. */
.tourn-expand {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-sm) 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: none;
  border-bottom-left-radius: 6px;
  border-bottom-right-radius: 6px;
  overflow: hidden;
}

/* Navigation row at the top of an expanded tournament card. These links
   used to live inside the <summary> as <a> tags, which triggered Chrome's
   "interactive element inside <summary>" warning (the summary itself is
   the toggle — nested links make click intent ambiguous). Moving them
   into the expand container keeps both destinations available without
   breaking the details/summary contract. */
.tourn-expand__links {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  font-size: var(--text-xs);
  font-family: var(--font-data);
  letter-spacing: 0.2px;
  text-transform: uppercase;
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid var(--border);
}

.tourn-expand__link-primary {
  color: var(--accent);
}

.tourn-expand__link-external {
  color: var(--text-tertiary);
  text-decoration: none;
  transition: color 150ms ease;
}

.tourn-expand__link-external:hover,
.tourn-expand__link-external:focus-visible {
  color: var(--accent);
}

.tourn-expand__meta {
  padding: 0;
}

/* Deep-link at the bottom of an expanded tournament row to the per-event
   audit page. Small, right-aligned, arrow cues "keep going" without
   fighting the section-header hierarchy above. */
.tourn-expand__cta {
  margin: 0;
  text-align: right;
  font-size: var(--text-xs);
  font-family: var(--font-data);
}

.tourn-expand__cta-link {
  color: var(--accent);
  letter-spacing: 0.2px;
  text-transform: uppercase;
}

/* Strip chrome off the matches grid and break it out to the panel's
   full width so the separator line runs edge-to-edge. The grid's own
   internal padding re-creates the 20px inset for its cells. */
.tourn-expand .tourn-matches {
  background: transparent;
  border: none;
  border-top: 1px solid var(--border);
  border-radius: 0;
  margin: 0 -20px;
}

/* Inline metric-strip modifier: kill the section-level bottom margin
   and tighten gaps since we're already inside a padded panel. */
.metric-strip--inline {
  margin: 0;
  padding: 0;
  gap: var(--space-xs) var(--space-xl);
}

/* After an open details row, give the next sibling a beat of space so
   the expand panel doesn't visually merge into the following row. The
   base table-container gap of 2px is right for collapsed rows but too
   tight when an expand panel sits above the next row. */
.tourn-row-wrapper[open],
.h2h-row-wrapper[open] {
  margin-bottom: var(--space-sm);
}

.tourn-matches {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: none;
  border-bottom-left-radius: 6px;
  border-bottom-right-radius: 6px;
  padding: var(--space-sm) 20px;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-secondary);
  display: grid;
  /* Columns: Round | HRI | Opponent | Result | Game W-L | Multiplier | Δ | Type
     The Δ column (v5.3) sits between Multiplier and Type so players can see the
     per-match rating change approximation right next to the multiplier that
     produced it. See PlayersHelper#per_match_rating_delta for the caveat. */
  grid-template-columns: 50px 64px 1fr 70px 60px 60px 60px 70px;
  gap: 6px var(--space-sm);
  align-items: center;
}

.tourn-matches__header {
  display: contents;
  font-size: var(--text-micro);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.4px;
}

.tourn-matches__header > span { padding-bottom: 6px; border-bottom: 1px solid var(--border); }

.tourn-match { display: contents; }
.tourn-match > span { padding: 4px 0; }

.tourn-match__result--win { color: var(--positive); }
.tourn-match__result--loss { color: var(--negative); }
.tourn-match__result--draw { color: var(--warning); }

.tourn-match__mult {
  color: var(--text-tertiary);
  font-size: var(--text-2xs);
}

/* Per-match Δ pill (v5.3). Approximate per-match rating change; column
   header carries the tooltip that names the approximation. Color follows
   positive/negative/neutral semantics the rest of the profile uses for
   rating movement so the eye reads the direction immediately. */
.tourn-match__delta-cell { text-align: right; }

.tourn-match__delta {
  display: inline-block;
  min-width: 36px;
  padding: 1px 6px;
  border-radius: 4px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-feature-settings: "tnum";
  text-align: right;
}

.tourn-match__delta--positive { color: var(--positive); background: color-mix(in oklch, var(--positive) 10%, transparent); }
.tourn-match__delta--negative { color: var(--negative); background: color-mix(in oklch, var(--negative) 10%, transparent); }
.tourn-match__delta--neutral  { color: var(--text-tertiary); }
.tourn-match__delta--none     { color: var(--text-tertiary); }

.tourn-match__opponent a {
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--text-xs);
}

.tourn-match__hri {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Column alignment — shared by profile inline expansion and tournament breakdown.
   Columns: 1 Round (left) | 2 HRI (center) | 3 Opponent (left) |
            4 Result (center) | 5 Game W-L (right) | 6 Multiplier (right) | 7 Type (right) */
.tourn-matches__header > span:nth-child(2),
.tourn-matches__header > span:nth-child(4) {
  text-align: center;
}

.tourn-matches__header > span:nth-child(n+5) {
  text-align: right;
}

.tourn-match > span:nth-child(4) {
  text-align: center;
}

.tourn-match > span:nth-child(n+5) {
  text-align: right;
}

.tourn-match__hri-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-feature-settings: "tnum";
  color: var(--text-secondary);
  background: rgba(148, 163, 184, 0.06);
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: help;
  transition: border-color 150ms ease, color 150ms ease;
  white-space: nowrap;
}

.tourn-match__hri-pill:hover {
  color: var(--text-primary);
  border-color: var(--text-tertiary);
}

.tourn-match__hri--none {
  color: var(--text-tertiary);
  font-family: var(--font-data);
  font-size: var(--text-2xs);
}

/* ============================================================
   RATING MATH PANEL — the framed "How this rating change was
   computed" section. Distinct from the Matches table below it
   so the two don't visually collapse into one long table. The
   left accent rail marks this as the insight zone; the mono
   kicker and tabular grid carry the data-terminal voice.
   ============================================================ */

.rating-math-panel {
  margin: 32px 0 28px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}

.rating-math-panel__kicker {
  margin: 0 0 2px;
  font-family: var(--font-data);
  font-size: var(--text-micro);
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--accent-hover);
  font-feature-settings: "tnum";
}

.rating-math-panel__heading {
  margin: 0 0 12px;
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: -0.01em;
}

.rating-math-panel__lede {
  margin: 0 0 18px;
  max-width: 72ch;
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--text-secondary);
}

.rating-math-panel__lede strong { color: var(--text-primary); }
.rating-math-panel__lede em { color: var(--text-primary); font-style: italic; }

/* Approximate phase rows: the engine uses moment-of-match opponent ratings;
   per-phase numbers here use pre-tournament ratings. The ≈ glyph (in ERB)
   plus a slightly muted delta color echoes the caveat paragraph below the
   table so the distinction registers without reading the footnote. */
.tourn-match--approx .rating-change {
  color: color-mix(in oklch, currentColor 75%, var(--text-tertiary));
}

.rating-math-panel__inline-data {
  display: inline-block;
  padding: 0 6px;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-feature-settings: "tnum";
  color: var(--accent-hover);
  background: var(--accent-subtle);
  border-radius: 4px;
}

/* v5.3 size-band callout. Sits between the heading and the lede, amber
   annotation style so it reads as "context for what you're about to see"
   rather than competing with the data below. Only shown for Planetary
   events that actually took a size-band reduction (Micro or Small). */
.rating-math-panel__size-band-alert {
  margin: 0 0 18px;
  max-width: 72ch;
}

/* "Full rivalry history →" link inside the H2H expansion on the profile.
   Sits below the win-rate number, accent-colored to read as the call-to-
   action it is, but at small text size so it doesn't compete with the
   data above it. */
.h2h-expand__full-link {
  display: block;
  margin-top: 8px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--accent);
  text-decoration: none;
}

.h2h-expand__full-link:hover { text-decoration: underline; }

/* Opponent name in a collapsed H2H row, made into a link to the
   /players/A/vs/B drill-in view. Inherits .player-name typography +
   color so the row looks visually unchanged at rest, but takes the
   accent on hover and underlines on focus to signal interactivity.
   Sits inside <summary>; browsers route the click to the anchor href
   instead of toggling the <details>, so clicking the name navigates
   without expanding the row. */
.h2h-row__name-link {
  text-decoration: none;
  color: inherit;
  border-bottom: 1px dashed transparent;
  transition: color 120ms ease, border-color 120ms ease;
}

.h2h-row__name-link:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.h2h-row__name-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ─── Player vs. Player drill-in (/players/:slug/vs/:opponent_slug) ─── */

/* HERO STRIP — the rivalry's identity in one look. Three-column matchup
   row (left player | center vibe + win-rate | right player), then a
   full-width split bar, then a meta row with count + flip-perspective. */
.player-vs .vs-hero {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-lg) 0;
  margin-bottom: var(--space-lg);
  /* Internal gap sits at space-sm (12px) so the 5 stacked children
     (matchup / split bar / readout / story chips / meta row) pack as a
     compact factbox rather than a widely-spaced list. No border-bottom:
     the next section down (.vs-card) renders its own border-top, so
     doubling them made a visible pair of stacked lines. */
}

.vs-hero__matchup {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: baseline;
  gap: var(--space-md);
}
/* Narrow viewport — stack the two player blocks vertically so names +
   ratings don't crush into each other. Right player also switches to
   left-aligned so the stack reads as a consistent ragged-left column. */
@media (max-width: 600px) {
  .vs-hero__matchup {
    grid-template-columns: 1fr;
    gap: var(--space-sm);
  }
  .vs-hero__player--right {
    align-items: flex-start;
    text-align: left;
  }
}

.vs-hero__player {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.vs-hero__player--left  { align-items: flex-start; text-align: left;  }
.vs-hero__player--right { align-items: flex-end;   text-align: right; }

.vs-hero__player-name {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--text-primary);
  text-decoration: none;
  letter-spacing: -0.01em;
}

.vs-hero__player-name:hover { color: var(--accent); }

.vs-hero__player-rating {
  font-family: var(--font-data);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--accent);
  font-feature-settings: "tnum";
}

.vs-hero__player-rd {
  margin-left: 6px;
  font-size: var(--text-2xs);
  font-weight: 400;
  color: var(--text-tertiary);
}

/* Vibe badge — FAVORABLE / NEMESIS / EVEN. Lives in the readout row
   under the split bar, paired with the win-rate number. */
.vs-hero__vibe {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.14em;
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid currentColor;
  text-transform: uppercase;
}

.vs-hero__vibe--favorable {
  color: var(--positive);
  background: color-mix(in srgb, var(--positive) 10%, transparent);
}
.vs-hero__vibe--nemesis {
  color: var(--negative);
  background: color-mix(in srgb, var(--negative) 10%, transparent);
}
.vs-hero__vibe--even {
  color: var(--text-tertiary);
}

/* Readout row — vibe chip + compact win-rate number + label, one line
   sitting directly under the split bar. Replaces the old centered
   giant hero-metric (banned template per the impeccable critique).
   The win-rate number here is still the emotional payoff of the page,
   but it sits in a flat row with breathing room instead of a dashboard
   centerpiece. */
.vs-hero__readout {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  flex-wrap: wrap;
}
.vs-hero__readout-value {
  font-family: var(--font-data);
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--accent);
  font-feature-settings: "tnum";
  letter-spacing: -0.01em;
  line-height: 1;
}
.vs-hero__readout-label {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/* Story chips — timeline factbox under the readout. Compact inline
   strip of "Streak · First met · Last met" entries. Each chip is:
   caps label, mono value, linked tournament detail. Replaces the
   standalone Story-so-far card (deleted on 2026-04-23). Only renders
   when there's rivalry history and at least one timeline fact. */
.vs-hero__story {
  display: flex;
  align-items: baseline;
  gap: var(--space-lg);
  flex-wrap: wrap;
  padding: var(--space-xs) 0 0 0;
  border-top: 1px solid color-mix(in oklch, var(--border) 50%, transparent);
}
.vs-hero__story-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  font-size: var(--text-xs);
}
.vs-hero__story-label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.vs-hero__story-value {
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--accent);
  font-feature-settings: "tnum";
}
.vs-hero__story-detail {
  color: var(--text-secondary);
  text-decoration: none;
}
a.vs-hero__story-detail:hover {
  color: var(--accent);
  text-decoration: underline;
}

/* The full-width split bar — taller and more prominent than the slim
   version on the profile expansion. Shows W/D/L counts overlaid when
   each segment is wide enough to fit them legibly. */
.vs-hero__split-row { padding: 0; }

.vs-hero__split {
  display: flex;
  width: 100%;
  height: 36px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--border);
  font-family: var(--font-data);
  font-feature-settings: "tnum";
}

.vs-hero__split-segment {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  font-size: var(--text-xs);
  font-weight: 700;
  color: white;
  letter-spacing: 0.04em;
  transition: width 200ms ease;
}

.vs-hero__split-segment--win  { background: var(--positive); }
.vs-hero__split-segment--draw { background: var(--text-tertiary); }
.vs-hero__split-segment--loss { background: var(--negative); }

.vs-hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
  align-items: baseline;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

.vs-hero__sep { opacity: 0.5; }
.vs-hero__count       { font-feature-settings: "tnum"; color: var(--text-secondary); }
.vs-hero__date-range  { font-feature-settings: "tnum"; }

/* Per-format mini-record inline in the hero meta line. Replaces the
   old standalone "By format" section — multi-format rivalries are just
   a small amount of information that belongs in the hero's meta row
   where it can be scanned with the match count and date range. */
.vs-hero__format-line {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
}
.vs-hero__format-label {
  color: var(--text-tertiary);
  letter-spacing: 0.04em;
}
.vs-hero__format-record {
  font-feature-settings: "tnum";
  color: var(--text-secondary);
  font-weight: 600;
}

.vs-hero__flip {
  color: var(--accent);
  text-decoration: none;
  font-weight: 500;
}

.vs-hero__flip:hover { text-decoration: underline; }

/* INSIGHT GRID — container for the Forecast card (currently the only
   inhabitant; Story-so-far card was removed 2026-04-23 with its facts
   redistributed to the hero chip strip + Scouting tab). Grid structure
   retained as a stable wrapper so future insight cards slot in without
   refactoring the template. Auto-fit columns keep the lone card
   full-width on wide viewports. */
.vs-insight-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}

/* Flattened "card" — no bg, no border. These two panels are read-only
   data, not interactive surfaces, so dashboard-card chrome would be
   decoration. The kicker (micro uppercase mono label) carries the
   structural role of "this is a distinct section" without the border
   box. Padding + the top accent rule give the panel spatial identity
   alongside its sibling in the two-up grid. */
.vs-card {
  background: transparent;
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 0;
  padding: var(--space-md) 0 0;
}

.vs-card__head { margin-bottom: var(--space-sm); }

/* .vs-card__kicker removed on 2026-04-23 — the decorative `// ...`
   terminal kicker strings (e.g. "// glicko-2 forecast · format
   breakdown") were stripped per the impeccable critique. The <h2>
   heading carries the framing on its own. */

.vs-card__heading {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
  letter-spacing: -0.01em;
}

/* PREDICTION CARD — Glicko expected score for the next meeting. The
   horizontal bar is the dominant visual; the percentages flank it and
   the player names sit above so the reader gets a quick "X favorite at
   Y%" without parsing labels. */
.vs-prediction__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Player-name anchor row at the top of the prediction card. The two
   names are the static reference frame; per-format rows below reuse
   the left/right columns without repeating the username. */
.vs-prediction__names {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-sm);
}

.vs-prediction__name {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
}

.vs-prediction__name--opp { text-align: right; }

/* Format-row list. Each row is its own grid with two visual lines:
   1. format label · player rating · pcts · opp rating
   2. bar (spans full row)
   Empty rows (no shared rating) collapse the data tracks and just
   show the label + a "no rating" note. */
.vs-prediction__formats {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Format row — single-line layout after the 2026-04-23 condensation:
   [format label] [player rating] [pct / pct] [opp rating] [bar]. Each
   row reads as a compact stat belt; the full-width bar sits at the end
   so the proportional view sits at the same eye line as the numbers. */
.vs-prediction__format-row {
  display: grid;
  grid-template-columns: 110px auto auto auto 1fr;
  column-gap: var(--space-md);
  align-items: center;
  padding: var(--space-xs) 0;
  border-top: 1px solid var(--border);
}

.vs-prediction__format-row:first-child { border-top: none; padding-top: 0; }

.vs-prediction__format-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.vs-prediction__format-name {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-secondary);
}

.vs-prediction__format-context {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  color: var(--text-tertiary);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.vs-prediction__rating {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-secondary);
  font-feature-settings: "tnum";
  white-space: nowrap;
}

.vs-prediction__rating--player { text-align: left; }
.vs-prediction__rating--opp    { text-align: right; }

.vs-prediction__rd {
  margin-left: 4px;
  font-size: var(--text-micro);
  color: var(--text-tertiary);
}

.vs-prediction__pcts {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  white-space: nowrap;
}

.vs-prediction__pct {
  display: inline-flex;
  align-items: baseline;
}

.vs-prediction__pct-num {
  font-family: var(--font-data);
  font-size: var(--text-md);
  font-weight: 700;
  line-height: 1;
  font-feature-settings: "tnum";
  letter-spacing: var(--tracking-tight);
}

.vs-prediction__pct--player .vs-prediction__pct-num { color: var(--accent); }
.vs-prediction__pct--opp    .vs-prediction__pct-num { color: var(--text-secondary); }

.vs-prediction__bar {
  display: flex;
  width: 100%;
  height: 6px;
  min-width: 80px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--border);
}

.vs-prediction__bar-segment--player { background: var(--accent); }
.vs-prediction__bar-segment--opp    { background: var(--text-tertiary); }

/* Empty format row (one or both players don't have a rating in this
   format). Subdued so the populated rows above/below carry the eye. */
.vs-prediction__format-row--empty {
  grid-template-columns: 130px 1fr;
  align-items: center;
}

.vs-prediction__missing {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  font-style: italic;
}

.vs-prediction__caveat {
  margin: var(--space-xs) 0 0;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  line-height: var(--leading-relaxed);
  color: var(--text-tertiary);
}

/* Analysis callout — dedicated readout panel beneath the format table.
   Tinted surface + accent-tinted border signals "this is the takeaway,
   not another data row." The accent label on the left carries a short
   accent underline to earn its visual weight without pulling the brand's
   reserved-glow budget (per CLAUDE.md, glow belongs to ratings/ranks/
   tier badges, not section labels). Subtle corner-bracket at the top-
   left gives the block its data-terminal readout flavor. */
.vs-prediction__analysis {
  display: flex;
  gap: var(--space-md);
  margin-top: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  background: color-mix(in oklch, var(--accent) 4%, transparent);
  border: 1px solid color-mix(in oklch, var(--accent) 22%, var(--border));
  border-radius: var(--radius-md);
  position: relative;
  overflow: hidden;
}
/* Terminal-style corner bracket — a small L-shape in the top-left
   corner of the panel, rendered with a pseudo-element so there's no
   extra markup. Stops short of being a full side-stripe border (the
   banned pattern) because it's a corner accent, not a full edge. */
.vs-prediction__analysis::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 14px;
  height: 14px;
  border-top: 2px solid var(--accent);
  border-left: 2px solid var(--accent);
  border-top-left-radius: var(--radius-md);
  pointer-events: none;
}
.vs-prediction__analysis-label {
  flex-shrink: 0;
  width: 72px;
  padding-top: 2px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  position: relative;
}
/* 2px accent underline under the "Analysis" label — a small earned
   signal that mirrors the brand's accent-mark-as-emphasis pattern
   (active tab underline, etc.). Width of 24px so it reads as a mark
   beneath the label, not a full underline. */
.vs-prediction__analysis-label::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -4px;
  width: 24px;
  height: 2px;
  background: var(--accent);
  border-radius: 1px;
}
.vs-prediction__analysis-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.vs-prediction__analysis-lead {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-md);
  line-height: 1.45;
  color: var(--text-primary);
  font-weight: 400;
}
.vs-prediction__analysis-lead strong {
  color: var(--text-primary);
  font-weight: 700;
}
.vs-prediction__analysis-detail {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text-secondary);
}
.vs-prediction__analysis-detail strong {
  color: var(--text-primary);
  font-weight: 600;
}
/* Contrast variant — "History contradicts the math" sentence. Gets a
   subtle accent tint on the leading strong so the insight reads as
   the sharpest piece of analysis on the card. */
.vs-prediction__analysis-detail--contrast strong:first-child {
  color: var(--accent);
}

@media (max-width: 640px) {
  /* Narrow viewport — fold the label above the body instead of beside. */
  .vs-prediction__analysis { flex-direction: column; gap: 6px; }
  .vs-prediction__analysis-label { width: auto; }
}

/* .vs-prediction__quote* rules removed on 2026-04-23 — the Yoda
   attribution block was stripped per CLAUDE.md's anti-reference rule
   ("NOT Star Wars IP pastiche"). The caveat paragraph above does all
   the forecast-framing work on its own. */

/* .vs-story* rules removed on 2026-04-23 — the standalone Story-so-far
   card was deleted per the impeccable critique. Timeline facts (streak /
   first / last) moved into the hero story chip strip; statistical
   outliers (closest / blowout / upset) moved into the Scouting tab.
   See .vs-hero__story-* and .vs-outliers* below. */

/* Memorable matches list inside the Scouting tab — closest / blowout /
   upset entries. Each row is: label tag, mono value, descriptive link.
   Same shape as the old vs-story entries but sized down a hair so the
   outliers don't hog the Scouting tab's vertical rhythm. */
.vs-outliers {
  margin-top: var(--space-md);
}
.vs-outliers__list {
  list-style: none;
  margin: var(--space-sm) 0 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-xs);
}
.vs-outliers__entry {
  display: grid;
  grid-template-columns: 120px auto 1fr;
  align-items: baseline;
  gap: var(--space-sm);
  padding: var(--space-xs) 0;
  border-bottom: 1px dashed color-mix(in oklch, var(--border) 70%, transparent);
}
.vs-outliers__entry:last-child { border-bottom: none; }
.vs-outliers__label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.vs-outliers__value {
  font-family: var(--font-data);
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--accent);
  font-feature-settings: "tnum";
}
.vs-outliers__detail {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  text-decoration: none;
}
a.vs-outliers__detail:hover {
  color: var(--accent);
  text-decoration: underline;
}

/* .vs-per-format* rules removed on 2026-04-23. The multi-format
   breakdown now renders inline in the vs-hero meta line — see
   .vs-hero__format-* above. */

/* vs-tabs — rivalry deep-dive tab bar + panels. Shows Trajectory,
   Scouting, Pace, and Matches each in their own panel; only one is
   visible at a time. Tab state is URL-hash-addressable so any view is
   shareable. Visual grammar mirrors .profile-format-tabs so the two
   tabbed controls on the profile / vs pages read as the same surface. */
.vs-tabs {
  margin-top: var(--space-xl);
}
.vs-tabs__nav {
  display: flex;
  align-items: stretch;
  gap: 0;
  margin: 0 0 var(--space-lg);
  padding: 0;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.vs-tabs__tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 22px;
  margin-bottom: -1px;
  font-family: var(--font-data);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  transition: color 150ms ease, border-color 150ms ease, background 150ms ease;
}
.vs-tabs__tab:hover {
  color: var(--text-primary);
  background: var(--accent-wash);
  border-bottom-color: var(--text-tertiary);
}
.vs-tabs__tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
/* Active tab — quiet version matching .profile-format-tab--active.
   No text-shadow, no heavy underline glow, no gradient background.
   Color + underline are enough to communicate state; glow is reserved
   for earned data points per CLAUDE.md's glow-restraint rule. */
.vs-tabs__tab--active {
  color: var(--accent);
  background: var(--accent-subtle);
  border-bottom-color: var(--accent);
}
.vs-tabs__tab--active::after {
  content: "";
  position: absolute;
  bottom: -2px;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--accent);
}
/* Count chip on the Matches tab — reminds the user how many matches
   are in the panel without forcing them to open it. */
.vs-tabs__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  padding: 1px 6px;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0;
  font-feature-settings: "tnum";
  color: var(--text-tertiary);
  background: color-mix(in oklch, var(--border) 50%, transparent);
  border-radius: var(--radius-full);
  text-transform: none;
}
.vs-tabs__tab--active .vs-tabs__count {
  color: var(--accent);
  background: color-mix(in oklch, var(--accent) 15%, transparent);
}

/* Panels — hidden by default via the `hidden` HTML attribute and the
   !important fallback below; the controller toggles `hidden` + adds
   `--active` on the visible one. Works with JS disabled because the
   server-rendered markup marks Trajectory as active + leaves the rest
   with `hidden`, so there's no FOUC and no empty page. */
.vs-tabs__panel[hidden] { display: none !important; }
/* No margin-top — the nav's own margin-bottom (var(--space-lg) = 24px)
   is already the separator between nav labels and panel content. Adding
   a panel margin stacked 40px of dead space between the two. */
.vs-tabs__panel { margin-top: 0; }
/* Reset outer margins on sub-sections when they live inside a tab
   panel — the panel's own spacing handles the rhythm, and stacked
   sub-section top-margins would double up. */
.vs-tabs__panel > section { margin-top: 0; }
.vs-tabs__panel > section + section { margin-top: var(--space-xl); }

/* Event cadence calendar — collapsible heatmap of tournament attendance
   organized by year × month × tier. Mono, grid-based, dots colored per
   tier. No chrome; reads as dense data. */
.event-calendar {
  margin: var(--space-lg) 0;
}
.event-calendar__summary {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.event-calendar__summary::-webkit-details-marker { display: none; }
.event-calendar__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  margin: 0;
}
.event-calendar__hint {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}
.event-calendar[open] .event-calendar__hint { display: none; }
.event-calendar__body {
  margin-top: var(--space-md);
  padding: var(--space-md);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.event-calendar__header,
.event-calendar__row {
  display: grid;
  grid-template-columns: 56px repeat(12, 1fr);
  gap: 2px;
  align-items: center;
}
.event-calendar__header { margin-bottom: 4px; }
.event-calendar__year-cell,
.event-calendar__month-label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
  letter-spacing: 0.08em;
  font-weight: 600;
  text-align: center;
}
.event-calendar__year-cell { text-align: left; padding-right: 8px; }
.event-calendar__cell {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  min-height: 22px;
  align-items: center;
  justify-content: center;
  padding: 3px 0;
  border-bottom: 1px solid color-mix(in oklch, var(--border) 40%, transparent);
}
.event-calendar__dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--text-tertiary);
  text-decoration: none;
  transition: transform 120ms ease, box-shadow 120ms ease;
}
.event-calendar__dot:hover { transform: scale(1.3); }
.event-calendar__dot--galactic       { background: var(--tier-galactic); box-shadow: 0 0 4px var(--tier-galactic); }
.event-calendar__dot--regional       { background: var(--tier-regional); }
.event-calendar__dot--sector         { background: var(--tier-sector); }
.event-calendar__dot--planetary      { background: var(--tier-planetary); }
.event-calendar__overflow {
  font-family: var(--font-data);
  font-size: 9px;
  color: var(--text-tertiary);
}
.event-calendar__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-top: var(--space-md);
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  letter-spacing: 0.06em;
  color: var(--text-tertiary);
}
.event-calendar__legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* TOP 10 featured row — lives inside the vs-field table but calls itself
   out as the named-cohort headline (live top-10 leaderboard) rather than
   a rating-delta bucket. Accent-tinted cells, accent label text, and the
   label's always-present ::before dot-slot flips on to accent + glow. */
.vs-field__row--featured td {
  background: color-mix(in oklch, var(--accent) 4%, transparent);
  border-bottom-color: color-mix(in oklch, var(--accent) 20%, var(--border));
  transition: background 200ms ease;
}
.vs-field__row--featured:hover td {
  background: color-mix(in oklch, var(--accent) 8%, transparent);
}
.vs-field__row--featured .vs-field__label {
  color: var(--accent);
  font-weight: 700;
}
.vs-field__row--featured .vs-field__label::before {
  background: var(--accent);
  box-shadow: 0 0 6px color-mix(in oklch, var(--accent) 70%, transparent);
}
.vs-field__row--featured .vs-field__hint {
  color: color-mix(in oklch, var(--accent) 60%, var(--text-tertiary));
}

/* ============================================================
   Player Dossier — collapsible section that folds auto-earned
   badges, matchup-strength buckets, closest calls, and event
   cadence behind a single file-drawer toggle. Keeps the hero
   focused on the rating while preserving the secondary data for
   the curious. See _player_dossier.html.erb + dossier_controller.js.
   ============================================================ */
.player-dossier {
  margin: var(--space-lg) 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: color-mix(in oklch, var(--surface) 70%, transparent);
  overflow: hidden;
  transition: border-color 200ms ease, background 200ms ease;
}
.player-dossier.is-open {
  border-color: color-mix(in oklch, var(--accent) 30%, var(--border));
  background: var(--surface);
}

.player-dossier__toggle {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--space-md);
  width: 100%;
  padding: var(--space-md) var(--space-lg);
  background: transparent;
  border: none;
  color: inherit;
  text-align: left;
  cursor: pointer;
  font: inherit;
  transition: background 150ms ease;
}
.player-dossier__toggle:hover,
.player-dossier__toggle:focus-visible {
  background: color-mix(in oklch, var(--accent) 5%, transparent);
  outline: none;
}
.player-dossier__toggle:focus-visible {
  box-shadow: inset 0 0 0 2px color-mix(in oklch, var(--accent) 50%, transparent);
}

.player-dossier__heading {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.player-dossier__title {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  margin: 0;
  color: var(--text-primary);
  letter-spacing: 0.02em;
}
.player-dossier__subtitle {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

.player-dossier__cta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-secondary);
  transition: color 150ms ease;
}
.player-dossier__toggle:hover .player-dossier__cta,
.player-dossier__toggle:focus-visible .player-dossier__cta {
  color: var(--accent);
}
.player-dossier__cta-text { display: inline-block; min-width: 60px; text-align: right; }
.player-dossier__cta-open { display: none; }
.player-dossier.is-open .player-dossier__cta-closed { display: none; }
.player-dossier.is-open .player-dossier__cta-open { display: inline; }

.player-dossier__caret {
  display: inline-block;
  font-size: var(--text-md);
  line-height: 1;
  transform: rotate(0deg);
  transition: transform 280ms cubic-bezier(0.22, 1, 0.36, 1);
}
.player-dossier.is-open .player-dossier__caret { transform: rotate(90deg); }

/* Animated body — minmax(0, Nfr) is the cleanest way to ease an auto-height
   reveal. Plain `0fr` alone does NOT collapse the row: grid's default
   `auto` minimum lets the track grow to min-content of the child, so the
   closed dossier leaked the inner's natural height at the bottom. The
   minmax lower-bound of 0 overrides that min-content floor and forces the
   track to truly collapse. */
.player-dossier__body {
  display: grid;
  grid-template-rows: minmax(0, 0fr);
  transition:
    grid-template-rows 360ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 220ms ease;
  opacity: 0;
}
.player-dossier.is-open .player-dossier__body {
  grid-template-rows: minmax(0, 1fr);
  opacity: 1;
}
.player-dossier__inner {
  overflow: hidden;
  min-height: 0;
  padding: 0 var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}
/* Padding-bottom + top rule only appear when open, so the closed state
   has no residual spacing contributing to container height. */
.player-dossier.is-open .player-dossier__inner {
  padding-top: var(--space-md);
  padding-bottom: var(--space-lg);
  border-top: 1px solid color-mix(in oklch, var(--accent) 15%, transparent);
}

/* Stagger the blocks inside the dossier when opening so the reveal
   feels like a file unfolding instead of a binary flip. */
@keyframes dossier-reveal {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.player-dossier.is-open .player-dossier__inner > * {
  animation: dossier-reveal 360ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
.player-dossier.is-open .player-dossier__inner > *:nth-child(1) { animation-delay: 100ms; }
.player-dossier.is-open .player-dossier__inner > *:nth-child(2) { animation-delay: 170ms; }
.player-dossier.is-open .player-dossier__inner > *:nth-child(3) { animation-delay: 240ms; }

/* Shared sub-section mini-header — consistent chrome for each block
   inside the dossier even though the bodies differ in shape. */
.dossier-block__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  padding-bottom: var(--space-xs);
  margin-bottom: var(--space-sm);
  border-bottom: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
}
.dossier-block__title {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin: 0;
}
.dossier-block__hint {
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
  font-family: var(--font-data);
}

/* Earned-badge cards with full definitions — replaces the mystery
   hero pills. Each card states label tag, full name, earning
   criterion, and live value in a single scan-friendly grid. */
.dossier-badges {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-sm);
}
.dossier-badge {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--space-sm);
  row-gap: 4px;
  padding: var(--space-sm) var(--space-md);
  background: color-mix(in oklch, var(--surface) 60%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  transition: border-color 150ms ease, background 150ms ease, transform 150ms ease;
}
.dossier-badge:hover {
  transform: translateY(-1px);
  border-color: color-mix(in oklch, var(--border) 30%, var(--text-tertiary));
}
.dossier-badge__tag {
  grid-row: 1 / 3;
  align-self: start;
  display: inline-flex;
  align-items: center;
  min-height: 22px;
  padding: 3px 8px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 3px;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  background: color-mix(in oklch, var(--border) 30%, transparent);
  white-space: nowrap;
}
.dossier-badge__body { min-width: 0; }
.dossier-badge__name {
  margin: 0 0 2px 0;
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text-primary);
  letter-spacing: 0.01em;
}
.dossier-badge__criterion {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--leading-relaxed);
  color: var(--text-tertiary);
}
.dossier-badge__meter {
  grid-column: 2;
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px dashed color-mix(in oklch, var(--border) 60%, transparent);
}
.dossier-badge__value {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: 0.01em;
}
.dossier-badge__unit {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* Per-slug palette — mirrors the hero power-badge colors so the
   visual language is continuous: same badge, richer display. */
.dossier-badge--rising { border-color: color-mix(in oklch, var(--positive) 30%, var(--border)); }
.dossier-badge--rising .dossier-badge__tag {
  color: var(--positive);
  border-color: color-mix(in oklch, var(--positive) 50%, transparent);
  background: color-mix(in oklch, var(--positive) 10%, transparent);
}
.dossier-badge--rising .dossier-badge__value { color: var(--positive); }

.dossier-badge--ironman { border-color: color-mix(in oklch, var(--tier-sector) 30%, var(--border)); }
.dossier-badge--ironman .dossier-badge__tag {
  color: color-mix(in oklch, var(--tier-sector) 85%, var(--text-primary));
  border-color: color-mix(in oklch, var(--tier-sector) 55%, transparent);
  background: color-mix(in oklch, var(--tier-sector) 10%, transparent);
}

.dossier-badge--hot { border-color: color-mix(in oklch, var(--accent) 40%, var(--border)); }
.dossier-badge--hot .dossier-badge__tag {
  color: var(--accent);
  border-color: color-mix(in oklch, var(--accent) 55%, transparent);
  background: color-mix(in oklch, var(--accent) 12%, transparent);
  box-shadow: 0 0 8px color-mix(in oklch, var(--accent) 25%, transparent);
}
.dossier-badge--hot .dossier-badge__value {
  color: var(--accent);
  text-shadow: 0 0 10px color-mix(in oklch, var(--accent) 35%, transparent);
}

/* Field + Closest Calls pair — table on one side, card stacks on the
   other. Two shapes instead of another same-sized rectangle so the
   rhythm reads as distinct before the content registers. */
.dossier-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--space-lg);
  align-items: start;
}
.dossier-pair__cell { min-width: 0; }

/* Nested overrides — the existing sub-section partials ship with their
   own top/bottom margins and h2 titles sized for a standalone context.
   Zero the margins and demote the titles so they fit the dossier's
   internal hierarchy without forking the partials. */
.player-dossier .vs-field,
.player-dossier .closest-calls,
.player-dossier .event-calendar {
  margin: 0;
}
.player-dossier .vs-field__title,
.player-dossier .closest-calls__title,
.player-dossier .event-calendar__title {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin: 0 0 var(--space-sm) 0;
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
}
.player-dossier .closest-calls__grid {
  grid-template-columns: 1fr; /* stack vertically inside the narrow pair column */
}

/* Reduced motion — state still changes, just without the animated
   reveal. Keeps the dossier accessible under user preference. */
@media (prefers-reduced-motion: reduce) {
  .player-dossier,
  .player-dossier__body,
  .player-dossier__caret,
  .player-dossier.is-open .player-dossier__inner > *,
  .dossier-badge {
    transition: none !important;
    animation: none !important;
  }
}

/* .vs-pace* rules removed on 2026-04-23 — the Meeting pace tab was
   dropped per the impeccable critique (cadence / last meeting / next
   expected didn't offer enough signal to earn a top-level tab). The
   meeting_pace helper stays available for future use. */

/* Tier breakdown on the vs page — mirrors vs-per-format layout but uses
   the existing tier-chip visual for each tier row. */
.vs-per-tier {
  margin: var(--space-md) 0 var(--space-lg);
}
.vs-per-tier__grid {
  display: grid;
  gap: var(--space-xs);
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  margin-top: var(--space-sm);
}
.vs-per-tier__row {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--space-sm);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.vs-per-tier__record {
  font-size: var(--text-base);
  font-feature-settings: "tnum";
  color: var(--text-primary);
}
.vs-per-tier__pct {
  font-size: var(--text-xs);
  color: var(--accent);
  font-feature-settings: "tnum";
  font-weight: 600;
}
.vs-per-tier__count {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  color: var(--text-tertiary);
}

/* vs-matches grid — the match table inside the Matches tab on the vs
   page. Previously relied on an inline style="grid-template-columns:..."
   on the parent element; moved here on 2026-04-23 so the template stays
   CSP-nonce clean and the layout is discoverable in CSS. */
.tourn-matches.vs-matches {
  grid-template-columns: 90px 1fr 80px 90px minmax(110px, 1fr) 100px;
  border-top: 1px solid var(--border);
  border-top-left-radius: var(--radius-sm);
  border-top-right-radius: var(--radius-sm);
}

.vs-matches__tourn a {
  color: var(--text-primary);
  text-decoration: none;
}

.vs-matches__tourn a:hover { color: var(--accent); }

/* Override the inherited .tourn-matches__header nth-child rules — those
   were calibrated for a different table layout (col 2 was a centered
   numeric HRI cell). On the vs-matches grid, col 2 is the Tournament
   name (text, left-aligned data) so the header needs to start-align too,
   otherwise header and data drift visually. */
.tourn-matches.vs-matches .tourn-matches__header > span:nth-child(2) {
  text-align: start;
}

.vs-matches__format,
.vs-matches__round {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  font-feature-settings: "tnum";
}

.vs-matches__pre-match {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-secondary);
  font-feature-settings: "tnum";
}

.vs-matches__rating-pill {
  display: inline-block;
  padding: 1px 6px;
  background: var(--accent-subtle);
  color: var(--accent-hover);
  border-radius: 3px;
  font-weight: 500;
}

.vs-matches__rating-sep {
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-wider);
  color: var(--text-tertiary);
  text-transform: uppercase;
}

.vs-matches__rating-empty {
  color: var(--text-tertiary);
  opacity: 0.6;
}

.vs-matches__result {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  font-weight: 600;
}

.vs-matches__game-score {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  font-weight: 500;
  font-feature-settings: "tnum";
}

/* Bulleted lists inside How-It-Works card descriptions. Default UL styles
   are oddly indented in this theme; tighten vertical rhythm and pull the
   bullet flush with the description column so it composes with the
   surrounding prose. */
.about-list {
  margin: 8px 0 0;
  padding-left: 20px;
  color: var(--text-secondary);
}

.about-list li {
  margin: 6px 0;
  line-height: var(--leading-relaxed);
}

.about-list li::marker {
  color: var(--text-tertiary);
}

/* The phase table sits inside the panel — override the outer table chrome
   so it blends into the panel surface instead of looking like a second
   card inside the frame. */
.rating-math-panel__table {
  background: transparent;
  border: none;
  border-top: 1px solid var(--accent-soft);
  border-radius: 0;
  padding: 8px 0 2px;
  margin: 0;
  grid-template-columns: minmax(170px, 1.8fr) 70px minmax(100px, 1fr) 80px minmax(100px, 1fr) 80px;
}

/* Per-match table on the audit page uses 9 columns (adds Odds between
   Result and Game W-L, and an approximate Δ between Multiplier and Type).

   Column sizing note: the original `1fr` on Opponent greedily absorbed
   every pixel of leftover width, pushing result/odds/game/mult/Δ/type
   far to the right and leaving 600+px of empty space in the middle on
   wide viewports. Balanced flex (Opponent 4fr, data cols 1fr each) keeps
   Opponent prominent without starving the right side. Each column carries
   a minmax floor so narrow viewports don't crush individual cells. */
.tourn-matches--audit {
  border-top: 1px solid var(--border);
  border-top-left-radius: 6px;
  border-top-right-radius: 6px;
  grid-template-columns:
    64px                   /* Round (label + trailing flag) */
    72px                   /* HRI pill */
    minmax(160px, 4fr)     /* Opponent */
    minmax(72px,  1fr)     /* Result */
    minmax(64px,  1fr)     /* Odds */
    minmax(64px,  1fr)     /* Game W-L */
    minmax(64px,  1fr)     /* Multiplier */
    minmax(72px,  1fr)     /* Δ */
    minmax(80px,  1fr);    /* Type */
}

/* Audit hero: the player at the event. H1 slightly smaller than the
   global heading to yield visual dominance to the rating number below. */
.audit-hero__name {
  font-size: var(--text-2xl); /* 22px */
}

/* The ± rating-change beside the hero number — sized so the number
   stays dominant but the delta is legible at a glance. */
.audit-hero__delta-change {
  font-size: var(--text-xl);
}

/* Right-align all numeric columns (Record, Raw Glicko-2, Multiplier,
   Applied, Running). The default .tourn-matches rule only right-aligns
   columns 5+, but the phase breakdown has numeric data in columns 2-6
   so every column except Phase (col 1) should be tabular-right. */
.rating-math-panel__table .tourn-matches__header > span:nth-child(n+2),
.rating-math-panel__table .tourn-match > span:nth-child(n+2) {
  text-align: right;
}

.rating-math-panel__phase {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.rating-math-panel__phase strong {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-primary);
}

.rating-math-panel__phase-count {
  color: var(--text-tertiary);
  font-size: var(--text-2xs);
  font-family: var(--font-data);
  font-feature-settings: "tnum";
}

.rating-math-panel__mult {
  color: var(--text-secondary);
  font-feature-settings: "tnum";
}

/* Multiplier-intensity phase dots. Swiss at base tier is the faintest;
   a Finals-winner match (highest multiplier) is the brightest. Visual
   encoding of "how much this phase amplified every point of surprise." */
.phase-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}
.phase-dot--faint { background: var(--accent-soft); }
.phase-dot--soft  { background: var(--accent-mid); }
.phase-dot--mid   { background: var(--accent-strong); }
.phase-dot--hot   { background: var(--accent-hot); box-shadow: 0 0 6px var(--accent-glow); }

/* Full-width separator rule above the Total row — spans every grid column
   as one continuous line instead of per-cell borders with column-gap breaks. */
.rating-math-panel__separator {
  grid-column: 1 / -1;
  border-top: 1px solid var(--accent-soft);
  margin: 8px 0 6px;
}

.rating-math-panel__total > span {
  font-family: var(--font-data);
  font-feature-settings: "tnum";
}

.rating-math-panel__total > span:first-child strong {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-primary);
  text-transform: uppercase;
  letter-spacing: 0.6px;
}

.rating-math-panel__caveat {
  margin: 16px 0 0;
  padding-top: 14px;
  border-top: 1px dashed var(--border);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  line-height: var(--leading-relaxed);
  color: var(--text-tertiary);
}

/* ============================================================
   MATCH HIGHLIGHTS — compact "biggest upset / costliest loss"
   callouts above the matches table. Two events of the tournament
   that moved the rating the most, given first-class surface above
   the row-level scan. Earned accent color — these are the rating's
   two most consequential moments.
   ============================================================ */

.match-highlights {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 20px 0 14px;
}

.match-highlight {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 2px solid;
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  flex: 1 1 auto;
  min-width: 280px;
}

.match-highlight--upset  { border-left-color: var(--positive); }
.match-highlight--fumble { border-left-color: var(--negative); }
.match-highlight--earned { border-left-color: var(--accent); }

.match-highlight__label {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--text-tertiary);
  white-space: nowrap;
}

.match-highlight--upset  .match-highlight__label { color: var(--positive); }
.match-highlight--fumble .match-highlight__label { color: var(--negative); }
.match-highlight--earned .match-highlight__label { color: var(--accent); }

.match-highlight__detail {
  color: var(--text-secondary);
}

.match-highlight__detail strong { color: var(--text-primary); }

.match-highlight__rating {
  display: inline-block;
  padding: 1px 6px;
  margin-left: 4px;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-feature-settings: "tnum";
  color: var(--text-tertiary);
  background: rgba(148, 163, 184, 0.06);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.match-highlight__data {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-feature-settings: "tnum";
  color: var(--text-primary);
}

.match-highlight__sep {
  color: var(--text-tertiary);
  padding: 0 2px;
}

/* Inline row marker that points out the upset / costliest-loss rows in
   the match table. Tiny triangles carry the same semantics as rating
   deltas — up = positive surprise, down = negative. */
.match-flag {
  display: inline-block;
  font-size: var(--text-nano);
  margin-left: 8px;
  line-height: var(--leading-tight);
  vertical-align: baseline;
  cursor: help;
}

.match-flag--upset  { color: var(--positive); }
.match-flag--fumble { color: var(--negative); }

@media (max-width: 768px) {
  .tourn-matches {
    grid-template-columns: auto auto 1fr;
    gap: 4px 10px;
  }
  .tourn-matches__header { display: none; }
  .tourn-match > span { font-size: var(--text-2xs); }
}

/* ============================================================
   FEEDBACK — modal + form + nav button + flash
   ============================================================ */

/* Nav-bar Feedback button: same visual weight as a nav link but styled
   as a button so a11y reads it as an action, not a destination. */
.nav-link--feedback {
  background: transparent;
  border: 1px solid var(--border);
  padding: 4px 12px;
  border-radius: 5px;
  cursor: pointer;
  transition: border-color 150ms ease, color 150ms ease, background 150ms ease;
}
.nav-link--feedback:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-wash);
}

/* Right-edge cluster in the sticky header: the section-jump nav plus the
   standalone Report Issue action. The action is NOT inside the <nav>, so
   screen-readers don't announce a modal trigger as a profile section. */
.profile-sticky__tail {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* Report Issue — deliberately different from the section-jump anchors so
   a keyboard tabber can tell an action from a jump. Ghost-button styling
   with a hairline border that activates on hover. */
.profile-sticky__report {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-tertiary);
  background: transparent;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 5px;
  cursor: pointer;
  transition: border-color 150ms ease, color 150ms ease;
}

.profile-sticky__report:hover,
.profile-sticky__report:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
}

/* Modal — native <dialog> element. Uses the data-terminal aesthetic to
   match the rest of the site. ::backdrop blurs the page underneath.
   `inset: 0; margin: auto` centers horizontally + vertically (Chrome/Edge
   defaults handle this for un-styled <dialog> but we override `display`,
   so we set it explicitly). */
.feedback-modal[open] {
  display: block;
  position: fixed;
  inset: 0;
  margin: auto;
  padding: 0;
  width: min(540px, calc(100vw - 24px));
  max-height: calc(100vh - 48px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text-primary);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--accent-soft);
  overflow: hidden;
}

.feedback-modal::backdrop {
  background: rgba(11, 17, 32, 0.72);
  backdrop-filter: blur(4px);
}

.feedback-modal__inner {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 22px 24px 24px;
  max-height: calc(100vh - 48px);
  overflow-y: auto;
}

.feedback-modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.feedback-modal__close {
  /* 40px min tap target — was 32px, which fails the 44/40px touch rule on
     mobile. Visual chrome (the × character) is unchanged. */
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 5px;
  font-size: var(--text-2xl);
  line-height: var(--leading-tight);
  color: var(--text-tertiary);
  cursor: pointer;
  transition: border-color 150ms ease, color 150ms ease;
}
.feedback-modal__close:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.feedback-modal__intro {
  margin: 0;
}

/* Form — also used standalone on /feedbacks/new. Tabular, dense, no fluff. */
.feedback-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.feedback-form__row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.feedback-form__label {
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.feedback-form__hint {
  margin: 6px 0 0;
  font-size: var(--text-xs);
  line-height: var(--leading-relaxed);
  color: var(--text-tertiary);
}

.feedback-form__hint strong {
  color: var(--text-secondary);
  font-weight: 600;
}

.feedback-form__input,
.feedback-form__textarea {
  font-family: var(--font-body);
  font-size: var(--text-base);
  color: var(--text-primary);
  background: var(--input);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 9px 12px;
  width: 100%;
  transition: border-color 150ms ease, box-shadow 150ms ease;
}

/* Strip the native <select> appearance and draw our own chevron so the
   caret sits at consistent padding, matches the dark theme, and doesn't
   look squashed against the right edge. The SVG inlines a chevron-down. */
select.feedback-form__input {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 36px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'><path d='M1 1.5L6 6.5L11 1.5' stroke='%2394A3B8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 14px center;
  cursor: pointer;
}

select.feedback-form__input:focus {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'><path d='M1 1.5L6 6.5L11 1.5' stroke='%230EA5E9' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
}

.feedback-form__textarea {
  resize: vertical;
  min-height: 120px;
  line-height: var(--leading-relaxed);
}

.feedback-form__input:focus,
.feedback-form__textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.feedback-form__actions {
  display: flex;
  justify-content: flex-end;
}

.feedback-form__errors {
  background: rgba(239, 68, 68, 0.08);
  border: 1px solid rgba(239, 68, 68, 0.4);
  color: var(--negative);
  border-radius: 5px;
  padding: 10px 12px;
  font-size: var(--text-sm);
}
.feedback-form__errors ul {
  list-style: disc;
  margin: 6px 0 0 18px;
  padding: 0;
}

/* Generic primary button used by the form submit + future actions. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 18px;
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: 600;
  border-radius: 5px;
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  transition: background 150ms ease, border-color 150ms ease, color 150ms ease;
}

.btn--primary {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
}
.btn--primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--bg);
}

/* Toast-style flash anchored bottom-center. Auto-fades after 4s. */
.flash {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 100;
  padding: 10px 18px;
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: 5px;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-primary);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4), 0 0 0 1px var(--accent-soft);
  animation: flash-fade 4s ease forwards;
}
.flash--notice { color: var(--text-primary); }

@keyframes flash-fade {
  0%, 80% { opacity: 1; transform: translateX(-50%) translateY(0); }
  100%    { opacity: 0; transform: translateX(-50%) translateY(8px); pointer-events: none; }
}

/* ============================================================
   AUTH PAGES — Devise sign in / password reset.
   Centered card, dark theme, brand chrome. Reuses the existing
   .feedback-form__input + .btn primitives.
   ============================================================ */

.auth-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(100vh - 120px);
  padding: 32px 16px;
}

.auth-card {
  position: relative;
  width: 100%;
  max-width: 420px;
  padding: 28px 32px 32px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45), 0 0 0 1px var(--accent-soft);
}

/* Same accent edge-glow as the profile hero card. */
.auth-card::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 8px;
  border: 1px solid transparent;
  background: linear-gradient(180deg, var(--accent-mid), transparent 50%) border-box;
  -webkit-mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}

.auth-card__brand {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 24px;
}

.auth-logo {
  height: 28px;
  width: auto;
}

.auth-card__chip {
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: 0.2em;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 4px;
  padding: 2px 8px;
  background: var(--accent-subtle);
  text-shadow: 0 0 8px var(--accent-glow);
}

.auth-card__title {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--text-primary);
  margin: 0 0 6px;
}

.auth-card__sub {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  margin: 0 0 20px;
}

.auth-form {
  /* Tighter than the feedback modal — shorter form, more breathing room. */
  gap: 16px;
}

.auth-actions {
  margin-top: 4px;
}

.auth-actions .btn {
  width: 100%;
  justify-content: center;
}

.auth-remember {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  cursor: pointer;
}
.auth-remember input[type="checkbox"] {
  accent-color: var(--accent);
  width: 14px;
  height: 14px;
}

.auth-card__footer {
  margin: 18px 0 0;
  text-align: center;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-tertiary);
}

/* Inline alert for Devise's flash messages (alert/notice). */
.auth-flash {
  margin: 0 0 14px;
  padding: 10px 12px;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  border-radius: 5px;
  border: 1px solid var(--border);
  background: var(--input);
  color: var(--text-secondary);
}
.auth-flash--alert {
  color: var(--negative);
  border-color: rgba(239, 68, 68, 0.4);
  background: rgba(239, 68, 68, 0.08);
}
.auth-flash--notice {
  color: var(--accent);
  border-color: var(--accent-strong);
  background: var(--accent-subtle);
}

/* ============================================================
   KEYBOARD SHORTCUTS DIALOG + ROW FOCUS
   ============================================================ */

/* Reuses the feedback-modal's centered native <dialog> pattern. Close
   button style is shared (.feedback-modal__close) since the X glyph
   and 40px square are the same. */
.kb-dialog { display: none; }
.kb-dialog[open] {
  display: block;
  position: fixed;
  inset: 0;
  margin: auto;
  padding: 0;
  width: min(440px, calc(100vw - 24px));
  max-height: calc(100vh - 48px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--accent-soft);
  overflow: hidden;
}
.kb-dialog::backdrop {
  background: rgba(11, 17, 32, 0.72);
  backdrop-filter: blur(4px);
}

.kb-dialog__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-md) var(--space-md);
}

.kb-dialog__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-md);
}

.kb-dialog__kicker {
  display: block;
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: 4px;
}

.kb-dialog__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

.kb-dialog__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: 0;
}

.kb-dialog__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-xs) 0;
  border-bottom: 1px solid var(--border);
}
.kb-dialog__row:last-child { border-bottom: 0; }

.kb-dialog__label {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.kb-dialog__keys {
  margin: 0;
  display: inline-flex;
  gap: 4px;
}

/* Shared <kbd> look. Matches the search-input__hint pill so the shortcut
   badges feel consistent with the / hint on the tournaments page. */
.kb {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  font-family: var(--font-data);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-secondary);
  background: color-mix(in oklch, var(--border) 60%, transparent);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: 4px;
}

.kb-dialog__footnote {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  line-height: var(--leading-relaxed);
}

/* "Focused" row highlight when the user j/k's through a table. Subtle
   inset accent + left rail stripe so the current row is obvious without
   blowing out the row's own hover state. */
.kb-row--focused {
  background: var(--card-hover) !important;
  box-shadow: inset 3px 0 0 var(--accent);
}

/* ============================================================
   INFO DIALOG — read-only explainer modal
   ============================================================
   Generic native <dialog> for informational content that doesn't belong
   inline. Current use: the "How the leaderboard ranks" modal opened from
   the ? chip next to Overall. Mirrors the kb-dialog surface treatment so
   every modal on the site feels like the same surface; wider because the
   body runs prose instead of two-column kbd rows. */
.info-dialog { display: none; }
.info-dialog[open] {
  display: block;
  position: fixed;
  inset: 0;
  margin: auto;
  padding: 0;
  width: min(640px, calc(100vw - 24px));
  max-height: calc(100vh - 48px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--accent-soft);
  overflow: hidden;
}
.info-dialog::backdrop {
  background: rgba(11, 17, 32, 0.72);
  backdrop-filter: blur(4px);
}

.info-dialog__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-md);
}

.info-dialog__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-md);
}

.info-dialog__kicker {
  display: block;
  font-family: var(--font-data);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: 4px;
}

.info-dialog__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

.info-dialog__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: var(--leading-relaxed);
}
/* Prose fills the modal body (no max-width cap) so paragraphs and the
   footer divider span edge-to-edge inside the dialog's padding. The
   dialog itself is capped at 640px, so line length stays within a
   readable range without the extra internal cap. */
.info-dialog__body p { margin: 0; }

.info-dialog__section {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}
.info-dialog__section p { margin: 0; }
.info-dialog__section p + p { margin-top: var(--space-2xs); }

.info-dialog__subhead {
  margin: 0;
  font-family: var(--font-data);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.info-dialog__body code {
  font-family: var(--font-data);
  font-size: var(--text-xs);
  padding: 1px 6px;
  background: color-mix(in oklch, var(--border) 45%, transparent);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
}

.info-dialog__footer {
  margin: 0;
  padding-top: var(--space-xs);
  border-top: 1px solid var(--border);
  font-family: var(--font-data);
  font-size: var(--text-xs);
}

.info-dialog__footer-link {
  color: var(--text-secondary);
  text-decoration: none;
  border-bottom: 1px dotted currentColor;
  transition: color 150ms ease;
}
.info-dialog__footer-link:hover { color: var(--accent); }

/* ============================================================
   FOLLOW A PLAYER (localStorage; no accounts)
   ============================================================ */

/* Toggle button on the profile hero. Stays quiet in the "not following"
   state (text-tertiary outline) and warms to gold when active so the
   star lights up like a real follow. */
.follow-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-secondary);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color 150ms ease, color 150ms ease, background 150ms ease;
}
.follow-toggle:hover {
  border-color: var(--accent);
  color: var(--text-primary);
}
.follow-toggle.is-followed {
  color: var(--rank-gold);
  border-color: var(--rank-gold);
  background: color-mix(in oklch, var(--rank-gold) 12%, transparent);
}
.follow-toggle__icon {
  display: inline-flex;
  font-size: var(--text-md);
  line-height: 1;
}
.follow-toggle.is-followed .follow-toggle__icon {
  text-shadow: 0 0 8px color-mix(in oklch, var(--rank-gold) 60%, transparent);
}

/* Followed row highlight on the leaderboard. Amber-gold rail on the left
   edge + very subtle tint so the user spots their rivals at a glance
   without blowing out the row's own colors. Does not compete with the
   kb-row--focused accent rail — the two land in different states. */
.rank-row.is-followed {
  box-shadow: inset 3px 0 0 var(--rank-gold);
  background: color-mix(in oklch, var(--rank-gold) 5%, var(--card));
}
.rank-row.is-followed:hover {
  background: color-mix(in oklch, var(--rank-gold) 8%, var(--card-hover));
}


