/* Wave Levels & Invites — competitive ladder.
   Aesthetic: broadcast stats table / esports ranking (Faceit, op.gg), NOT a gamer landing page.
     - the DATA is the design: every number is monospace + tabular, so columns align down the page
     - near-black ground, one accent (Discord blurple), colour used only where it carries meaning (tier, medal)
     - tier = a coloured edge and a small label, never a badge with decoration
     - one motion moment: a fast staggered row entrance. Nothing else animates.
   Rank names come from the SERVER ROLES only (Bronze..Unreal). Below the first role there is no rank — we never invent one.
   Fonts are SELF-HOSTED under ./fonts (the CSP allows no external font host; it needs font-src 'self', see _headers):
     Archivo        — grotesque built for interface text, carries the member names and UI  (SIL OFL, see fonts/OFL-Archivo.txt)
     JetBrains Mono — every figure on the page, chosen for its tabular numerals           (SIL OFL, see fonts/OFL-JetBrainsMono.txt)
   Both are variable (400–700) so one file per subset covers every weight used. */

/* latin first: the browser only fetches latin-ext when a name actually needs it */
@font-face {
  font-family: "Archivo"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("fonts/archivo-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
                 U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("fonts/archivo-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
                 U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("fonts/jetbrainsmono-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
                 U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("fonts/jetbrainsmono-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
                 U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  color-scheme: dark;

  /* a reward that is not a role (a side-quest reward or a product): gold, no role dot - the same as the bot's Discord cards */
  --reward: #f2cf6b;
  --reward-bg: rgba(242, 207, 107, .12);
  --reward-line: rgba(242, 207, 107, .42);

  --bg: #08090c;
  --surface: #0e1014;
  --surface-2: #14171d;
  --surface-3: #191d25;
  --line: #1c2028;
  --line-2: #272d38;

  --text: #eceef1;
  --dim: #98a0ac;
  --faint: #7c838d;                  /* >= 4.5:1 on every dark surface (was #626a76, 3.5:1) */

  --accent: #5865f2;
  --accent-lift: #7c87ff;
  --accent-wash: rgba(88, 101, 242, .13);
  --good: #3ba55d;
  --glow: rgba(88, 101, 242, .10);
  --strong: #ffffff;                 /* the strongest text colour: pure white on dark */
  --nav: rgba(8, 9, 12, .86);        /* sticky nav, translucent over the ground */

  --medal-1: #e8b93f;
  --medal-2: #b9c2ce;
  --medal-3: #cd7f4a;

  --t-bronze: #cd7f4a; --t-silver: #c3cad6; --t-gold: #e8b93f; --t-platinum: #52dbe6;
  --t-diamond: #4aa8ff; --t-elite: #a97bff; --t-champion: #ff5f93; --t-unreal: #ff8a3d;

  --shadow-card: none;                       /* dark relies on borders only, per the original design */
  --shadow-nav: none;
  --shadow-pop: 0 14px 32px rgba(0, 0, 0, .42);  /* floating layers: tooltips */
  --crown: var(--medal-1);
  --spark: #ffffff;
  --spark-glow: var(--accent-lift);

  --sans: "Archivo", "Segoe UI Variable Text", "Segoe UI", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;

  --row: 56px 44px minmax(0, 1fr) 132px 150px 84px 88px 64px;
  --radius: 10px;
  --radius-sm: 8px;                  /* controls: buttons, inputs, the segmented switch */

  /* ---- motion system (WaveDropMaps team spec, matched exactly) ----
     Colour/border/background changes are linear and fast (they are a state fact, not a gesture). Anything that
     moves in space uses a spring, because a gesture should feel like it has weight. Nothing loops except the
     live dot - every other animation says something true once, then holds still. */
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
  --ease-out: cubic-bezier(.22, 1, .36, 1);
  --ease-inout: cubic-bezier(.65, 0, .35, 1);
  --dur-fast: 140ms;
  --dur: 240ms;
  --dur-slow: 420ms;
}

/* Light theme. Not an inversion: the ground stays a soft off-white, surfaces go pure white so rows lift off it,
   and the accent darkens so it still carries on a light background. */
:root[data-theme="light"] {
  color-scheme: light;

  --reward: #765509;
  --reward-bg: rgba(214, 165, 44, .14);
  --reward-line: rgba(168, 122, 20, .45);

  --bg: #f5f6fa;
  --surface: #ffffff;
  --surface-2: #eef0f5;
  --surface-3: #e3e6ee;
  --line: #dde1ea;
  --line-2: #c5cbda;

  --text: #14161b;
  --dim: #4a515e;
  --faint: #5f6672;                  /* >= 4.5:1 on white and on the soft ground (was #7c8494, 3.4:1) */

  --accent: #4551d4;
  --accent-lift: #3742b8;
  --accent-wash: rgba(69, 81, 212, .10);
  --good: #17713a;
  --glow: rgba(69, 81, 212, .08);
  --strong: #05070b;                 /* ...and near-black on light */
  --nav: rgba(246, 247, 249, .88);

  /* medal colours double as text (the podium figure, the medal digit, a top-3 position), so on white they are
     the deep end of gold / silver / bronze: >= 4.5:1. The crown keeps the brighter gold - it is a picture. */
  --medal-1: #835f08;
  --medal-2: #5e6771;
  --medal-3: #945629;
  --crown: #d19a12;
  --spark: var(--accent);
  --spark-glow: rgba(69, 81, 212, .55);

  /* light needs real depth to read as polished rather than flat: a soft lift under every card, plus a hairline
     under the sticky nav so it visibly separates from the page instead of just changing colour at the edge */
  --shadow-card: 0 1px 2px rgba(20, 22, 27, .04), 0 10px 24px -16px rgba(20, 22, 27, .16);
  --shadow-nav: 0 1px 0 rgba(20, 22, 27, .06);
  --shadow-pop: 0 14px 32px rgba(20, 22, 27, .16), 0 2px 6px rgba(20, 22, 27, .06);
}

* { box-sizing: border-box; }
/* several components below set their own `display` (grid rows, grid cards); without this, that declaration
   (author-origin) outranks the browser's default `[hidden]{display:none}` (user-agent origin) and the search
   filter's `hidden = true` would silently stop hiding anything. This keeps the native hidden attribute authoritative
   everywhere on the page, now and for anything added later. */
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 400 15px/1.5 var(--sans);
  min-height: 100vh;
  /* depth without a gradient mesh: one very soft cool wash behind the masthead only */
  background-image: radial-gradient(1100px 380px at 50% -220px, var(--glow), transparent 70%);
  background-repeat: no-repeat;
  transition: background-color .18s ease, color .18s ease;
}
.wrap { width: calc(100% - 40px); max-width: 1120px; margin-left: auto; margin-right: auto; }
.sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden;
      clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }   /* clip: the fallback for older WebKit */
.skip { position: absolute; left: -9999px; }
.skip:focus { left: 16px; top: 12px; z-index: 70; padding: 10px 16px; background: var(--accent); color: #fff; border-radius: var(--radius-sm);
              font-weight: 600; text-decoration: none; }
#board { outline: none; }                 /* the skip link's target: focusable only so focus lands there */
:focus-visible { outline: 2px solid var(--accent-lift); outline-offset: 2px; border-radius: 6px; }
/* Safari before 15.4 has no :focus-visible - there, keyboard users still get a ring on plain :focus */
@supports not selector(:focus-visible) { :focus { outline: 2px solid var(--accent-lift); outline-offset: 2px; } }
/* iOS: no grey flash over every tappable pill and button - each one has its own pressed state */
button, a, .badge, .pill, [role="tab"] { -webkit-tap-highlight-color: transparent; }
b, strong { font-weight: 600; }

/* ---------- scrollbars, themed instead of the default OS grey ---------- */
html { scrollbar-color: var(--line-2) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background-color: var(--line-2); border-radius: 999px;
  border: 3px solid var(--bg); background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background-color: var(--faint); }
::-webkit-scrollbar-corner { background: transparent; }

/* ---------- "also in your language" suggestion (app.js langHint) ----------
   A slim strip above the masthead, in the page's own surface and type: the sentence, one small action, a close. It
   sits in the flow (never over content) and is shown once. */
.lang-hint { background: var(--surface-2); border-bottom: 1px solid var(--line); color: var(--dim); font-size: 13px; }
:root[data-theme="light"] .lang-hint { background: var(--surface); }
.lang-hint-in { display: flex; align-items: center; gap: 12px; min-height: 44px; padding: 6px 0; }
.lang-hint-in p { margin: 0; flex: 1 1 auto; min-width: 0; color: var(--text); }
.lang-hint-go {
  appearance: none; flex: none; height: 30px; padding: 0 12px; cursor: pointer; border: 0; border-radius: var(--radius-sm);
  color: #fff; background: var(--accent); font: 600 13px/1 var(--sans);
  transition: background-color var(--dur-fast) linear;
}
.lang-hint-go:hover { background: #4752c4; }
:root[data-theme="light"] .lang-hint-go:hover { background: #3742b8; }
.lang-hint-x {
  appearance: none; flex: none; width: 30px; height: 30px; display: grid; place-items: center; cursor: pointer;
  margin-right: -6px; color: var(--dim); background: none; border: 0; border-radius: var(--radius-sm);
}
.lang-hint-x:hover { color: var(--text); background: var(--surface-3); }
.lang-hint-x svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
@media (pointer: coarse) { .lang-hint-go, .lang-hint-x { height: 40px; } .lang-hint-x { width: 40px; } }
@media print { .lang-hint { display: none; } }

/* ---------- masthead ---------- */
.masthead { padding: 32px 0 24px; }
.mast-top { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.mast-right { display: flex; align-items: center; gap: 14px; }
/* ---------- join the Discord ----------
   The one call to action on the page: solid accent, same height and radius as the theme toggle, the Discord glyph
   at text size. Primary without shouting - no gradient, no glow, no motion beyond the shared press. */
.join {
  display: inline-flex; align-items: center; gap: 8px; height: 34px; padding: 0 14px 0 12px; flex: none;
  color: #fff; background: var(--accent); border-radius: var(--radius-sm); text-decoration: none;
  font: 600 13px/1 var(--sans); letter-spacing: -.005em; white-space: nowrap;
  transition: background-color var(--dur-fast) linear, transform var(--dur-slow) var(--ease-spring);
}
.join:hover { background: #4752c4; }                         /* darker on hover: white stays >= 4.5:1 */
:root[data-theme="light"] .join:hover { background: #3742b8; }
.join:active { transform: scale(.965); transition: transform 80ms var(--ease-out); }
.join .dc { width: 17px; height: 17px; flex: none; }
/* phones: the masthead is tight, so the header button becomes the glyph alone (its label stays for screen readers) */
@media (max-width: 480px) {
  .mast-top .join { width: 34px; padding: 0; justify-content: center; }
  .mast-top .join .join-l { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden;
                            clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
  .mast-right { gap: 10px; }
}
.theme {
  appearance: none; width: 34px; height: 34px; display: grid; place-items: center; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm); color: var(--dim);
  transition: transform var(--dur-slow) var(--ease-spring),
              color var(--dur-fast) linear, border-color var(--dur-fast) linear, box-shadow var(--dur-fast) linear;
}
@media (hover: hover) and (pointer: fine) {
  .theme:hover { color: var(--text); border-color: var(--line-2); transform: translateY(-2px); box-shadow: 0 6px 14px -6px rgba(0, 0, 0, .35); }
}
.theme:active { transform: scale(.965); transition: transform 80ms var(--ease-out); }
.theme svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; }
.theme .i-sun { display: none; }
:root[data-theme="light"] .theme .i-sun { display: block; }
:root[data-theme="light"] .theme .i-moon { display: none; }
/* ---------- language picker ----------
   The button matches the theme toggle beside it (height, surface, border, lift and press): a globe, the language
   code ("EN") and a small caret. It opens a listbox menu drawn like the site's other floating layers (the column
   tips and rank tooltips): the raised surface, a hairline border, the shared radius and pop shadow. Each language
   is written in itself; the current one carries the accent tick and full-strength text. */
.lang { position: relative; flex: none; }
.lang-btn {
  appearance: none; display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 9px 0 10px;
  cursor: pointer; font: inherit; color: var(--dim);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  transition: transform var(--dur-slow) var(--ease-spring),
              color var(--dur-fast) linear, border-color var(--dur-fast) linear, box-shadow var(--dur-fast) linear;
}
@media (hover: hover) and (pointer: fine) {
  .lang-btn:hover { color: var(--text); border-color: var(--line-2); transform: translateY(-2px); box-shadow: 0 6px 14px -6px rgba(0, 0, 0, .35); }
}
.lang-btn:active { transform: scale(.965); transition: transform 80ms var(--ease-out); }
.lang.open .lang-btn { color: var(--text); border-color: var(--line-2); transform: none; box-shadow: none; }
.lang-btn .i-globe { width: 15px; height: 15px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.6; }
.lang-btn .i-caret { width: 10px; height: 10px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.6;
                     stroke-linecap: round; stroke-linejoin: round; opacity: .8;
                     transition: transform var(--dur) var(--ease-out); }
.lang.open .i-caret { transform: rotate(180deg); }
.lang-code { font: 600 11px/1 var(--mono); letter-spacing: .06em; min-width: 2ch; }

.lang-menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 40; min-width: 188px; margin: 0; padding: 4px;
  list-style: none; outline: none;
  background: var(--surface-2); border: 1px solid var(--line-2); border-radius: var(--radius);
  box-shadow: var(--shadow-pop);
}
:root[data-theme="light"] .lang-menu { background: var(--surface); }
.lang-menu [role="option"] {
  display: grid; grid-template-columns: 14px minmax(0, 1fr) auto; align-items: center; gap: 10px;
  min-height: 36px; padding: 0 10px; border-radius: 6px; cursor: pointer; user-select: none;
  color: var(--dim); font: 500 14px/1.2 var(--sans); white-space: nowrap;
}
.lang-menu [role="option"] b { color: var(--faint); font: 600 10px/1 var(--mono); letter-spacing: .08em; }
.lang-menu .tick { width: 14px; height: 14px; fill: none; stroke: var(--accent-lift); stroke-width: 2;
                   stroke-linecap: round; stroke-linejoin: round; visibility: hidden; }
.lang-menu [aria-selected="true"] { color: var(--strong); font-weight: 600; }
.lang-menu [aria-selected="true"] .tick { visibility: visible; }
/* the highlighted option (pointer or arrow keys) gets the row-hover surface; from the keyboard it also gets the
   accent ring, so the focus position is always visible */
.lang-menu .is-active { background: var(--surface-3); color: var(--text); }
:root[data-theme="light"] .lang-menu .is-active { background: var(--surface-2); }
.lang-menu:focus-visible .is-active { box-shadow: inset 0 0 0 2px var(--accent-lift); }
/* Japanese written in itself needs a Japanese face even when the page is in another language */
.lang-menu [lang="ja"] span { font-family: "Archivo", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic UI",
                                           "Yu Gothic", Meiryo, "Noto Sans JP", "Noto Sans CJK JP", sans-serif; }
@media (prefers-reduced-motion: no-preference) {
  .lang-menu:not([hidden]) { animation: lang-pop var(--dur) var(--ease-out); transform-origin: top right; }
  @keyframes lang-pop { from { opacity: 0; transform: translateY(-4px) scale(.98); } }
}
/* touch: full 44px rows */
@media (pointer: coarse) { .lang-menu [role="option"] { min-height: 44px; } }

/* phones: the globe and caret go, the code stays, and the Live tag keeps only its dot (the word stays for screen
   readers and as the dot's tooltip) - with the language button beside the theme toggle, "Wave Drop Maps" still
   fits on one line at 360px+ in every language. The menu hangs from the button's right edge and never runs past
   the screen's 16px gutter. */
@media (max-width: 480px) {
  .lang-btn { padding: 0; width: 34px; justify-content: center; }
  .lang-btn .i-globe, .lang-btn .i-caret { display: none; }
  .lang-menu { right: -44px; min-width: 0; width: min(220px, calc(100vw - 32px)); }
  .live { padding: 6px; margin-right: -4px; }
  .live span { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden;
               clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
}
.brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
a.brand { color: inherit; text-decoration: none; border-radius: 8px; }
a.brand:hover .org { color: var(--text, inherit); }
.mark { width: 26px; height: 26px; flex: none; color: var(--strong); }
.org {
  margin: 0; color: var(--dim); font-size: 12px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase; line-height: 1.35;
}
.live { margin: 0; display: inline-flex; align-items: center; gap: 7px; color: var(--faint); font-size: 11px;
        font-weight: 600; letter-spacing: .14em; text-transform: uppercase; white-space: nowrap; }
.live i { width: 6px; height: 6px; border-radius: 50%; background: var(--good); box-shadow: 0 0 0 3px rgba(59, 165, 93, .18); }
.live.stale i { background: var(--faint); box-shadow: none; }

h1 {
  margin: 10px 0 0; color: var(--strong);
  font: 600 clamp(30px, 4.4vw, 44px)/1.08 var(--sans);
  letter-spacing: -.028em; text-wrap: balance;
}
h1 em { font-style: normal; color: var(--dim); font-weight: 400; }
/* one line that tells a first-time visitor what this page is and how to get on it */
.tagline { margin: 12px 0 0; max-width: 62ch; color: var(--dim); font-size: 16px; line-height: 1.5; text-wrap: pretty; }

.facts { display: flex; flex-wrap: wrap; gap: 16px 48px; margin: 24px 0 0; }
.facts div { display: flex; flex-direction: column-reverse; justify-content: flex-end; gap: 5px; }   /* figures line up on top even when a label takes two lines */
.facts dt { color: var(--faint); font-size: 10px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase; }
.facts dd {
  margin: 0; color: var(--text); font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: 19px; font-weight: 600; line-height: 1; letter-spacing: -.02em;
}

/* ---------- nav ---------- */
.navbar { position: sticky; top: 0; z-index: 10; background: var(--nav); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
          border-bottom: 1px solid var(--line); box-shadow: var(--shadow-nav); }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .navbar { background: var(--bg); } }
@media (prefers-reduced-transparency: reduce) { .navbar { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--bg); } }
.nav-in { display: flex; align-items: center; justify-content: space-between; gap: 20px; min-height: 54px; }
.tabs { position: relative; display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; margin-bottom: -1px; }
.tabs::-webkit-scrollbar { display: none; }
.tabs button {
  appearance: none; border: 0; background: none; color: var(--dim); cursor: pointer; white-space: nowrap;
  padding: 0 14px; min-height: 52px; font: 500 14px/1 var(--sans); letter-spacing: -.005em;
  border-bottom: 2px solid transparent;      /* kept transparent, for layout only - colour comes from .tab-slide */
  transition: color var(--dur-fast) linear, transform var(--dur-slow) var(--ease-spring);
}
.tabs button:hover { color: var(--text); }
.tabs button:focus-visible { outline-offset: -4px; }
.tabs button[aria-selected="true"] { color: var(--strong); }
.tabs button:active { transform: scale(.965); transition: transform 80ms var(--ease-out); }
/* MOTION SYSTEM: the sliding tab indicator - a single 2px bar whose position/width are measured in JS from the
   active tab's own rect (select() in app.js) and only ever move by CSS transition, which is what makes it slide
   between tabs instead of jumping. Placed instantly (no transition) on first paint via .no-slide-anim. */
.tab-slide {
  position: absolute; left: 0; bottom: -1px; height: 2px; border-radius: 2px; background: var(--accent);
  width: var(--pill-w, 0px); transform: translateX(var(--pill-x, 0px)); pointer-events: none;
  transition: transform var(--dur-slow) var(--ease-spring), width var(--dur) var(--ease-out);
}
.tabs.no-slide-anim .tab-slide { transition: none; }

.search { flex: 0 1 260px; }
/* -webkit-appearance: iOS Safari otherwise draws its own rounded, inset search field over this one */
.search input::-webkit-search-decoration { -webkit-appearance: none; }
.search input {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 40px; padding: 0 12px; color: var(--text); font: 400 14px var(--sans);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  transition: border-color var(--dur-fast) linear;
}
.search input:hover { border-color: var(--line-2); }
:root[data-theme="light"] .search input { background: var(--surface); }
.search input::placeholder { color: var(--faint); }
.search input:focus { border-color: var(--line-2); outline-offset: 0; }

/* ---------- podium (top 3) ----------
   One lit strip split into three places, like a scoreboard: medal disc, avatar (1st wears the crown), name, rank
   badge and the gap to the place above, and the figure in its medal colour. Light from the top-left (the motion
   system's lit surface: hairline highlight on the top edge, soft cast shadow), 1st gets a faint gold wash. */
.podium {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 24px 0 32px; overflow: hidden;
  background: linear-gradient(180deg, var(--surface-2), var(--surface) 70%);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-card), inset 0 1px 0 rgba(255, 255, 255, .05), 0 14px 30px -22px rgba(0, 0, 0, .55);
}
.pod {
  position: relative; min-width: 0; display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto;
  align-items: center; gap: 12px; padding: 16px 20px;
  transition: background-color var(--dur-fast) linear;
}
.pod + .pod { border-left: 1px solid var(--line); }
.pod.m1 { background: linear-gradient(100deg, rgba(232, 185, 63, .09), rgba(232, 185, 63, 0) 70%); }
:root[data-theme="light"] .pod.m1 { background: linear-gradient(100deg, rgba(184, 134, 11, .08), rgba(184, 134, 11, 0) 70%); }
@media (hover: hover) and (pointer: fine) { .pod:hover { background-color: var(--surface-3); } }
.pod .medal {
  width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center; flex: none;
  font: 700 11px/1 var(--mono); color: var(--medal); border: 1.5px solid var(--medal);
  background: rgba(127, 127, 127, .08);
}
:root[data-theme="light"] .pod .medal { background: var(--surface); }   /* the digit is text: keep it on white */
.pod .avwrap { position: relative; flex: none; }
.pod .av { width: 44px; height: 44px; font-size: 16px; box-shadow: 0 0 0 2px var(--surface), 0 0 0 3.5px var(--medal); }
.pod .crown {
  position: absolute; left: 50%; top: -15px; width: 22px; height: 18px; color: var(--crown);
  transform: translateX(-50%) rotate(-10deg); filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .45));
}
.pod .who-p { min-width: 0; }
/* a long name gets two lines before it is cut, instead of "Use Code Wa..." on a phone */
.pod .name { color: var(--strong); font-size: 15px; font-weight: 600; letter-spacing: -.01em; line-height: 1.25;
             overflow: hidden; overflow-wrap: break-word;   /* split a word only if it cannot fit a line alone */
             display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; }
.pod .meta { display: flex; align-items: center; flex-wrap: wrap; gap: 5px 9px; margin-top: 6px; min-width: 0; }
.pod .gap { color: var(--dim); font: 500 11px/1.2 var(--mono); letter-spacing: .07em; text-transform: uppercase;
            white-space: nowrap; }
.pod .fig { text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.pod .big { color: var(--medal); font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 28px;
            font-weight: 600; line-height: 1; letter-spacing: -.03em; }
.pod .sub { color: var(--dim); font: 500 11px/1 var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* mid-width: three places no longer fit side by side with room for a badge - stack them as rows of one board */
@media (max-width: 980px) {
  .podium { grid-template-columns: 1fr; }
  .pod + .pod { border-left: 0; border-top: 1px solid var(--line); }
  .pod { padding: 14px 16px; }
}
@media (max-width: 420px) {
  .pod { gap: 10px; padding: 14px 12px; }
  .pod .av { width: 38px; height: 38px; }
  .pod .big { font-size: 24px; }
  /* badge and gap line always stacked, so all three places share one rhythm instead of wrapping unevenly */
  .pod .meta { flex-direction: column; align-items: flex-start; gap: 6px; }
  /* a long gap line in German or Polish ("15 poziomów do 1. miejsca") takes a second line instead of spilling */
  .pod .gap { white-space: normal; line-height: 1.35; }
}

/* ---------- table ---------- */
.thead, .row { display: grid; grid-template-columns: var(--row); align-items: center; gap: 14px; }
.thead {
  padding: 0 20px 12px; color: var(--faint);
  font-size: 11px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
}
.thead .ic { position: relative; display: inline-flex; justify-content: flex-end; }
.thead .ic button {
  appearance: none; background: none; border: 0; padding: 4px; margin: -4px -4px -4px 0; cursor: pointer;
  color: inherit; display: inline-flex; border-radius: 6px; transition: color .12s;
  font: inherit; letter-spacing: inherit; text-transform: inherit;
}
/* a dotted underline: the familiar "this term is explained" cue */
.thead .ic-l { text-decoration: underline dotted; text-decoration-thickness: 1px; text-underline-offset: 4px;
               margin-right: -.14em; }             /* cancel the trailing letter-space so the label sits flush right */
.thead .ic button:hover, .thead .ic.open button { color: var(--text); }

/* The explanation popover. Shown on hover (mouse), on focus (keyboard) and on tap (touch, via the .open class).
   It is purely informational, so it never takes the pointer — that also stops it flickering as the cursor moves. */
.tip {
  position: absolute; top: calc(100% + 9px); right: -6px; z-index: 12; width: max-content; max-width: 268px;
  display: none; pointer-events: none; padding: 10px 13px; text-align: left;
  background: var(--surface-2); border: 1px solid var(--line-2); border-radius: var(--radius);
  box-shadow: var(--shadow-pop);
  font-size: 13px; font-weight: 400; letter-spacing: 0; text-transform: none; line-height: 1.45; color: var(--dim);
}
:root[data-theme="light"] .tip, :root[data-theme="light"] .rtip { background: var(--surface); }
.ic.open .tip, .ic button:focus-visible + .tip { display: block; }
/* hover only on devices that really have a pointer: on a phone :hover sticks after a tap */
@media (hover: hover) and (pointer: fine) {
  .ic:hover .tip { display: block; }
  .ic:hover button { color: var(--text); }
}
.ic.dismissed .tip { display: none !important; }       /* Escape hides it even while hovered or focused */
.tip b { display: block; margin-bottom: 3px; color: var(--text); font-size: 13px; font-weight: 600; }
.tip::before {
  content: ""; position: absolute; bottom: 100%; right: 12px;
  border: 6px solid transparent; border-bottom-color: var(--line-2);
}
.list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.row {
  --c: var(--line-2);
  position: relative; overflow: hidden; padding: 12px 20px; min-height: 68px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  transition: border-color var(--dur-fast) linear, box-shadow var(--dur-fast) linear,
              transform var(--dur-slow) var(--ease-spring);
}
.row::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--c-art, var(--c)); opacity: 0; }
.row[data-tier]::before, .row[style*="--c"]::before { opacity: .9; }
.row:hover { border-color: var(--line-2); }
:root[data-theme="light"] .row:hover { box-shadow: 0 1px 2px rgba(20,22,27,.04), 0 14px 28px -16px rgba(20,22,27,.2); }
/* rows and cards "press" - a small, springy scale-down/up, same feel as a button */
.row:active, .step:active, .season-card:active { transform: scale(.985); transition: transform 80ms var(--ease-out); }

.pos { text-align: right; color: var(--dim); font-family: var(--mono); font-variant-numeric: tabular-nums;
       font-size: 15px; font-weight: 500; }
.row.m1 .pos { color: var(--medal-1); font-weight: 700; }
.row.m2 .pos { color: var(--medal-2); font-weight: 700; }
.row.m3 .pos { color: var(--medal-3); font-weight: 700; }

.who { min-width: 0; }
.who b { display: block; color: var(--text); font-size: 15px; font-weight: 500; letter-spacing: -.008em;
         overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who .sub { display: none; }
.msgs { text-align: right; color: var(--dim); font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 13px; }

.tier { color: var(--c, var(--faint)); font-size: 12px; font-weight: 600; letter-spacing: .04em; }
.tier.none { color: var(--faint); font-family: var(--mono); font-weight: 400; }

/* Discord-style role pill: dot + name in the role's own colour, on a wash of the same colour */
.badge {
  display: inline-flex; align-items: center; gap: 7px; max-width: 100%;
  padding: 4px 11px 4px 8px; border-radius: 999px;
  color: var(--c, var(--dim));
  background: color-mix(in srgb, var(--c, var(--dim)) 11%, transparent);
  border: 1px solid color-mix(in srgb, var(--c, var(--dim)) 26%, transparent);
  font-size: 13px; font-weight: 600; letter-spacing: -.005em; line-height: 1.35;
}
:root[data-theme="light"] .badge {
  background: color-mix(in srgb, var(--c, var(--dim)) 15%, transparent);
  border-color: color-mix(in srgb, var(--c, var(--dim)) 36%, transparent);
}
.badge .dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: none; }
/* the role's real Discord icon, sized to sit exactly where the dot would. `flex: none` so a slow-loading icon
   never squashes the label next to it. */
.badge .ricon { width: 14px; height: 14px; border-radius: 3px; object-fit: contain; flex: none; }
.badge span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* CONSISTENT BADGES: a rank is the same pill everywhere it appears - podium, table rows, both Ranks ladders, the
   Invites "Next" column - never a stripped-down "dot + text" in one place and a full pill in another. Rows get a
   slightly smaller pill purely because the row itself is denser, not a different component. */
.row .badge, .row .pill { font-size: 12px; padding: 3px 10px 3px 7px; }
.row .badge .dot { width: 7px; height: 7px; }
.row .badge .ricon { width: 13px; height: 13px; }
/* every badge/pill is also a tooltip trigger (see app.js attachRankTip): cursor says so, and it never loses its
   focus ring just because it is a <span> rather than a <button> */
.badge, .pill { cursor: default; }
.badge:focus-visible, .pill:focus-visible { outline: 2px solid var(--accent-lift); outline-offset: 2px; }

/* ---------- invites: current rank + a real "next rank" progress treatment ----------
   The Invites list has its own grid (scoped to #panel-invites, so it never affects the Levels list): current rank,
   then a next-rank cell showing a filling bar plus the next rank's own role badge, or a clear "Top rank" state
   once there is no next rank left to reach. */
#panel-invites .thead, #panel-invites .row {
  grid-template-columns: 56px 44px minmax(0, 1fr) 148px 232px 78px;
}
#panel-invites .cur { min-width: 0; display: flex; align-items: center; }
#panel-invites .row > .lvl { text-align: right; }
/* the count's header sits flush right over its numbers, like every other figure column; a long word ("Einladungen",
   "Zaproszenia") then runs left into the empty header space beside it instead of past the page edge (a flex box
   overflows on the start side when its content is pushed to the end) */
#panel-invites .thead .n { display: flex; justify-content: flex-end; white-space: nowrap; margin-right: -.14em; }

.next-wrap { min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.next-label { display: flex; align-items: center; gap: 8px; min-width: 0; }
.next-tag {
  flex: none; color: var(--faint); font-size: 10px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
}
.next-bar-row { display: flex; align-items: center; gap: 8px; }
.next-bar-row .bar { flex: 1; }
/* the fill matches the pill it is heading for: a role's own colour (set inline by app.js), otherwise the gold of a
   reward pill - never the row's grey edge colour, which it would otherwise inherit and all but vanish into */
.next-bar-row .bar i { --c: var(--reward); --c-art: initial; }
.next-frac {
  flex: none; min-width: 46px; text-align: right; color: var(--dim);
  font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 12px;
}
.top-rank {
  display: inline-flex; align-items: center; gap: 7px; padding: 5px 12px; border-radius: 999px;
  color: var(--good); background: color-mix(in srgb, var(--good) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--good) 38%, transparent);
  font-size: 12px; font-weight: 700; letter-spacing: .03em;
}
.top-rank svg { width: 13px; height: 13px; }

/* the compact copy shown stacked under the member name on narrow screens */
.next-wrap.compact { gap: 4px; }
.next-wrap.compact .next-frac { font-size: 11px; }

@media (max-width: 1080px) {
  #panel-invites .thead, #panel-invites .row { grid-template-columns: 50px 40px minmax(0, 1fr) 126px 196px 66px; }
}
@media (max-width: 880px) {
  #panel-invites .thead, #panel-invites .row { grid-template-columns: 46px 36px minmax(0, 1fr) 190px 58px; }
  #panel-invites .thead .cur, #panel-invites .row .cur { display: none; }
  /* the Rank column is gone at this width: the member's rank moves under their name instead of disappearing */
  #panel-invites .who .sub { display: flex; margin-top: 4px; }
  #panel-invites .who .sub .next-wrap { display: none; }
}
@media (max-width: 620px) {
  #panel-invites .thead, #panel-invites .row { grid-template-columns: 28px 40px minmax(0, 1fr) 52px; }
  #panel-invites .thead .cur, #panel-invites .row .cur,
  #panel-invites .thead .next, #panel-invites .row > .next-wrap:not(.compact) { display: none; }
  #panel-invites .who .sub { flex-direction: column; align-items: flex-start; gap: 8px; }
  #panel-invites .who .sub .next-wrap { display: flex; }
  #panel-invites .who .sub .next-wrap.compact { width: 100%; max-width: 100%; }
  #panel-invites .who .sub .next-label { max-width: 100%; }
}

.prog { display: flex; align-items: center; gap: 10px; }
.bar { flex: 1; height: 3px; background: var(--line); border-radius: 2px; overflow: hidden; }
.bar i {
  display: block; height: 100%; width: var(--w, 0%); background: var(--c-art, var(--c, var(--accent))); border-radius: 2px;
  transform-origin: left; /* the fill keyframes scale this in from 0, the real width (--w) is already correct */
}
.prog .pct { color: var(--faint); font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 12px; min-width: 32px; text-align: right; }

.xp { text-align: right; color: var(--dim); font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 13px; white-space: nowrap; }
.lvl { text-align: right; color: var(--strong); font-family: var(--mono); font-variant-numeric: tabular-nums;
       font-size: 17px; font-weight: 600; letter-spacing: -.02em; white-space: nowrap; }
.lvl.long { font-size: 14px; }

/* avatar */
.av { width: 40px; height: 40px; border-radius: 50%; flex: none; display: grid; place-items: center; overflow: hidden;
      background: var(--surface-3); color: var(--dim); font-size: 15px; font-weight: 600; }
.av img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* tier colours (keys come from the role names) */
[data-tier="bronze"]   { --c: var(--t-bronze); }
[data-tier="silver"]   { --c: var(--t-silver); }
[data-tier="gold"]     { --c: var(--t-gold); }
[data-tier="platinum"] { --c: var(--t-platinum); }
[data-tier="diamond"]  { --c: var(--t-diamond); }
[data-tier="elite"]    { --c: var(--t-elite); }
[data-tier="champion"] { --c: var(--t-champion); }
[data-tier="unreal"]   { --c: var(--t-unreal); }
/* medal (position) is a separate channel from tier (role), so the two never overwrite each other */
.m1 { --medal: var(--medal-1); } .m2 { --medal: var(--medal-2); } .m3 { --medal: var(--medal-3); }

/* ---------- ranks ladder ---------- */
/* the Levels/Invites switcher above the ladder: a real tablist, keyboard-usable (arrow keys move + activate) */
.seg {
  position: relative; display: inline-flex; gap: 2px; padding: 3px; margin: 24px 0 0;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 11px;
}
/* MOTION SYSTEM: the sliding pill - app.js measures the active button's rect relative to .seg and sets
   --pill-x/--pill-w on .seg itself (custom properties inherit down to this pseudo-element). The buttons sit at
   z-index 1, above the pill, so their own background stays transparent - only this one surface ever moves. */
.seg::before {
  content: ""; position: absolute; top: 3px; bottom: 3px; left: 0; border-radius: var(--radius-sm);
  width: var(--pill-w, 0px); transform: translateX(var(--pill-x, 0px));
  background: var(--surface); box-shadow: 0 1px 3px rgba(0, 0, 0, .14);
  transition: transform var(--dur-slow) var(--ease-spring), width var(--dur) var(--ease-out);
}
:root[data-theme="light"] .seg::before { box-shadow: 0 1px 2px rgba(20, 22, 27, .08); }
.seg.no-slide-anim::before { transition: none; }
.seg-btn {
  position: relative; z-index: 1; appearance: none; border: 0; background: none; cursor: pointer; color: var(--dim);
  padding: 0 20px; min-height: 44px; border-radius: var(--radius-sm); font: 600 14px var(--sans); letter-spacing: -.005em;
  transition: color var(--dur-fast) linear, transform var(--dur-slow) var(--ease-spring);
}
.seg-btn:hover { color: var(--text); }
.seg-btn:focus-visible { outline-offset: -2px; }
.seg-btn[aria-selected="true"] { color: var(--strong); }
.seg-btn:active { transform: scale(.965); transition: transform 80ms var(--ease-out); }

h2 { margin: 32px 0 4px; color: var(--strong); font: 600 19px/1.2 var(--sans); letter-spacing: -.015em; }
.lede { margin: 0 0 16px; color: var(--dim); font-size: 14px; }
.ladder {
  list-style: none; margin: 0; padding: 0; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-card);
}
/* every row uses the SAME fixed-width threshold column, so the badge and description that follow always start at
   the same x regardless of whether the label is "5" or "70 invites" - that fixed edge is what was missing before
   and made the ladder feel like it was zig-zagging.
   The divider between rows is a real border, not a background peeking through a grid gap: the gap trick rounded
   away on some row-height combinations (a two-line description vs. one line) and silently dropped a divider. A
   border on every row but the first is the same 1px everywhere regardless of what that row contains. */
.step {
  display: grid; grid-template-columns: var(--at-w, 76px) minmax(0, 1fr) auto; align-items: center; gap: 16px;
  min-height: 80px; padding: 16px 20px; background: var(--surface);
}
.step + .step { border-top: 1px solid var(--line); }
.step-at {
  flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 2px;
}
.at-num {
  color: var(--strong); font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-size: 22px; font-weight: 800; line-height: 1; letter-spacing: -.02em;
}
.at-unit { color: var(--faint); font-size: 10px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }

.step-mid { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.step-mid .note { margin: 0; color: var(--dim); font-size: 13px; line-height: 1.45; }

.step-pills { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.pill {
  display: inline-flex; align-items: center; white-space: nowrap; max-width: 100%;
  padding: 5px 11px; border-radius: 999px; font-size: 11px; font-weight: 700; letter-spacing: .03em;
}
.pill.claim {
  color: var(--accent-lift); background: var(--accent-wash);
  border: 1px solid color-mix(in srgb, var(--accent) 38%, transparent);
}
/* a product reward (a free route, etc.) has no Discord role and so no role colour - a plain neutral pill instead
   of a coloured badge, so it never looks like it is impersonating an actual server role */
.pill.product { color: var(--reward); background: var(--reward-bg); border: 1px solid var(--reward-line); }
/* ---------- history ---------- */
.season-card { padding: 20px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: 8px; box-shadow: var(--shadow-card); }
.season-card h3 { margin: 0 0 14px; color: var(--strong); font: 600 15px/1 var(--sans); letter-spacing: -.01em; }
.winners { display: grid; gap: 8px; }
.win { display: grid; grid-template-columns: 22px 30px minmax(0, 1fr) auto; align-items: center; gap: 12px; }
.win-empty { margin: 0; color: var(--dim); font-size: 13px; }
.win .p { color: var(--faint); font-family: var(--mono); font-size: 12px; }
.win .av { width: 30px; height: 30px; font-size: 12px; }
.win b { color: var(--text); font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.win .s { color: var(--dim); font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 12px; white-space: nowrap; }

/* ---------- pages (app.js pager) ----------
   Previous · 1 … 6 7 8 … 23 · Next above and below each board, and "601–700 of 2,296" beside it. Every control is
   the masthead's button (the theme and language buttons: raised surface, hairline border, the same lift and press);
   the page you are on is filled with the accent, like Join. Real links, so a page can be opened in a new tab. */
.pager { display: flex; flex-direction: row-reverse; align-items: center; justify-content: space-between;
         flex-wrap: wrap; gap: 10px 16px; margin: 0 0 16px; }
.pager.first { margin-top: 24px; }
.list + .pager { margin: 16px 0 0; }
.pager-in { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
.pager-sum { margin: 0; color: var(--faint); font: 500 12px/1.3 var(--mono); font-variant-numeric: tabular-nums; }
.pg {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 36px; height: 36px;
  padding: 0 10px; cursor: pointer; color: var(--dim); text-decoration: none; white-space: nowrap;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  font: 600 13px/1 var(--mono); font-variant-numeric: tabular-nums;
  transition: transform var(--dur-slow) var(--ease-spring), color var(--dur-fast) linear,
              border-color var(--dur-fast) linear, box-shadow var(--dur-fast) linear;
}
.pg.pg-arrow { font-family: var(--sans); letter-spacing: -.005em; padding: 0 12px 0 10px; }
.pg.next { padding: 0 10px 0 12px; }
@media (hover: hover) and (pointer: fine) {
  .pg[href]:not([aria-current]):hover { color: var(--text); border-color: var(--line-2); transform: translateY(-2px);
                                        box-shadow: 0 6px 14px -6px rgba(0, 0, 0, .35); }
}
.pg[href]:not([aria-current]):active { transform: scale(.965); transition: transform 80ms var(--ease-out); }
.pg[aria-current="page"] { color: #fff; background: var(--accent); border-color: var(--accent); cursor: default; }
.pg[aria-disabled="true"] { opacity: .45; cursor: default; }
.pg .i-caret { width: 10px; height: 10px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.6;
               stroke-linecap: round; stroke-linejoin: round; }
.pg .i-caret.left { transform: rotate(90deg); }
.pg .i-caret.right { transform: rotate(-90deg); }
.pg-gap { min-width: 14px; text-align: center; color: var(--faint); font: 600 13px/1 var(--mono); }
@media (pointer: coarse) { .pg { height: 44px; min-width: 38px; padding: 0 8px; } }
/* phones: the pager centred with the count under it, Previous / Next as the arrow alone (the word stays for screen
   readers), so first, last and the pages either side of this one fit on one line from 360px up */
@media (max-width: 620px) {
  .pager { flex-direction: column; justify-content: center; gap: 8px; }
  .pager-in { justify-content: center; }
  .pg.pg-arrow { padding: 0; min-width: 38px; }
  .pg .pg-l { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden;
              clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
}
/* no podium above the column header (a later page, or a search): the podium's own spacing instead */
.thead.first { margin-top: 24px; }
/* search results come from every page: the whole row opens that member's page, and the name is its button */
.row.found { cursor: pointer; }
.who b .jump {
  appearance: none; display: block; max-width: 100%; margin: 0; padding: 0; border: 0; background: none; cursor: pointer;
  color: inherit; font: inherit; letter-spacing: inherit; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (hover: hover) and (pointer: fine) { .row.found:hover .jump { text-decoration: underline; text-underline-offset: 3px; } }
/* the member a search just jumped to */
.row.hit { border-color: var(--accent-lift); box-shadow: var(--shadow-card), 0 0 0 1px var(--accent-lift); }
.row.hit:focus { outline: none; }
.pager-note { margin: 16px 0 0; text-align: center; color: var(--faint); font-size: 13px; }

/* ONE empty / notice / error state for the whole page: a quiet dashed panel with a title line and a hint */
.empty {
  margin: 24px 0 0; padding: 40px 24px; text-align: center;
  border: 1px dashed var(--line-2); border-radius: var(--radius); background: var(--surface);
}
.empty-t { margin: 0; color: var(--text); font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
.empty-h { margin: 6px auto 0; max-width: 46ch; color: var(--dim); font-size: 14px; }
.past-title + .empty { margin-top: 0; }
.empty .retry {
  margin-top: 16px; min-height: 40px; padding: 0 18px; cursor: pointer;
  color: #fff; background: var(--accent); border: 0; border-radius: var(--radius-sm);
  font: 600 14px var(--sans); transition: background-color var(--dur-fast) linear, transform var(--dur-slow) var(--ease-spring);
}
.empty a.retry { display: inline-flex; align-items: center; text-decoration: none; }
.empty .retry:hover { background: #4752c4; }           /* darker on hover: white text stays >= 4.5:1 */
:root[data-theme="light"] .empty .retry:hover { background: #3742b8; }
.empty .retry:active { transform: scale(.965); transition: transform 80ms var(--ease-out); }
.empty .retry:disabled { opacity: .7; cursor: progress; }
.foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px 24px;
        margin-top: 40px; padding: 24px 0 48px; border-top: 1px solid var(--line); color: var(--faint); font-size: 12px; }
.foot p { margin: 0; }
.foot-meta { flex-basis: 100%; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 6px 24px;
             padding-top: 14px; border-top: 1px solid var(--line, rgba(127,127,127,.18)); color: var(--faint); font-size: 13px; }
.foot-meta a { color: var(--dim); text-underline-offset: 2px; }
.foot-meta a:hover { color: var(--text); }

/* ---------- entrances ---------- */
/* MOTION SYSTEM: a panel fades in (opacity 0 + translateY(7px), 240ms ease-out) every time it genuinely becomes
   visible - app.js restarts this class on the panel select() switches to. Row/card staggers are capped at 30ms
   per item and 5 items (150ms max) so a 100-row list does not end up with its last row entering half a second
   late; the count-up on the big podium figures and the progress-bar fill are the other two "it just appeared"
   moments. All of it is decorative rather than functional, so all of it is skipped outright under reduced motion
   (the sliding indicators and the button/row press below are the only motion that survives reduced motion, and
   they simply run shorter and without a spring). */
@media (prefers-reduced-motion: no-preference) {
  .fade-in { animation: fade-in var(--dur) var(--ease-out) both; }
  @keyframes fade-in { from { opacity: 0; transform: translateY(7px); } }
  .rise { animation: rise var(--dur) var(--ease-out) both; animation-delay: calc(min(var(--i, 0), 5) * 30ms); }
  @keyframes rise { from { opacity: 0; transform: translateY(5px); } }
  .bar i { animation: fill 1000ms var(--ease-out) both; animation-delay: 160ms; }
  @keyframes fill { from { transform: scaleX(0); } }
  /* after the first paint has played, a refresh or theme toggle must not replay row entrances or bar fills */
  .settled .rise, .settled .bar i { animation: none; }
}

/* ---------- responsive ---------- */
@media (max-width: 1080px) {
  :root { --row: 50px 40px minmax(0, 1fr) 122px 78px 86px 60px; }
  .thead .pr, .row .prog { display: none; }
}
@media (max-width: 880px) {
  :root { --row: 50px 40px minmax(0, 1fr) 122px 78px 60px; }
  .thead .mc, .row .msgs { display: none; }
}
@media (max-width: 620px) {
  .wrap { width: calc(100% - 32px); }
  :root { --row: 28px 40px minmax(0, 1fr) 52px; }
  .masthead { padding: 24px 0 18px; }
  .facts { gap: 8px 22px; }
  .thead .tr, .thead .x, .row > .tier, .row > .xp, .row > .badge { display: none; }
  .row { min-height: 66px; padding: 12px 14px; gap: 11px; }
  .pos.long { font-size: 12px; letter-spacing: -.04em; }   /* #1,101: four digits in the 28px column */
  .who .sub { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 4px; }
  .who .sub .tier { font-size: 11px; }
  .who .sub .xp { display: block; color: var(--faint); font-size: 12px; }
  .nav-in { flex-wrap: wrap; padding-bottom: 8px; }
  .search { flex-basis: 100%; }
  .search input { height: 44px; font-size: 16px; }   /* full touch target; 16px stops iOS zooming the page on focus */
  /* the ladder stacks cleanly into a single column: threshold on its own line, then the badge and description,
     then any pills - instead of squeezing the fixed-width grid into a column too narrow for it */
  .step { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; min-height: 0; padding: 16px; }
  .step-at { flex-direction: row; align-items: baseline; gap: 6px; }
  .step-at.k-level { flex-direction: row-reverse; }      /* reads "LEVEL 5", not "5 LEVEL" */
  .step-mid { width: 100%; }
  .step-pills { flex-direction: row; flex-wrap: wrap; align-items: flex-start; width: 100%; }
}


/* ---- rank / reward tooltips (app.js attachRankTip) -----------------------------------------------------------
   role=tooltip, positioned by JS (collision-aware: flips above/below, clamps horizontally). It never uses
   pointer-events so it can never trap a click, and it is pure information - nothing here is interactive. */
.rtip {
  position: fixed; z-index: 60; max-width: 260px; max-width: min(260px, calc(100vw - 24px)); padding: 10px 13px;
  background: var(--surface-2); border: 1px solid var(--line-2); border-radius: var(--radius);
  box-shadow: var(--shadow-pop); pointer-events: none;
}
.rtip.hoverable { pointer-events: auto; }          /* shown by a mouse: the pointer may move onto it */
.rtip-t { display: block; color: var(--text); font-size: 13px; font-weight: 700; letter-spacing: -.005em; }
.rtip-m { display: block; margin-top: 2px; color: var(--faint); font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }
.rtip-b { margin: 6px 0 0; color: var(--dim); font-size: 13px; line-height: 1.45; }
.rtip::after {
  content: ""; position: absolute; left: var(--arrow-x, 20px); width: 10px; height: 10px;
  background: inherit; transform: translateX(-50%) rotate(45deg);
}
.rtip:not(.flip)::after { bottom: -5px; border-right: 1px solid var(--line-2); border-bottom: 1px solid var(--line-2); }
.rtip.flip::after { top: -5px; border-left: 1px solid var(--line-2); border-top: 1px solid var(--line-2); }

/* ---- robustness: "works on every device and every setting" ---------------------------------------------------- */

/* touch: a badge/pill is visually compact but is also a tooltip trigger on tap - give it a real >=44px hit area
   without inflating how it looks, by extending an invisible tap zone around it instead of the pill itself */
@media (hover: none) {
  .badge, .pill { position: relative; }
  .badge::after, .pill::after { content: ""; position: absolute; inset: -11px; }
}

/* reduced motion: the sliding indicators and the button/row press are FUNCTIONAL feedback and stay, just shorter
   and without the spring's overshoot; every other rule in this file already gates its motion behind
   prefers-reduced-motion: no-preference and simply does not run here. */
@media (prefers-reduced-motion: reduce) {
  .tab-slide, .seg::before,
  .theme, .seg-btn, .tabs button, .pg,
  .row, .pod, .step, .season-card, .trophy {
    transition-duration: 150ms !important;
    transition-timing-function: var(--ease-out) !important;
  }
}

/* forced-colors (Windows High Contrast): the SYSTEM palette owns every colour. Nothing opts out wholesale
   (forced-color-adjust inherits, so opting out a container would smuggle the author colours of everything inside
   it past the user's theme); only the few bars that must be painted opt out, and they use system colours too.
   Surfaces whose edge was only a background get a real border. */
@media (forced-colors: active) {
  .badge, .pill, .row, .step, .season-card, .ladder, .podium, .seg, .bar, .rtip, .tip, .trophy, .cabinet,
  .top-rank, .empty, .skel-pod, .skel-row, .sb-rail, .pg { border: 1px solid CanvasText; }
  .pg[aria-current="page"] { forced-color-adjust: none; background: Highlight; color: HighlightText; border-color: Highlight; }
  .row.hit { outline: 2px solid Highlight; }
  .pod.m1, :root[data-theme="light"] .pod.m1, .cabinet { background-image: none; }
  /* the rank edge keeps its role colour: it is meaning, not decoration (the badge text says the same) */
  .row[data-tier]::before, .row[style*="--c"]::before { forced-color-adjust: none; opacity: 1; }
  .row:not([data-tier]):not([style*="--c"])::before { display: none; }
  .lang-hint { border-bottom: 1px solid CanvasText; }
  /* the Live dot is only a background: keep it, in system colours (on phones it stands alone, without the word) */
  .live i { forced-color-adjust: none; background: CanvasText; box-shadow: none; }
  .live.stale i { background: GrayText; }
  .lang-hint-go { border: 1px solid ButtonText; }
  .tabs button { border-bottom-color: Canvas; }              /* the 2px layout border must not draw a line */
  .badge .dot, .bar i, .sb-rail i, .tab-slide, .sb-spark { forced-color-adjust: none; background: Highlight; }
  .badge .dot { background: CanvasText; }
  .sb-rail i::after { display: none; }
  .seg::before { forced-color-adjust: none; background: Canvas; border: 2px solid Highlight; box-shadow: none; }
  .tabs button[aria-selected="true"], .seg-btn[aria-selected="true"] { color: Highlight; }
  .empty .retry { border: 1px solid ButtonText; }
  .join { border: 1px solid LinkText; }             /* its fill is dropped: keep it looking like a button */
  .lang-menu { border: 1px solid CanvasText; }
  .lang-menu .is-active { forced-color-adjust: none; background: Highlight; color: HighlightText; }
  .lang-menu .tick { stroke: CanvasText; }
  :focus-visible { outline: 2px solid Highlight !important; }
}

/* prefers-contrast: more - stronger borders and no low-contrast "faint" text standing in for meaning */
@media (prefers-contrast: more) {
  :root { --line: var(--line-2); }
  .badge, .pill, .row, .pod, .step, .season-card, .ladder, .search input, .seg {
    border-width: 1.5px;
  }
  .tier.none { color: var(--dim); }
}

/* prefers-reduced-transparency: no blur/glass anywhere - the sticky nav becomes a plain solid surface */
@media (prefers-reduced-transparency: reduce) {
  .navbar { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--bg); }
}

/* very old engines: color-mix() has no fallback value syntax, so give the handful of components that lean on it
   hardest (badges, the podium's tinted wash) a flat, still-correct colour when the function itself is unsupported */
@supports not (color: color-mix(in srgb, red, blue)) {
  .badge { background: var(--surface-2); border-color: var(--line-2); }
  :root[data-theme="light"] .badge { background: var(--surface-3); }
  .pill.product { background: var(--surface-2); border-color: var(--line-2); }
}

/* ---------- small phones ----------
   All four tabs fit from ~360px up; below that the row scrolls and a fade on the right edge says so (.more is set
   by app.js only while there is something off-screen to the right). The three season facts stay on one line. */
@media (max-width: 420px) {
  .tabs button { padding: 0 8px; font-size: 13px; }
  .thead { letter-spacing: .07em; }          /* room for a long column word beside "Member" (German, Polish, Spanish) */
  .facts { display: grid; grid-template-columns: repeat(2, auto); justify-content: space-between; gap: 12px 12px; }
  .facts dt { letter-spacing: .1em; font-size: 10px; }
  .org { font-size: 11px; letter-spacing: .1em; }
  .brand { gap: 8px; }
  .mark { width: 22px; height: 22px; }
  .tagline { font-size: 15px; }
}
.tabs.more { -webkit-mask-image: linear-gradient(90deg, #000 82%, transparent);
             mask-image: linear-gradient(90deg, #000 82%, transparent); }

/* ---------- season bar ----------
   One plain progress bar and the end date - nothing else. */
.season-bar { margin-top: 24px; }
.sb-rail { position: relative; height: 6px; border-radius: 999px; background: var(--surface-3); }
.sb-rail i { position: relative; display: block; height: 100%; width: var(--w, 0%); border-radius: inherit;
              overflow: hidden; transform-origin: left;
              background: linear-gradient(90deg, var(--accent), var(--accent-lift));
              box-shadow: 0 0 12px -2px var(--accent); }
/* a light sweeping along the fill now and then: the season is moving */
.sb-rail i::after { content: ""; position: absolute; inset: 0; width: 40%;
                     background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .45), transparent);
                     transform: translateX(-120%); }
/* the spark at the leading edge: a four-point glint where "now" is */
.sb-spark { position: absolute; top: 50%; left: var(--w, 0%); width: 16px; height: 16px; margin: -8px 0 0 -8px;
            pointer-events: none; background: var(--spark);
            clip-path: polygon(50% 0, 60% 40%, 100% 50%, 60% 60%, 50% 100%, 40% 60%, 0 50%, 40% 40%);
            filter: drop-shadow(0 0 4px var(--spark-glow)) drop-shadow(0 0 8px var(--accent)); }
@media (prefers-reduced-motion: no-preference) {
  .sb-rail i::after { animation: sb-sweep 3.2s var(--ease-inout) 1.2s infinite; }
  .sb-spark { animation: sb-twinkle 2.4s ease-in-out infinite; }
}
@keyframes sb-sweep { 0% { transform: translateX(-120%); } 55%, 100% { transform: translateX(260%); } }
@keyframes sb-twinkle { 0%, 100% { transform: scale(.8) rotate(0deg); opacity: .85; }
                        50% { transform: scale(1.15) rotate(45deg); opacity: 1; } }
.sb-end { min-height: 1.3em; margin-top: 8px; text-align: right; color: var(--faint); font: 500 12px/1.3 var(--mono); }
@media (prefers-reduced-motion: no-preference) { .sb-rail i { animation: sb-grow 900ms var(--ease-out) both; } }
@keyframes sb-grow { from { transform: scaleX(0); } }
/* perks: separate items with a divider; channel names are links */
.perks .perk-sep { display: inline-block; margin: 0 9px; color: var(--line-2); font-weight: 400; }
.perks .chan { color: var(--accent-lift, var(--accent)); text-decoration: none; font-weight: 600;
               border-radius: 4px; padding: 0 3px; background: var(--accent-wash); }
.perks .chan:hover { text-decoration: underline; text-underline-offset: 2px; }
/* a badge placed straight in a table row must hug its text, not stretch across the column */
.row > .badge { justify-self: start; }

/* ---------- trophy cabinet (History) ---------- */
.cabinet { margin: 24px 0 32px; padding: 24px; border-radius: var(--radius); border: 1px solid var(--line);
           background: linear-gradient(180deg, rgba(232, 185, 63, .07), transparent 70%), var(--surface);
           box-shadow: var(--shadow-card), inset 0 1px 0 rgba(255, 255, 255, .05); }
.cabinet-head h2 { margin: 0; }
.cabinet-head p { margin: 4px 0 16px; color: var(--dim); font-size: 14px; }
.cabinet-empty { margin: 0; color: var(--dim); font-size: 14px; }
.shelf { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 8px;
         grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }   /* the season name fits one line */
.trophy { position: relative; display: flex; align-items: center; gap: 12px; min-width: 0; padding: 12px 16px;
          border-radius: calc(var(--radius) - 2px); background: var(--surface-2); border: 1px solid var(--line);
          border-top: 2px solid var(--medal-1); }
.trophy .cup { flex: none; width: 24px; height: 24px; color: var(--crown); filter: drop-shadow(0 0 6px rgba(232, 185, 63, .4)); }
:root[data-theme="light"] .trophy .cup { filter: drop-shadow(0 1px 1px rgba(131, 95, 8, .3)); }
.trophy .av { width: 38px; height: 38px; box-shadow: 0 0 0 2px var(--surface-2), 0 0 0 3.5px var(--medal-1); }
.trophy .who-t { display: flex; flex-direction: column; min-width: 0; gap: 2px; }
.trophy .who-t b { color: var(--strong); font-size: 15px; font-weight: 600; overflow: hidden; text-overflow: ellipsis;
                   white-space: nowrap; }
.trophy .what { color: var(--medal-1); font-size: 13px; font-weight: 600; }
.trophy .when { color: var(--faint); font: 500 12px/1.35 var(--mono); }
.past-title { margin: 0 0 12px; }

/* ---------- loading skeleton ----------
   The board's own shape (podium strip, header line, rows) in the surface colours, shown from the first paint until
   the snapshot arrives, so the page never jumps from blank to full. Its only motion is a slow, gentle pulse, and
   none at all under reduced motion. Without JavaScript it never shows: the noscript notice does instead. */
.skel-pod, .skel-row { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow-card); }
.skel-pod { display: grid; grid-template-columns: repeat(3, 1fr); height: 92px; margin: 24px 0 32px; overflow: hidden; }
.skel-pod i + i { border-left: 1px solid var(--line); }
.skel-head { height: 11px; width: 38%; margin: 0 0 16px 20px; border-radius: 4px; background: var(--surface-2); }
.skel-row { height: 68px; margin-bottom: 8px; position: relative; }
.skel-row::before, .skel-row::after { content: ""; position: absolute; top: 50%; border-radius: 999px; background: var(--surface-3); }
.skel-row::before { left: 72px; width: 40px; height: 40px; margin-top: -20px; }
.skel-row::after { left: 128px; width: 30%; max-width: 220px; height: 10px; margin-top: -5px; }
@media (prefers-reduced-motion: no-preference) {
  .skel { animation: skel-pulse 1.6s ease-in-out infinite; }
  @keyframes skel-pulse { 50% { opacity: .55; } }
}
@media (max-width: 980px) {
  .skel-pod { grid-template-columns: 1fr; height: 234px; }
  .skel-pod i + i { border-left: 0; border-top: 1px solid var(--line); }
}
@media (max-width: 620px) {
  .skel-row { height: 66px; }
  .skel-row::before { left: 50px; }
  .skel-row::after { left: 102px; width: 40%; }
}
html:not(.js) .skel, html:not(.js) .navbar, html:not(.js) .season-bar, html:not(.js) .facts, html:not(.js) .theme,
html:not(.js) .live, html:not(.js) .lang { display: none; }

/* ---------- languages (i18n.js) ----------
   Until the chosen language's words arrive, the page's fixed text is held invisible (layout kept) so nobody sees
   English flash and then change. Failsafe: if they never arrive, the animation shows the English anyway. */
html.i18n-wait [data-i18n] { animation: i18n-wait 0s 1.5s both; }
@keyframes i18n-wait { from { visibility: hidden; } to { visibility: visible; } }
/* Japanese: the self-hosted faces are Latin only, so Latin (names, numbers) keeps Archivo and the Japanese text
   falls to each platform's own Japanese UI font instead of whatever the browser picks. Letter-spacing made for
   Latin capitals only pulls kana apart, so it is dropped there. */
:root:lang(ja) {
  --sans: "Archivo", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic UI", "Yu Gothic", Meiryo,
          "Noto Sans JP", "Noto Sans CJK JP", system-ui, sans-serif;
}
:lang(ja) .facts dt, :lang(ja) .thead, :lang(ja) .at-unit, :lang(ja) .next-tag, :lang(ja) .rtip-m,
:lang(ja) .live, :lang(ja) .pod .gap { letter-spacing: .02em; }
:lang(ja) h1, :lang(ja) .tagline { word-break: auto-phrase; }   /* Chrome: break between phrases, not mid-word */
/* the ladder's threshold column is one fixed width so every row lines up; where the unit word is long
   ("invitaciones", "Einladungen", "zaproszenia") that width grows for the whole ladder, not per row */
:lang(es), :lang(fr), :lang(de), :lang(pl) { --at-w: 96px; }
/* the 10px caption labels are fine for Latin capitals but too small for kanji and kana */
:lang(ja) .facts dt, :lang(ja) .at-unit, :lang(ja) .next-tag, :lang(ja) .rtip-m { font-size: 11px; }
/* long words (German, Polish, French): break a word that cannot fit its line rather than push past the edge */
.tagline, .lede, .empty-t, .empty-h, .cabinet-head p, .step-mid .note, .tip, .rtip { overflow-wrap: break-word; }

/* ---------- print ----------
   app.js switches to the light theme for the print itself; this strips the chrome and keeps rows whole. */
@media print {
  body { background: #fff; background-image: none; }
  .navbar, .theme, .lang, .skip, .live, .season-bar, .skel, .rtip, .tip, .seg, .retry, .join,
  .pager-in, .pager-note { display: none !important; }
  .masthead { padding: 0 0 12px; }
  .wrap { width: 100%; max-width: none; }
  .row, .pod, .step, .season-card, .trophy, .podium, .ladder, .cabinet { box-shadow: none !important; break-inside: avoid; }
  .rise, .fade-in, .bar i, .sb-rail i { animation: none !important; }
  .foot { padding-bottom: 0; }
}

/* pre-launch secrets: random filler letters, blurred (the real word is never sent to the page) */
/* inline-block: WebKit/Safari only reliably applies `filter` to a box, not to a run of inline text */
.blurred { display: inline-block; filter: blur(4.5px); -webkit-filter: blur(4.5px); user-select: none; -webkit-user-select: none;
           pointer-events: none; opacity: .85; }
/* an engine without filters, or High Contrast (which switches filters off): a solid redaction bar, never the letters */
@supports not ((filter: blur(1px)) or (-webkit-filter: blur(1px))) {
  .blurred { color: transparent; background: var(--line-2); border-radius: 3px; opacity: 1; }
}
@media (forced-colors: active) {
  .blurred { forced-color-adjust: none; filter: none; color: transparent; background: GrayText; border-radius: 3px; opacity: 1; }
}
@media print { .blurred { filter: none; visibility: hidden; } }
