/* Served from here rather than from fonts.googleapis.com. Two reasons, and the
   second is the one that matters: a third-party stylesheet is render-blocking
   on a host this page does not control, and Google Fonts logs the IP of every
   visitor who loads it, which is a data transfer this game has no business
   making on a player's behalf. Self-hosting also removes two origins from the
   Content-Security-Policy, which is what makes that policy short enough to
   read.

   Both families are split latin / latin-ext exactly as Google splits them, and
   the `unicode-range` is theirs verbatim. That split is not an optimisation
   here, it is correctness: the draft pool is full of Modrić, Pjanić and
   Mkhitaryan, whose glyphs live in latin-ext. Shipping only the latin subset
   would have dropped those names to a fallback font mid-roster. The browser
   fetches latin-ext only on a page that actually needs it.

   `swap` because the alternative is a roster that renders invisible while a
   47 KB font is in flight. */
@font-face {
  font-family: "Bebas Neue";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/bebas-neue-v16-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: "Bebas Neue";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/bebas-neue-v16-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;
}

/* One file covers 400 through 700: Google serves the same Inter woff2 for
   every weight the page asks for, so it is a variable font and the range says
   so. Declaring four separate weights pointing at one file would make the
   browser fetch it four times over. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/inter-v20-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: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/inter-v20-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;

  /* This used to be `color-scheme: light dark`, which meant no colour here
     could be a literal - each one had to mix toward transparent just to
     survive on a backdrop it couldn't see. Committing to one dark value
     lets every value below be read straight off the page. */
  --ink: #12161f;
  --panel: #1c2230;
  --panel-2: #232b3c;
  --pitch: color-mix(in srgb, #3d8b4a 28%, var(--ink));

  --line: #2e3646;
  --line-2: #69748a;

  --text: #f2f5fa;
  --text-2: #8d97ab;
  --dim: #6b7488;

  --accent: #35c9c1;
  --accent-lit: #5be0d8;

  /* Kept apart from --accent on purpose: the draft's tier-elite card is
     gold regardless of what the site accent is, because gold reads as
     "best" independent of the current color scheme. */
  --gold: #c9a84c;

  --green: #4fd18f;
  --blue: #6ea8fe;
  --red: #ef6262;
  --purple: #a888f0;
  --pink: #ef5fa3;

  --display: "Bebas Neue", Impact, Haettenschweiler, sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--text);
  font: 15px/1.5 Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
}

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

header.site {
  display: flex;
  align-items: baseline;
  gap: 18px;
  padding: 11px 18px;
  border-bottom: 1px solid var(--line);
}

/* The share panel on the verdict. */
.share-line { margin: 0 0 12px; color: var(--text-2); font-size: 13px; }

.share-row { display: flex; gap: 8px; flex-wrap: wrap; }

/* Kept in normal layout flow rather than display:none - the copy button's
   script calls field.select() on click, which a display:none input cannot
   satisfy in every browser. Clipped to nothing instead, so the URL text
   never paints but stays selectable/copyable. */
.share-url {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

.share-copy {
  flex: 0 0 auto;
  padding: 9px 20px;
  border: 1px solid var(--accent);
  border-radius: 3px;
  background: transparent;
  color: var(--accent);
  font-family: var(--display);
  font-size: 16px;
  letter-spacing: .06em;
  cursor: pointer;
}

.share-copy:hover { background: var(--accent); color: var(--ink); }

/* The error page. Its job is to say what happened in one line and put a way
   back to the game under it - a dead end is what turns one bad request into a
   player who does not return. */
.error-page {
  max-width: 46ch;
  margin: 12vh auto 0;
  text-align: center;
}

.error-code {
  margin: 0;
  font-family: var(--display);
  font-size: 76px;
  line-height: 1;
  color: var(--line-2);
}

.error-heading {
  margin: 6px 0 12px;
  font-family: var(--display);
  font-size: 34px;
  font-weight: 400;
  letter-spacing: .04em;
  color: var(--accent);
}

.error-detail { margin: 0 0 26px; color: var(--text-2); }

.error-home {
  display: inline-block;
  padding: 11px 22px;
  border: 1px solid var(--accent);
  border-radius: 3px;
  font-family: var(--display);
  font-size: 18px;
  letter-spacing: .06em;
  text-decoration: none;
  color: var(--accent);
}

.error-home:hover { background: var(--accent); color: var(--ink); }

.error-ref { margin: 22px 0 0; font-size: 12px; color: var(--dim); }
.error-ref code { color: var(--text-2); }

/* Deliberately quiet. It is on every page including the draft, where the pitch
   and the roster are the only things that should be asking for attention. The
   credit has to be here - the stats and the portraits are all three of these
   sources rearranged - but nothing about it needs to be read twice, so it is
   the smallest type on the page and centred rather than left-hung, where it
   reads as a colophon instead of as a paragraph someone forgot to finish. */
footer.site {
  margin-top: 32px;
  padding: 16px 18px 24px;
  border-top: 1px solid var(--line);
  font-size: 10.5px;
  line-height: 1.6;
  text-align: center;
  color: var(--dim);
}

/* One line, not wrapped and balanced across three - `nowrap` plus the
   smallest gutter padding can spare says so outright, so a wide verdict page
   doesn't quietly reopen the wrap this once ran to three lines on. */
footer.site p { margin: 0 auto; white-space: nowrap; }
footer.site a { color: var(--text-2); }
footer.site a:hover { color: var(--accent); }

.header-meta {
  font-size: 11px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--dim);
}

.header-meta .hl { color: var(--accent); }

.brand {
  display: inline-block;
  font-family: var(--display);
  font-size: 25px;
  font-weight: 400;
  letter-spacing: .06em;
  line-height: 1.4;
  text-decoration: none;
  color: var(--text);
}

.brand .lit { color: var(--accent); }

/* Everything at the top right: the How to Play chip, then the account link.
   `margin-left: auto` lives here and only here - the free space collects to the
   left of the whole group whether or not the page in between supplied a
   .header-meta, and the two stay next to each other in that order. */
.header-right {
  margin-left: auto;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 14px;
}

/* How to Play - the trigger is a chip at the top right of every page's header,
   and the rules themselves open as a popup over the page. */
.howto {
  flex: 0 0 auto;
  /* The header aligns on the baseline, for the brand and the meta line; the
     chip is a bordered box and reads better centred against them. */
  align-self: center;
}

/* The outlined pill both header controls wear - How to Play and the account
   link. One definition rather than one each: they sit side by side, so any
   difference in padding, weight or tracking between two copies reads as a
   mistake from across the room.

   Named .nav-chip rather than .chip: the draft screen's player-tag pills
   further down this file already claim .chip, and a second rule with the
   same selector and equal specificity is decided by source order, not
   intent - the tag rule comes later and would silently win every property
   they share (padding, radius, font-size), which is exactly what happened
   here before the rename. */
.nav-chip {
  display: inline-block;
  /* An inline-block's default vertical-align is `baseline`, which asks
     whatever line box it sits in to leave room below it for a normal line's
     descenders - invisible here until something makes the browser actually
     account for that room, which the account chip does once it also carries
     `overflow: hidden` for its ellipsis (below). The fix belongs on the chip
     itself rather than on whichever wrapper happens to trigger it, since nothing
     about this chip is meant to align to text around it in the first place. */
  vertical-align: top;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 3px;
  white-space: nowrap;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .15em;
  text-decoration: none;
  color: var(--dim);
  cursor: pointer;
  transition: color .12s, background .12s, border-color .12s, transform .1s;
}

.nav-chip:hover {
  color: var(--text);
  background: #ffffff0a;
  border-color: var(--line-2);
  transform: scale(1.04);
}

/* Two labels per trigger - a full word for everywhere with room, and a
   compact glyph the phone breakpoint below swaps in instead of shrinking the
   word past reading. Compact is the one hidden by default so a page that
   never loads the phone media query (print, a stale cache) still reads the
   full label rather than an unexplained glyph. */
.nav-chip-compact { display: none; }

/* Shared shell for every chip that opens a <details> popup over the page -
   How to Play and, since it stopped being a page of its own, the account
   chip too. Both carry this class alongside their own (.howto, .account-popup)
   so a rule that has to stay identical between them is one rule, not two
   copies drifting apart. */
.popup summary {
  list-style: none;
  /* Above its own backdrop, so the trigger stays lit and clickable while the
     popup is open and doubles as the close control. */
  position: relative;
  z-index: 41;
}

.popup summary::-webkit-details-marker { display: none; }

/* The chip stays in its hover state while the panel it opened is up. */
.popup[open] summary {
  color: var(--text);
  background: #ffffff0a;
  border-color: var(--line-2);
}

/* Every popup opens over a dimmed page. The backdrop dims it and takes the
   clicks meant for it, so whatever is underneath stays out of reach while the
   panel is up; base.html turns a click on it into a close. */
.popup-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(0, 0, 0, .78);
}

/* The page beneath must not scroll away under either popup. */
body:has(.popup[open]) { overflow: hidden; }

.popup-panel {
  position: fixed;
  z-index: 42;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(560px, calc(100vw - 32px));
  max-height: min(78vh, 620px);
  overflow-y: auto;
  padding: 22px 24px 20px;
  border: 1px solid var(--line-2);
  border-radius: 4px;
  background: var(--panel);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .6);
  text-align: left;
  animation: popup-open .16s ease-out;
}

@keyframes popup-open {
  from { opacity: 0; transform: translate(-50%, -48%) scale(.97); }
}

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

.popup-title {
  margin: 0 0 14px;
  font-family: var(--display);
  font-weight: 400;
  font-size: 24px;
  letter-spacing: .05em;
  color: var(--accent);
}

/* Top right of the panel, same corner a browser window puts its own close
   control in. `.popup-panel` is already `position: fixed`, so this needs no
   positioning context of its own beyond that. */
.popup-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: none;
  color: var(--dim);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  transition: color .12s, background .12s, border-color .12s;
}

.popup-close:hover {
  color: var(--text);
  background: #ffffff0a;
  border-color: var(--line-2);
}

.howto ol {
  margin: 0;
  padding: 0 0 0 20px;
  color: var(--text-2);
  font-size: 12.5px;
  line-height: 1.7;
}

.howto ol li { padding: 3px 0; }
.howto ol li::marker { color: var(--accent); font-weight: 700; }

main { max-width: 1100px; margin: 0 auto; padding: 20px; }

.label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  opacity: .6;
}

/* Landing */
.landing {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  padding: 52px 0 56px;
  text-align: center;
}

.hero {
  margin: 0;
  /* Stated rather than inherited from the verdict's season-hero panel, which
     shared this class name until it became `.verdict-hero`. The copy shares a
     grid cell with `.hero-banner`, which is the taller of the two and so sets
     the cell's height - without this the headline and lede sit jammed against
     the top of that cell with the slack all below them. */
  padding: 26px 16px;
  font-family: var(--display);
  font-weight: 400;
  /* Scales itself between the 360px phone and the desktop column. */
  font-size: clamp(38px, 7vw, 56px);
  line-height: .94;
  letter-spacing: .03em;
}

.hero .lit { color: var(--accent); }

/* The armed banner and the hero copy occupy the same grid cell, toggled by
   the `armed` class setup.html's script adds to `.hero-swap` once a league
   is picked. Both stay in flow (visibility, not display) so the section's
   height is always the taller of the two - swapping never reflows the
   league row beneath it, which used to leave a stale :hover on whatever
   button ended up under the pointer after the shift. Unarmed (no JS, or
   before any click) always shows the hero copy, so nothing here can leave
   the section blank. */
.hero-swap { display: grid; position: relative; }

.hero-content,
.hero-banner { grid-area: 1 / 1; }

.hero-banner {
  visibility: hidden;
  width: 100%;
  max-width: 440px;
  aspect-ratio: 16 / 9;
  justify-self: center;
  /* Two gradients intersected rather than one radial ellipse: an ellipse
     inscribed in a 16:9 box is itself wide, so its fade band barely reaches
     the left/right edges - top and bottom softened but the sides stayed a
     hard cut. Fading each axis independently, then keeping only where both
     are opaque, feathers all four sides evenly. */
  -webkit-mask-image:
    linear-gradient(to right, transparent 0%, #000 16%, #000 84%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, #000 12%, #000 88%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent 0%, #000 16%, #000 84%, transparent 100%),
    linear-gradient(to bottom, transparent 0%, #000 12%, #000 88%, transparent 100%);
  mask-composite: intersect;
}

.hero-swap.armed .hero-content { visibility: hidden; }
.hero-swap.armed .hero-banner { visibility: visible; }

/* Independent of the armed/unarmed toggle above - the season pick has nothing
   to do with which league is armed, so the badge stays in the same spot
   whether the hero shows copy or art. */
.year-badge {
  position: absolute;
  top: -20px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--display);
  font-size: 72px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: #35c9c1;
  -webkit-text-stroke: 1.5px #000;
  paint-order: stroke fill;
  visibility: hidden;
  z-index: 1;
}

.year-badge.shown { visibility: visible; }

.lede {
  max-width: 440px;
  margin: 0 auto;
  font-size: 13px;
  line-height: 1.65;
  color: var(--text-2);
}

.picker {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: 100%;
}

.kicker {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .22em;
  color: var(--dim);
}

/* All five league descriptions render at once, stacked in the same grid
   cell as the "Select a league" label - see the comment in setup.html for
   why (avoids the reflow the hero banner used to cause when it revealed
   content JS had only just discovered the size of). Only the picked
   league's paragraph (.shown) ever becomes visible. */
.kicker-swap {
  display: grid;
  width: 100%;
  max-width: 480px;
}

.kicker-swap .kicker,
.kicker-desc { grid-area: 1 / 1; }

.kicker-swap .kicker { font-size: 13px; }

.kicker-desc {
  margin: 0;
  visibility: hidden;
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-2);
  text-align: center;
}

.kicker-swap.armed .kicker { visibility: hidden; }
.kicker-swap.armed .kicker-desc.shown { visibility: visible; }

/* One row for however many leagues the server sent - --league-count comes
   from `leagues|length` in the template, so this needs no edit when Ligue 1
   goes live and the count moves from 4 to 5. */
.league-grid {
  display: grid;
  grid-template-columns: repeat(var(--league-count, 4), 1fr);
  gap: 8px;
  width: 100%;
  max-width: 620px;
}

.league {
  padding: 14px 4px;
  font-family: var(--display);
  font-size: 18px;
  font-weight: 400;
  letter-spacing: .04em;
  line-height: 1.05;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: none;
  color: #cfcfcf;
  cursor: pointer;
  transition: color .12s, background .12s, border-color .12s,
              transform .1s, box-shadow .12s;
}

/* Swaps in for .league-name only under the phone breakpoint below; at every
   other width this stays hidden and the button shows its name. */
.league-logo { display: none; }

.league:hover,
.league.armed {
  color: var(--accent);
  background: var(--panel-2);
  border-color: var(--accent);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 18%, transparent);
}

.league:hover { transform: scale(1.04); }

/* The landing page's leaderboard link and the account page's two buttons share
   this rule rather than copying it. They are asked to look exactly like
   Randomize, and rules that have to stay identical are one rule with extra
   selectors. What each one needs on top is below: a button is already a centred
   block that does not underline itself, and the account pair sizes to its own
   words instead of filling a column. */
.randomize,
.board-link,
.account-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 230px;
  height: 45.5px;
  padding: 12px;
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .15em;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: none;
  color: var(--dim);
  cursor: pointer;
  transition: color .12s, background .12s, border-color .12s, transform .1s;
}

.board-link {
  text-decoration: none;
}

.randomize:hover,
.board-link:hover,
.account-btn:hover {
  color: var(--text);
  background: #ffffff0a;
  border-color: var(--line-2);
  transform: scale(1.04);
}

.season-select {
  width: 100%;
  max-width: 230px;
  height: 45.5px;
  padding: 12px;
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .1em;
  text-align: center;
  text-align-last: center;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: none;
  color: var(--dim);
  cursor: pointer;
}

.season-select:hover,
.season-select:focus-visible {
  color: var(--text);
  border-color: var(--line-2);
}

/* Hidden until a league is armed - setup.html's script adds .armed to the
   form the moment either a direct click or Randomize settles on one, same
   trigger that swaps in the hero banner. */
.start-draft { display: none; }
.picker.armed .start-draft {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 230px;
  height: 45.5px;
  padding: 12px;
  font-family: var(--display);
  font-size: 18px;
  font-weight: 400;
  letter-spacing: .06em;
  border: 1px solid var(--accent);
  border-radius: 3px;
  background: var(--accent);
  color: #171106;
  cursor: pointer;
  transition: filter .12s, transform .1s;
  animation: start-draft-glow 1.8s ease-in-out infinite;
}

@keyframes start-draft-glow {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  50% { box-shadow: 0 0 18px 4px color-mix(in srgb, var(--accent) 55%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .picker.armed .start-draft { animation: none; }
}

.picker.armed .start-draft:hover {
  filter: brightness(1.08);
  transform: scale(1.04);
}

/* Focus is its own signal, never the hover treatment: a keyboard user has to
   be able to see where they are without a pointer. Every interactive element
   is named here rather than the landing page's two, because the draft and the
   verdict were left riding the browser default - visible in Chromium against
   black, but not a thing to rely on across browsers. */
a:focus-visible,
button:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--accent-lit);
  outline-offset: 2px;
}

/* Phone. The row stays one row - buttons shrink and swap their name for a
   small logo badge rather than wrapping to a second row or a single column. */
@media (max-width: 460px) {
  .league-grid { gap: 4px; max-width: 100%; }
  .league { padding: 8px 2px; }
  .league-name { display: none; }
  .league-logo { display: block; margin: 0 auto; width: 22px; height: 22px; object-fit: contain; }
  .landing { padding: 30px 0 34px; gap: 18px; }
  .lede { font-size: 12px; }
  .brand { font-size: 21px; }
  /* The header is a single row here (brand + chips), so How to Play and the
     account link give up their word for a glyph rather than fight the row
     for space - same swap the league grid makes above, name for badge. */
  .nav-chip-full { display: none; }
  .nav-chip-compact { display: inline-flex; }
  .nav-chip {
    width: 30px;
    height: 30px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
  }
  .account-nav .nav-chip { max-width: none; }
  .randomize,
  .board-link,
  .season-select,
  .picker.armed .start-draft { max-width: 100%; }
  /* One line only holds above this breakpoint - the credit's too long to fit
     a phone width at any size still worth reading, and forcing it here would
     widen the whole page instead of just the footer. */
  footer.site p { white-space: normal; }
}

/* Leaderboard */
.board {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  padding: 44px 0 52px;
  text-align: center;
}

.board-title {
  margin: 0;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(34px, 6vw, 48px);
  line-height: 1;
  letter-spacing: .03em;
}

.board-title .lit { color: var(--accent); }

/* One card per league on one row, counted by the server exactly as the landing
   page's league row is - `auto-fit` was left to work it out and put four cards
   on the first row and Bundesliga alone on a second, which reads as a ranking
   of the leagues rather than a set of them. Two rows are fine when they are
   the same two rows for everyone; 4+1 is not. */
.board-grid {
  display: grid;
  grid-template-columns: repeat(var(--board-count, 5), 1fr);
  gap: 10px;
  width: 100%;
  max-width: 980px;
  margin-top: 6px;
}

.board-card {
  padding: 18px 14px 20px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--panel-2);
  text-align: left;
}

.board-league {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-family: var(--display);
  font-weight: 400;
  font-size: 17px;
  letter-spacing: .04em;
}

.board-logo { width: 20px; height: 20px; object-fit: contain; }

.board-empty,
.board-off {
  margin: 0;
  font-size: 12px;
  line-height: 1.6;
  color: var(--dim);
}

.board-off { max-width: 440px; }

.board-back {
  margin-top: 8px;
  padding: 12px 26px;
  font-family: var(--display);
  font-size: 18px;
  font-weight: 400;
  letter-spacing: .06em;
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--text);
  text-decoration: none;
  transition: color .12s, background .12s, border-color .12s, transform .1s;
}

.board-back:hover {
  color: var(--accent);
  background: var(--panel-2);
  border-color: var(--accent);
  transform: scale(1.04);
}

/* One column below the width where a fifth of the row stops holding a league
   name and a list of players beside it. Not the 460px phone breakpoint the
   landing row uses: that row's buttons can shed their names for a logo, and a
   board card cannot shed its rows. */
@media (max-width: 820px) {
  .board-grid { grid-template-columns: 1fr; max-width: 480px; }
}

@media (max-width: 460px) {
  .board { padding: 28px 0 34px; gap: 14px; }
}

/* Draft: roster left, pitch right. The 4-3-3 is the thing being built and the
   thing you click, so it takes the wide column - the ratio eraball uses for its
   own court. The roster gave up its rating column to fit the narrow side. */
.game {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: 20px;
  align-items: start;
}

.pool { min-width: 0; }
.rail { min-width: 0; }

.pool h2.club {
  margin: 0 0 9px;
  font-family: var(--display);
  font-size: 25px;
  font-weight: 400;
  letter-spacing: .04em;
}

.pool h2.club .era { color: var(--accent); }

.club-text { display: inline-block; }

.game.drafting .roster-scroll,
.game.drafting .rail,
.game.drafting .pos-filter {
  pointer-events: none;
  opacity: .5;
  filter: blur(2px);
  transition: opacity .2s, filter .2s;
}

.game.settled .club-text { animation: club-settle .4s ease-out; }
@keyframes club-settle {
  0% { color: var(--accent); transform: scale(1.04); }
  100% { color: inherit; transform: scale(1); }
}

/* Position filter - narrows the roster to one broad group. Replaces the
   old click-a-pitch-slot-to-filter interaction entirely; the pitch is a
   placement target now, nothing else. */
.pos-filter { display: flex; gap: 6px; margin-bottom: 10px; }
.pos-pill {
  padding: 5px 14px;
  border: 1px solid var(--line);
  border-radius: 3px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  color: var(--text-2);
  text-decoration: none;
  transition: color .12s, background .12s, border-color .12s;
}
.pos-pill:hover { color: var(--text); border-color: var(--line-2); }
.pos-pill.active {
  color: var(--accent);
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--ink));
}

/* Around twelve rows visible, the rest reachable by scrolling this box
   rather than the whole page - so the pitch beside it never scrolls out of
   reach on a long roster. */
.roster-scroll {
  max-height: 630px;
  overflow-y: auto;
  /* Styling the webkit scrollbar switches it from macOS's overlay style
     (no layout space) to a classic reserved-width one - this padding is
     that reserved 8px, so the thumb doesn't sit on top of the Ast column. */
  padding-right: 8px;
  scrollbar-color: color-mix(in srgb, var(--accent) 55%, transparent) transparent;
}
.roster-scroll::-webkit-scrollbar { width: 8px; }
.roster-scroll::-webkit-scrollbar-track { background: transparent; }
.roster-scroll::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: 4px;
}
.roster-scroll::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--accent-lit) 70%, transparent);
}

table.roster { width: 100%; border-collapse: collapse; font-size: 11.5px; }
table.roster td { padding: 9px 3px; border-top: 1px solid var(--line); text-align: right; white-space: nowrap; }
table.roster td.l { text-align: left; }
/* The name takes whatever the fixed columns leave and ellipsises past it. The
   trimming moved onto the inner span when the position line was added beneath:
   text-overflow acts on a block box's own inline content, and a cell holding
   two stacked lines no longer has any of its own. */
table.roster td.nm { width: 100%; max-width: 0; font-size: 14px; padding-left: 8px; }
table.roster td.nm .nm-name { display: block; overflow: hidden; text-overflow: ellipsis; }

/* The roster is ordered by contribution, so the role is the one thing a run of
   names does not tell you. Kept at the size of the GOALS/AST labels beside it,
   in the dimmest text on the page - it is there to be read down the column at a
   glance, not to compete with the name above it. */
table.roster td.nm .pos-label {
  display: block;
  margin-top: 1px;
  font-size: 8.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--dim);
}
/* Goals/Ast carry their own label now that there's no header row - stacked
   like the stat card's big/small numbers, with extra side padding so the
   two columns don't read as one run of digits. */
table.roster td.stat { text-align: center; padding: 5px 12px; }
table.roster td.stat .n { font-size: 14px; font-weight: 700; color: var(--text); }
table.roster td.stat .l {
  font-size: 8px;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: .1em;
  margin-top: 1px;
}
/* Blocked reasons and place buttons are sentences, not numbers - the blanket
   nowrap above is right for the stat columns but would run a blocked reason
   like "the pitch still needs 4 players and only 2 rounds remain" off the
   edge of a 433px column. */
table.roster tr.blocked { opacity: .55; }

/* Player faces. The initials tile sits underneath every portrait, so a missing
   or failed image falls back to it without any JavaScript. */
/* Portraits are 100x130. A circle had to crop one to a square, and a square
   taken from the top of a 100x130 frame clips whatever the photographer left
   above the hairline - so heads lost their tops. The frame now carries the
   portrait's own ratio and nothing is cut. --face is the width; the height
   follows from it. */
.face-wrap {
  position: relative;
  /* Above the sparkle overlay (`.sparkles`),
     which paints last/on top by default - without this his face would
     twinkle along with the card. */
  z-index: 1;
  display: inline-block;
  flex: none;
  width: var(--face);
  height: calc(var(--face) * 1.3);
  border-radius: 3px;
  overflow: hidden;
  background: color-mix(in srgb, var(--accent) 22%, var(--panel));
}

.face {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

img.face {
  /* The frame matches the source ratio, so contain fits the whole portrait and
     never crops. A source that ever ships a different ratio letterboxes onto
     the initials tile behind it rather than losing a face. */
  object-fit: contain;
  object-position: top center;
}

.face.none {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(var(--face) * 0.4);
  font-weight: 700;
  opacity: .75;
}

/* Extra left padding - once a row is armed, the gold edge bar
   (tr.armed td:first-child's box-shadow) sits right at the cell edge, and
   without this the portrait crowded right up against it.
   `table.roster td.face-cell` (not bare `td.face-cell`) is not decoration -
   `table.roster td`'s own specificity (0,1,2) otherwise beats a bare
   `td.face-cell` (0,1,1) and silently wins the padding regardless of what
   value is set here. Confirmed via getComputedStyle in a live browser:
   three prior edits to this rule (10px, 18px, 28px) never actually
   applied - the cell stayed at the base rule's 3px the whole time. */
table.roster td.face-cell { width: 50px; padding-left: 28px; padding-right: 0; line-height: 0; }

button {
  padding: 4px 12px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: none;
  color: inherit;
  cursor: pointer;
  font: inherit;
}
button:hover { border-color: var(--accent); }

/* .error - was mixed against transparent over an unknown base. */
.error {
  padding: 8px 10px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--red) 22%, var(--ink));
  color: var(--text);
}

.done { font-weight: 600; }

/* The 4-3-3 */
.pitch {
  background: var(--pitch);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 6px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

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

/* A slot is a card now, not a chip: 140px so a portrait can be seen rather than
   identified, with the position code sitting in the corner the way eraball sets
   its own. */
.spot {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: 140px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 6px 4px;
  border-radius: 5px;
  font-size: 10px;
  text-align: center;
  color: inherit;
  text-decoration: none;
}

.spot .pos {
  position: absolute;
  top: 5px;
  left: 7px;
  font-family: var(--display);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: .1em;
  line-height: 1;
  opacity: 1;
  color: color-mix(in srgb, var(--accent) 62%, var(--ink));
}

.bench-row .spot { min-height: 118px; }

/* The goalkeeper is alone on his line, and `flex: 1` stretched his card the
   full width of the pitch - a keeper four times the size of a centre-back. He
   takes a third and centres, which is roughly what he occupies on the three
   lines above him. */
.pitch .line > .spot:only-child { flex: 0 1 33%; }

.spot.filled {
  background: color-mix(in srgb, var(--accent) 24%, var(--pitch));
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--pitch));
}

/* The same rating tier as the stat card, carried onto the pitch once he's
   actually placed - mixed against --pitch instead of --ink so a filled
   slot still reads as grass, not as the card's own dark backdrop. The
   sparkle dots are real elements in a `.sparkles` overlay (see below) so
   they can twinkle without animating the gradient underneath them. */
.spot.filled.tier-average {
  background: color-mix(in srgb, var(--line-2) 30%, var(--pitch));
  border-color: color-mix(in srgb, var(--line-2) 55%, var(--pitch));
}
.spot.filled.tier-legendary {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--purple) 60%, var(--pitch)) 0%,
    color-mix(in srgb, var(--purple) 26%, var(--pitch)) 100%);
  border-color: var(--purple);
}
.spot.filled.tier-elite {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--gold) 55%, var(--pitch)) 0%,
    color-mix(in srgb, var(--gold) 22%, var(--pitch)) 100%);
  border-color: var(--gold);
}
.spot.filled.tier-great {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--pink) 46%, var(--pitch)) 0%,
    color-mix(in srgb, var(--pink) 18%, var(--pitch)) 100%);
  border-color: var(--pink);
}
.spot.filled.tier-good {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--blue) 40%, var(--pitch)) 0%,
    color-mix(in srgb, var(--blue) 16%, var(--pitch)) 100%);
  border-color: var(--blue);
}
.spot.filled.tier-poor {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--red) 40%, var(--pitch)) 0%,
    color-mix(in srgb, var(--red) 16%, var(--pitch)) 100%);
  border-color: var(--red);
}

/* Twinkling sparkle dots, shared by the stat card and any filled pitch
   slot. Real elements (`_sparkles.html`'s macro, twenty `<i>`s in a
   `.sparkles` wrapper) rather than a single animated background layer, so
   each one can carry its own animation-delay and twinkle independently
   instead of all pulsing in lockstep. Only the top three tiers show any at
   all - good/average/poor stay flat - and how many of the twenty survive
   scales with the tier: great shows the first 6, elite the first 14,
   legendary all 20. The portrait sits in its own stacking context
   (`.face-wrap` below) specifically so this layer - painted last, on top
   of everything else in the card/slot by default - never covers his face. */
.spot.filled, .player-card {
  position: relative;
}
.sparkles { position: absolute; inset: 0; pointer-events: none; display: none; }
.player-card.tier-legendary .sparkles, .spot.filled.tier-legendary .sparkles,
.player-card.tier-elite .sparkles, .spot.filled.tier-elite .sparkles,
.player-card.tier-great .sparkles, .spot.filled.tier-great .sparkles {
  display: block;
}
.tier-great .sparkles i:nth-child(n+7) { display: none; }
.tier-elite .sparkles i:nth-child(n+15) { display: none; }

.sparkles i {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 10%, rgba(255,255,255,.4) 45%, transparent 75%);
  opacity: .2;
  animation: sparkle-twinkle 2.2s ease-in-out infinite;
  /* Negative delay starts each dot already partway through the cycle -
     twenty dots, twenty different phases, no two in sync. */
  animation-delay: calc(var(--i) * -0.15s);
}
/* One position per nth-child, reused by every tier - which dots are
   visible (above) is what actually varies per tier, not where they sit. */
.sparkles i:nth-child(1)  { left: 12%; top: 15%; }
.sparkles i:nth-child(2)  { left: 88%; top: 12%; }
.sparkles i:nth-child(3)  { left: 70%; top: 30%; }
.sparkles i:nth-child(4)  { left: 92%; top: 45%; }
.sparkles i:nth-child(5)  { left: 15%; top: 55%; }
.sparkles i:nth-child(6)  { left: 78%; top: 65%; }
.sparkles i:nth-child(7)  { left: 40%; top: 72%; }
.sparkles i:nth-child(8)  { left: 60%; top: 85%; }
.sparkles i:nth-child(9)  { left: 25%; top: 88%; }
.sparkles i:nth-child(10) { left: 90%; top: 80%; }
.sparkles i:nth-child(11) { left: 50%; top: 20%; }
.sparkles i:nth-child(12) { left: 5%;  top: 38%; }
.sparkles i:nth-child(13) { left: 82%; top: 58%; }
.sparkles i:nth-child(14) { left: 32%; top: 42%; }
.sparkles i:nth-child(15) { left: 65%; top: 48%; }
.sparkles i:nth-child(16) { left: 8%;  top: 78%; }
.sparkles i:nth-child(17) { left: 96%; top: 28%; }
.sparkles i:nth-child(18) { left: 45%; top: 62%; }
.sparkles i:nth-child(19) { left: 20%; top: 68%; }
.sparkles i:nth-child(20) { left: 85%; top: 92%; }

@keyframes sparkle-twinkle {
  0%, 100% { opacity: .2; }
  50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .sparkles i { animation: none; }
}

/* A bigger deal than merely "elite": the glow itself breathes, and the
   sparkle scatter above is the densest of the three tiers that get one.
   Card gets both its entrance animation and the glow at once - a bare
   `animation:` here would silently drop card-open instead of adding to it. */
.player-card.tier-legendary {
  animation: card-open .18s ease-out, legendary-glow 2.2s ease-in-out infinite;
}
.spot.filled.tier-legendary {
  animation: legendary-glow 2.2s ease-in-out infinite;
}
@keyframes legendary-glow {
  0%, 100% { box-shadow: 0 0 32px color-mix(in srgb, var(--purple) 55%, transparent); }
  50% { box-shadow: 0 0 46px color-mix(in srgb, var(--purple) 75%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .player-card.tier-legendary, .spot.filled.tier-legendary { animation: none; }
}

/* An empty slot has to read as a card you can click, not as a caption floating
   on grass. `--line` is #222, which is a hairline against the black the rest of
   the page is built on and simply disappears against the pitch green - so an
   untouched 4-3-3 rendered as eleven position codes scattered on a field with
   no edges between them, and "available" looked identical to the .28 opacity
   that means "this one will not take him". The border is mixed off the accent
   against the pitch, which is the surface it is actually drawn on. */
.spot.empty {
  border: 1px dashed color-mix(in srgb, var(--accent) 34%, var(--pitch));
  background: color-mix(in srgb, var(--accent) 4%, var(--pitch));
  opacity: 1;
}

a.spot.empty { cursor: pointer; }

a.spot.empty:hover {
  opacity: 1;
  border-style: solid;
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--pitch));
}

/* An armed player turns the pitch into the answer to a question: these slots
   take him, those do not. Dimming rather than hiding keeps the shape of the
   4-3-3 readable, which is the thing you are actually deciding about. */
.spot.lit {
  opacity: 1;
  border: 1px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, var(--pitch));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 40%, var(--pitch));
}

.spot.lit .pos,
.spot.lit .n { color: var(--accent); }

.spot .cta {
  font-size: 9px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Red rather than merely dim: an armed player needs "will not take him" to
   read as a rejection, not a softer version of the same "open" state lit
   slots show. */
.spot.dimmed {
  opacity: 1;
  border: 1px solid var(--red);
  background: color-mix(in srgb, var(--red) 12%, var(--pitch));
}

.spot.dimmed .pos { color: var(--red); }

tr.armed td { background: color-mix(in srgb, var(--accent) 11%, var(--ink)); }
tr.armed td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
tr.armed td.nm { color: var(--accent); font-weight: 600; }
/* Gold is what marks the armed player's name. The position under it is the
   same fact on every row, armed or not, and lighting it up too would spend the
   accent on something the highlight is not about. */
tr.armed td.nm .pos-label { color: var(--dim); }

tr.pickable { cursor: pointer; }
tr.pickable:hover td { background: color-mix(in srgb, var(--accent) 6%, var(--ink)); }

.spot .n { font-weight: 700; font-size: 13px; }
.spot .who {
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.spot .yr { opacity: .7; }

.bench-label { display: block; margin: 12px 0 4px; }
.bench-row .spot.filled {
  background: var(--panel-2);
  border-color: var(--line);
}

.needs { font-size: 12px; opacity: .8; margin-top: 10px; }

/* Verdict */
.big { font-size: 46px; font-weight: 800; line-height: 1.05; }

table.standings { border-collapse: collapse; font-size: 15px; width: 100%; min-width: 560px; }
table.standings td { padding: 7px 10px; border-top: 1px solid var(--line); text-align: right; }
table.standings td.l { text-align: left; }
table.standings tr.you {
  font-weight: 700;
  background: color-mix(in srgb, var(--accent) 16%, var(--ink));
}

.again { margin-top: 24px; }

/* Below this the two columns cannot both hold their floor width, so they stack -
   pitch first, because it is the thing being built and the roster is what feeds
   it. `.rail` no longer needs its sticky or its border undone: it has neither. */
@media (max-width: 1000px) {
  .game { grid-template-columns: 1fr; }
  .rail { order: -1; }
}

@media (max-width: 620px) {
  .spot { min-height: 104px; }
  .bench-row .spot { min-height: 92px; }
  .spot .pos { font-size: 12px; }
  /* The bench seat's label carries the minutes now ("SUB 1 · 45'"), eleven
     characters against the pitch's two or three - the pitch's 12px wraps it
     onto a second line here, which climbs over the portrait above it. */
  .bench-row .spot .pos { font-size: 10px; letter-spacing: .06em; white-space: nowrap; }
  /* Flex-wrap alone put How to Play and the account chip whereever the meta
     text's last wrapped line happened to end - the draft screen's pick/season/
     target line runs to two or three lines on a phone, and the icons drifted
     down to sit beside whichever one of those it left them next to. Grid
     pins brand and the icons to a fixed top row and gives the meta text a
     full-width row of its own below, so how long that text gets never moves
     them. Pages with no .header-meta (there is no third grid item then) just
     leave that row's cell empty - nothing to place there, nothing shown. */
  header.site {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "brand right"
      "meta  meta";
    row-gap: 4px;
    column-gap: 12px;
  }
  .brand { grid-area: brand; }
  .header-meta { grid-area: meta; }
  .header-right { grid-area: right; margin-left: 0; align-self: start; }
}

/* --- tags ---------------------------------------------------------------- */

/* Chips sit inline with the player's name on the draft screen, so they have to
   read at a glance and never wrap the row. Colour carries the family: gold for
   what was won, green for what was done, blue for what is possible. */
.chip {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 6px;
  border-radius: 8px;
  border: 1px solid var(--line);
  font-size: 10px;
  letter-spacing: .02em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: .85;
  /* Every chip carries its meaning in a `title`, and the cursor is the only
     thing that says so before you hover long enough to find out. */
  cursor: help;
}
/* One gold in the codebase, not two that nearly match. #d4a017 is gone.
   These four are keyed on the family the template stamps onto every chip from
   `tags.LEGEND`, not on a list of tag names. The list they replaced named the
   nine tags that existed when it was written, so the eight added since - iron
   man, enforcer, one-club man, evergreen, homegrown, European XI regular,
   continental nights, out of position - rendered plain white in a row of
   coloured ones. A tag can no longer arrive without a colour. */
.chip.f-won  { border-color: var(--accent); color: var(--accent); }
.chip.f-done { border-color: var(--green); color: var(--green); }
.chip.f-bond { border-color: var(--blue); color: var(--blue); }
.chip.f-none { opacity: .55; }

/* Stays blue. Gold / green / blue is the semantic family - folding "knows"
   into gold would erase the distinction the colours exist to carry. It is not
   in LEGEND because it is not a tag: it counts squad-mates, so it carries the
   family's colour by hand. */
.chip.knows { border-color: var(--blue); color: var(--blue); font-weight: 700; }

/* The detail row. Chips left the row itself because eight columns in a 430px
   column is already the ceiling; what they lost was a `title` attribute, which
   was the only written explanation a tag ever had. The sentence under them is
   that explanation, on the page rather than under a pointer. */
tr.detail td {
  border-top: 0;
  padding: 0 4px 9px;
  text-align: left;
  white-space: normal;
  background: color-mix(in srgb, var(--accent) 5%, var(--ink));
}


.player-card {
  padding: 10px 6px 4px;
  border: 1px solid var(--line);
  border-radius: 4px;
  animation: card-open .18s ease-out;
}

/* Rating never prints as a number on the draft screen - only the tier it
   falls in shows, as the card's own color. Six tiers, reusing the app's
   existing semantic hues rather than inventing new ones where one already
   fit - gold was already "best", red already means "won't work" on the
   pitch slots right next to this. Purple is the one genuinely new token,
   for "legendary" - a Messi/Cristiano season needed a color nothing else
   in the app was already using. A diagonal gradient + sparkle flecks +
   glow rather than a flat tint, so the tier reads at a glance instead of
   needing a close look at the border color. */
.player-card.tier-legendary {
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--purple) 60%, var(--ink)) 0%,
    color-mix(in srgb, var(--purple) 24%, var(--ink)) 45%,
    color-mix(in srgb, var(--purple) 50%, var(--ink)) 100%);
  border-color: var(--purple);
  box-shadow: 0 0 32px color-mix(in srgb, var(--purple) 55%, transparent);
}
.player-card.tier-elite {
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--gold) 55%, var(--ink)) 0%,
    color-mix(in srgb, var(--gold) 20%, var(--ink)) 45%,
    color-mix(in srgb, var(--gold) 45%, var(--ink)) 100%);
  border-color: var(--gold);
  box-shadow: 0 0 28px color-mix(in srgb, var(--gold) 45%, transparent);
}
.player-card.tier-great {
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--pink) 46%, var(--ink)) 0%,
    color-mix(in srgb, var(--pink) 16%, var(--ink)) 45%,
    color-mix(in srgb, var(--pink) 36%, var(--ink)) 100%);
  border-color: var(--pink);
  box-shadow: 0 0 22px color-mix(in srgb, var(--pink) 35%, transparent);
}
.player-card.tier-good {
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--blue) 42%, var(--ink)) 0%,
    color-mix(in srgb, var(--blue) 14%, var(--ink)) 45%,
    color-mix(in srgb, var(--blue) 32%, var(--ink)) 100%);
  border-color: var(--blue);
  box-shadow: 0 0 20px color-mix(in srgb, var(--blue) 30%, transparent);
}
.player-card.tier-poor {
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--red) 40%, var(--ink)) 0%,
    color-mix(in srgb, var(--red) 13%, var(--ink)) 45%,
    color-mix(in srgb, var(--red) 30%, var(--ink)) 100%);
  border-color: var(--red);
  box-shadow: 0 0 18px color-mix(in srgb, var(--red) 28%, transparent);
}

@keyframes card-open {
  from { opacity: 0; transform: translateY(-6px) scale(.98); }
}

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

/* The close side of card-open: Cancel's swap:150ms delay keeps the old
   #game in the DOM for 150ms with this class on it, which is exactly long
   enough for the card to shrink back down before htmx actually removes it. */
#game.htmx-swapping .player-card {
  opacity: 0;
  transform: translateY(-6px) scale(.98);
  transition: opacity .15s ease-in, transform .15s ease-in;
}

@media (prefers-reduced-motion: reduce) {
  #game.htmx-swapping .player-card { transition: none; }
}

.player-card-head { display: flex; gap: 12px; align-items: center; margin-bottom: 10px; }

.player-card-name {
  font-family: var(--display);
  font-size: 22px;
  letter-spacing: .03em;
  color: var(--text);
  line-height: 1.1;
}

.player-card-sub {
  font-size: 11px;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: .1em;
}

.player-card .chips { margin: 0 0 12px; }

.player-card-stats-big {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  max-width: 420px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  margin-bottom: 8px;
}
.player-card-stats-big > div { text-align: center; padding: 8px 2px; border-left: 1px solid var(--line); }
.player-card-stats-big > div:first-child { border-left: none; }
.player-card-stats-big .n { font-family: var(--display); font-size: 26px; color: var(--accent); line-height: 1; }
.player-card-stats-big .l { font-size: 9px; color: var(--text-2); text-transform: uppercase; letter-spacing: .12em; margin-top: 3px; }

.player-card-stats-sml { display: grid; grid-template-columns: repeat(4, 1fr); max-width: 420px; }
.player-card-stats-sml > div { text-align: center; padding: 6px 2px; }
.player-card-stats-sml .n { font-size: 13px; color: var(--text); font-weight: 600; }
.player-card-stats-sml .l { font-size: 8.5px; color: var(--text-2); text-transform: uppercase; letter-spacing: .1em; margin-top: 2px; }

td.nm .car { margin-left: 4px; font-size: 9px; color: var(--dim); }

.receipt { border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; }
.receipt-total {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  font-weight: 700;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}
.receipt-total .muted { font-weight: 400; opacity: .7; }

ul.effects { list-style: none; margin: 8px 0 0; padding: 0; font-size: 12px; }
ul.effects li {
  display: grid;
  grid-template-columns: 42px 150px 1fr;
  gap: 8px;
  padding: 2px 0;
}
ul.effects .d { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
ul.effects li.good .d { color: var(--green); }
ul.effects li.bad .d { color: var(--red); }
ul.effects li.flat { opacity: .5; }
ul.effects .s { opacity: .75; }
ul.effects .s em { font-style: normal; opacity: .7; }

/* The tag legend. Spans both columns of the draft grid: it is reference read
   once or twice a game, not per pick, so it goes under the board rather than
   competing with it for the narrow column. Three columns because one would run
   twenty-three rows deep. */
.legend {
  grid-column: 1 / -1;
  margin-top: 6px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: 6px;
  /* The gradient wants a near-black past --panel to sit at its dark end.
     --ink is already that colour - pure black - so the gradient runs
     --panel to --ink rather than reaching for a fresh literal the rest of
     the file does not have. */
  background: linear-gradient(160deg, var(--panel) 0%, var(--ink) 100%);
}

.legend summary {
  display: flex;
  align-items: center;
  gap: 8px;
  list-style: none;
  cursor: pointer;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .2em;
  color: var(--accent);
}

.legend summary::-webkit-details-marker { display: none; }

.legend summary .tick {
  width: 3px;
  height: 13px;
  border-radius: 2px;
  background: var(--accent);
}

.legend-body { margin-top: 9px; columns: 3; column-gap: 9px; }

.legend .entry {
  break-inside: avoid;
  display: flex;
  gap: 9px;
  align-items: flex-start;
  margin-bottom: 6px;
  padding: 5px 10px;
  border-radius: 3px;
}

.legend .entry .k {
  flex: none;
  min-width: 104px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
}

.legend .entry .v { font-size: 11px; line-height: 1.35; color: var(--text-2); }

/* The same semantic families the chips use, so a gold chip on a row and a gold
   row in the legend are saying the same thing. */
.legend .f-won  { background: color-mix(in srgb, var(--accent) 6%, var(--ink)); border-left: 2px solid var(--accent); }
.legend .f-done { background: color-mix(in srgb, var(--green) 6%, var(--ink));  border-left: 2px solid var(--green); }
.legend .f-bond { background: color-mix(in srgb, var(--blue) 6%, var(--ink));   border-left: 2px solid var(--blue); }
.legend .f-none { background: color-mix(in srgb, var(--text-2) 8%, var(--ink)); border-left: 2px solid var(--line-2); }

.legend .f-won .k  { color: var(--accent); }
.legend .f-done .k { color: var(--green); }
.legend .f-bond .k { color: var(--blue); }
.legend .f-none .k { color: var(--text-2); }

.legend-foot {
  margin: 9px 0 0;
  font-size: 10.5px;
  font-style: italic;
  text-align: center;
  color: var(--dim);
}

@media (max-width: 900px) { .legend-body { columns: 2; } }
@media (max-width: 620px) { .legend-body { columns: 1; } }

@media (max-width: 760px) {
  ul.effects li { grid-template-columns: 36px 1fr; }
  ul.effects .s { grid-column: 2; opacity: .6; }
}

/* --- verdict redesign -------------------------------------------------- */

/* Panels. Every section on the verdict page is one of these: a bordered box
   with an uppercase kicker header (matching the landing page's `.kicker`)
   and a border-bottom separating it from the body. */
.panel { background: var(--panel); border: 1px solid var(--line); margin-bottom: 14px; }
.panel-head {
  padding: 9px 14px;
  border-bottom: 1px solid var(--line);
  font-size: 10px;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--dim);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.panel-head .big {
  font-family: var(--display);
  font-size: 22px;
  color: var(--accent);
  text-transform: none;
  letter-spacing: .02em;
}
.panel-body { padding: 14px; }

/* A panel that folds. `<details>` rather than a JS toggle, so it opens for a
   no-JS visitor and the closed content is still in the page for search and
   for print. The head doubles as the `<summary>`, so it keeps the panel's own
   border and spacing and only gains the marker and a pointer. */
.panel.fold > summary.panel-head {
  cursor: pointer;
  list-style: none;
  gap: 10px;
}
/* Safari draws its own triangle through a pseudo-element `list-style: none`
   does not reach. */
.panel.fold > summary.panel-head::-webkit-details-marker { display: none; }
.panel.fold > summary.panel-head::after {
  content: "+";
  margin-left: auto;
  font-size: 14px;
  line-height: 1;
  color: var(--dim);
}
.panel.fold[open] > summary.panel-head::after { content: "\2212"; }
.panel.fold[open] > summary.panel-head { border-bottom: 1px solid var(--line); }
/* Closed, the head is the whole panel - a bottom border would draw a line
   under nothing. */
.panel.fold:not([open]) > summary.panel-head { border-bottom: 0; }

/* Team sheet look - column headers uppercase and dim, numbers right-aligned,
   a highlighted total row. Sized to be read at arm's length: this is the one
   table on the verdict page carrying every squad member's numbers. */
table.rated { width: 100%; border-collapse: collapse; font-size: 15px; }
table.rated th {
  text-align: right;
  padding: 7px 10px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .1em;
  font-weight: 400;
  color: var(--dim);
}
table.rated th.l { text-align: left; }
table.rated td { padding: 7px 10px; border-top: 1px solid var(--line); text-align: right; }
table.rated td.l { text-align: left; }
table.rated tr.sub { opacity: .65; }
table.rated tr.total { background: var(--panel-2); font-weight: 700; }
table.rated tr.total td:first-child { color: var(--accent); }
table.rated .rating-final { color: var(--accent); font-weight: 700; }
table.rated .chip-delta.pos { color: var(--green); }
table.rated .chip-delta.neg { color: var(--red); }
table.rated .chip-delta.flat { color: var(--dim); }

/* Standings already had `table.standings`; it never had header styling. */
table.standings th {
  text-align: right;
  padding: 7px 10px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .1em;
  font-weight: 400;
  color: var(--dim);
}
table.standings th.l { text-align: left; }

/* Season hero: the giant simulated record, the outcome banner, the
   game-by-game squares. Named `.verdict-hero` rather than `.hero` because the
   landing page's `<h1 class="hero">` already owns that name in this same
   stylesheet - sharing it leaked the landing headline's display font and
   `clamp()` size onto this panel, and this rule's padding back onto the h1. */
.verdict-hero {
  text-align: center;
  padding: 26px 16px;
  /* Both inherited from `.hero` back when this panel shared that name with the
     landing headline. Accidental, but the kicker and the square legend never
     set a font of their own and were riding on it, so they are restored here
     rather than dropped along with the shared name. */
  font-family: var(--display);
  line-height: .94;
}
.verdict-hero .kicker {
  font-size: 16px;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 10px;
}
.verdict-hero .record {
  font-family: var(--display);
  font-size: 44px;
  letter-spacing: .04em;
  line-height: 1;
}
.verdict-hero .record .w-unit { color: var(--accent); }
.verdict-hero .record .d-unit { color: var(--dim); }
.verdict-hero .record .l-unit { color: var(--red); }

.verdict-hero .banner {
  margin: 14px auto 0;
  max-width: 380px;
  padding: 9px 16px;
  font-family: var(--display);
  font-size: 19px;
  letter-spacing: .14em;
  border: 2px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent);
  transition: opacity .5s;
}
.verdict-hero .banner.tier-europe {
  border-color: var(--blue);
  background: color-mix(in srgb, var(--blue) 10%, transparent);
  color: var(--blue);
}
.verdict-hero .banner.tier-relegated {
  border-color: var(--red);
  background: color-mix(in srgb, var(--red) 10%, transparent);
  color: var(--red);
}
.verdict-hero .banner.tier-mid {
  border-color: var(--line-2);
  background: transparent;
  color: var(--text);
}

.squares { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; margin-top: 16px; }
.square {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 22px;
  padding: 0 8px;
  border-radius: 3px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .02em;
  white-space: nowrap;
  color: var(--ink);
}
.square.sq-W { background: var(--accent); }
.square.sq-D { background: var(--line-2); }
.square.sq-L { background: var(--red); }
/* Added by JS only if it runs - see verdict.html's reveal script. A
   no-JS visitor never sees this class and gets the finished board. */
.square.pending { opacity: 0; transition: opacity .2s; }

.square-legend {
  margin-top: 10px;
  display: flex;
  justify-content: center;
  gap: 16px;
  font-size: 14px;
  /* Stated rather than inherited. The tracking used to arrive from `.hero`'s
     `.03em`, which resolved against *that* rule's clamped 56px font-size and
     so came down the tree as a computed 1.68px - wide tracking on small text
     that nobody picked. This is a chosen number, and being relative it now
     scales with the legend rather than with a headline elsewhere. */
  letter-spacing: .15em;
  color: var(--text-2);
}
.square-legend .dot { display: inline-block; width: 12px; height: 12px; margin-right: 6px; }
.square-legend .dot.sq-W { background: var(--accent); }
.square-legend .dot.sq-D { background: var(--line-2); }
.square-legend .dot.sq-L { background: var(--red); }


/* --- Account ------------------------------------------------------------ */

/* Last thing in .header-right, so it sits at the far right with the How to Play
   chip immediately to its left. It claims no auto margin of its own: the group
   already carries one, and a second would have split the header's free space
   between the two instead of pushing them both right. */
.account-nav {
  flex: 0 0 auto;
  /* The header aligns on the baseline; this is a bordered box, like the chip
     beside it, and reads better centred against the brand. */
  align-self: center;
}

/* A display name can be forty characters. Left to run, it pushes the pair off
   the right edge of a phone header, so it is cut at a width that still holds a
   real name and told to say so with an ellipsis. `nowrap` comes from .nav-chip,
   and an ellipsis needs a block box, which .nav-chip's inline-block gives it. */
.account-nav .nav-chip {
  max-width: 20ch;
  overflow: hidden;
  text-overflow: ellipsis;
}

.account-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.account-form .label { flex: 0 0 auto; }

.account-input {
  flex: 1 1 180px;
  min-width: 0;
  padding: 8px 10px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--text);
  font: inherit;
  font-size: 13px;
}
.account-input:focus {
  outline: none;
  border-color: var(--accent);
}

/* Save and Sign out. They borrowed `.start-draft` and were therefore invisible:
   that class is `display: none` until the landing page's picker is armed, and
   nothing arms a picker on the account page - so the name had no way to be saved
   and the session had no way to be ended.

   The look comes from the `.randomize` rule above, which they are listed in.
   All that is left here is the sizing: Randomize fills a 230px column on a
   centred landing page, and these two sit beside a text field and after a
   paragraph, where a button the width of its own word is what reads right. */
.account-btn {
  flex: 0 0 auto;
  width: auto;
  padding: 9px 18px;
  line-height: normal;
}

/* --- Board rows ---------------------------------------------------------- */

/* A grid rather than a table: the same columns have to hold at 320px, where a
   table's minimum column widths push the points off the edge. Both this and
   the head below need their own width - `.board` centres its children by
   shrinking them to content otherwise, which is how the whole row read
   name-width narrow instead of running the same width as the league tabs
   above it. */
.board-rows {
  width: 100%;
  max-width: 980px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Column labels above the rows. A plain grid rather than a table header cell
   for the same reason `.board-row` is a grid: the column widths have to match
   exactly, and a `<ol>` of grids can't share a `<table>`'s own alignment.

   The name column is capped rather than `1fr`: an open-ended column on a
   980px row with only five other short columns pulled season/drafts/points
   apart from the name they describe, all the way out to the row's right
   edge. Capped, the columns read as one cluster and the unused width just
   sits past the squad link - the same place a `<table>` with none of its
   columns stretched would leave it. */
.board-head {
  display: grid;
  grid-template-columns: 22px minmax(120px, 260px) auto auto;
  gap: 8px;
  width: 100%;
  max-width: 980px;
  padding: 0 0 8px;
  border-bottom: 1px solid var(--line);
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--dim);
}
/* Fixed pixel widths throughout, name column included - not `auto`, which
   soaks up a grid container's leftover space with no `fr` track to share it
   with, and not `minmax()` either. `.board-row` is a grid per `<li>`, not one
   grid shared down the `<ol>` the way a `<table>`'s columns are shared down
   its rows, so a content-sized name track sizes itself off *that row's own
   name* - the next row over, with a shorter or longer one, would then start
   every column after it at a different x position. Fixed widths keep every
   row's tracks identical regardless of content; `justify-content:
   space-between` is what spreads that identical set across the full 980px
   row instead of leaving it clustered against the name with dead space past
   the squad link. */
.board-head-full {
  grid-template-columns: 28px 240px 55px 56px 64px 60px 56px 80px;
  justify-content: space-between;
}

.board-row {
  display: grid;
  grid-template-columns: 22px minmax(120px, 260px) auto auto;
  gap: 8px;
  align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}
.board-rows-full .board-row {
  grid-template-columns: 28px 240px 55px 56px 64px 60px 56px 80px;
  justify-content: space-between;
}
.board-row:last-child { border-bottom: 0; }

/* Zebra striping, the same trick eraball's own leaderboard uses to keep a
   hundred rows readable without a line under every one - `--panel-2` rather
   than eraball's flat black, because this page's ground is `--ink` (#12161f),
   not the pure black eraball sits on, and a darker stripe would vanish here.
   `.mine`'s own background wins regardless of row parity - that highlight
   matters more than the stripe. */
.board-row:nth-child(even) { background: color-mix(in srgb, var(--panel-2) 55%, transparent); }

/* Rank echoes eraball's medal treatment: the display face, sized up, gold
   for first, and the podium's two other metals for second and third. */
.board-rank {
  font-family: var(--display);
  font-size: 15px;
  color: var(--dim);
  text-align: center;
}
.board-rank-1 { font-size: 20px; color: var(--accent); }
.board-rank-2 { font-size: 18px; color: #c0c0c0; }
.board-rank-3 { font-size: 16px; color: #cd7f32; }

/* `.board` centres everything by default; the name is the one column that
   reads left, same as eraball's TEAM column. */
.board-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
  text-align: left;
}
.board-head span:nth-child(2) { text-align: left; }
.board-season { color: var(--text-2); font-size: 11px; }

.board-points {
  font-family: var(--display);
  font-size: 18px;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.board-record { color: var(--text-2); font-size: 11px; font-variant-numeric: tabular-nums; }

/* Ahead of or behind the season's real champion - the one comparison every
   row shares regardless of which season it targeted. */
.board-diff { font-size: 12px; font-variant-numeric: tabular-nums; }
.board-diff.up { color: var(--green); }
.board-diff.down { color: var(--dim); }

.board-rating { color: var(--text-2); font-size: 12px; font-variant-numeric: tabular-nums; }

.board-row.mine { background: var(--panel-2); }
.board-row.mine .board-name { color: var(--accent-lit); }

.board-squad { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; white-space: nowrap; }

.board-more,
.board-back { display: inline-block; margin-top: 10px; font-size: 12px; }

.board-you {
  margin: 0 0 14px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  font-size: 13px;
}
.board-you strong { color: var(--accent); }

/* League tabs on the board reuse `.league`/`.league-grid` from the landing
   page's picker - same boxed look, `.armed` for the one currently open. This
   just widens the row and drops the bottom margin the picker wants but a
   second nav row right below does not. Anchors need what a button gets free:
   centred text, no underline, block so the whole box is clickable. */
.board-leagues { max-width: 980px; margin: 0 0 4px; }
.board-leagues a.league {
  display: block;
  text-align: center;
  text-decoration: none;
  line-height: normal;
}

/* Left-aligned rather than centred like everything else on `.board`: this
   row and the league tabs above it both run the table's own 980px width, and
   a filter reads as a filter when it starts where the rows it filters start,
   not floating in the middle of the page. */
.board-weeks {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-start;
  width: 100%;
  max-width: 980px;
  margin: 0 0 10px;
}

.board-week {
  padding: 7px 16px;
  font-family: var(--display);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: .04em;
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--dim);
}
/* Only the year row's entries are links - "This week" above is still a bare
   span with nothing else to switch to yet. */
a.board-week {
  text-decoration: none;
  transition: color .12s, background .12s, border-color .12s;
}
a.board-week:hover {
  color: var(--text);
  border-color: var(--line-2);
}
.board-week.active {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--panel-2);
}

@media (max-width: 460px) {
  /* Rank, name and points are the row; season, record, diff, rating and the
     share link are all context a phone-width column doesn't have room to
     state. The header row sheds the same five so its three remaining labels
     stay lined up over what is actually still there. */
  .board-rows-full .board-row,
  .board-head-full { grid-template-columns: 24px 1fr auto; }
  .board-rows-full .board-season,
  .board-rows-full .board-record,
  .board-rows-full .board-diff,
  .board-rows-full .board-rating,
  .board-rows-full .board-squad,
  .board-head-full span:nth-child(3),
  .board-head-full span:nth-child(5),
  .board-head-full span:nth-child(6),
  .board-head-full span:nth-child(7),
  .board-head-full span:nth-child(8) { display: none; }
}

/* The board control on the verdict, set under the share panel so the two read
   as one column of afterwards-actions. */
.board-panel { margin-top: 14px; }
.board-panel .lede { margin: 0; font-size: 13px; color: var(--text-2); }
.board-panel strong { color: var(--accent); }
.board-panel form .lede { margin-bottom: 8px; }

/* The achievements grid: locked mockup at
   .superpowers/brainstorm/10842-1786456618/content/achievement-cards-v3.html.
   Tier colors reuse app/tags.py's LEGEND semantic families (gold = won,
   green = done on the pitch, blue = squad combination) rather than the
   player-card rarity colors, which already mean something else there -
   purple is "legendary" on a player-card, gold is "elite". */
.ach-section { margin-top: 24px; }
.ach-section:first-of-type { margin-top: 16px; }
.ach-section-title {
  font-family: var(--display);
  font-size: 15px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--tier-color);
  margin-bottom: 8px;
}
.ach-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}
.ach-card {
  background: var(--panel);
  border: 1px solid var(--line);
  padding: 14px 16px;
  position: relative;
  overflow: hidden;
}
.ach-card.earned { border-color: var(--tier-color); }
.ach-card.locked { background: #161b26; border-color: #232a38; opacity: .55; }
.ach-top { margin-bottom: 6px; }
.ach-name {
  font-family: var(--display);
  font-size: 17px;
  letter-spacing: .06em;
  color: var(--text);
  line-height: 1.1;
}
.locked .ach-name { color: var(--dim); }
.ach-desc { font-size: 11px; color: var(--text-2); line-height: 1.5; }
.locked .ach-desc { color: var(--dim); }
.ach-dot {
  position: absolute;
  top: 8px;
  right: 10px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--tier-color);
  opacity: .7;
}
.tier-legendary { --tier-color: var(--gold); }
.tier-epic { --tier-color: var(--green); }
.tier-rare { --tier-color: var(--blue); }
.tier-common { --tier-color: var(--dim); }

/* The shirt collection grid, structurally next to .ach-grid above but
   grouped into four tier sections rather than one mixed grid, and never
   hiding a locked name - the grind is the point, so seeing who you're
   chasing and how close you are matters more than a surprise would. Tier
   colors read as a rarity ramp: gold (icon) > purple (legend) > blue (elite)
   > the base app accent (star, the most common tier). `--tier-color` is set
   once on `.shirt-section` and inherited by every card and its progress text
   underneath, the same way a custom property cascades to any element that
   doesn't set its own. */
.shirt-section { margin-top: 24px; }
.shirt-section:first-of-type { margin-top: 16px; }
.shirt-section-title {
  font-family: var(--display);
  font-size: 15px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--tier-color);
  margin-bottom: 8px;
}
.shirt-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 10px;
}
.shirt-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 3px;
  overflow: hidden;
}
.shirt-card.earned { border-color: var(--tier-color); }
.shirt-card.locked { border-color: #232a38; }

.shirt-swatch { aspect-ratio: 1 / 1; position: relative; background: var(--kit-bg); }
.locked .shirt-swatch { filter: grayscale(85%) brightness(.55); }

.shirt-jersey-name {
  position: absolute;
  top: 8%;
  left: 0;
  right: 0;
  text-align: center;
  font-family: "Arial Black", var(--display), Impact, sans-serif;
  font-weight: 900;
  font-size: 12px;
  letter-spacing: .04em;
  color: var(--kit-color);
}
.shirt-jersey-number {
  position: absolute;
  top: 26%;
  left: 0;
  right: 0;
  text-align: center;
  font-family: "Arial Black", Impact, sans-serif;
  font-weight: 900;
  font-size: 58px;
  line-height: 1;
  -webkit-text-stroke: 2.5px var(--kit-color);
  color: transparent;
}

.shirt-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 5px 7px;
  gap: 4px;
  background: var(--panel);
}
.locked .shirt-strip { background: #161b26; }
.shirt-player-name {
  font-size: 9.5px;
  color: var(--text);
  font-family: var(--display);
  letter-spacing: .02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.locked .shirt-player-name { color: var(--text-2); }
.shirt-progress {
  font-size: 9.5px;
  font-weight: 700;
  color: var(--tier-color);
  flex-shrink: 0;
}
.locked .shirt-progress { color: var(--dim); }
.tier-icon { --tier-color: var(--gold); }
.tier-legend { --tier-color: var(--purple); }
.tier-elite { --tier-color: var(--blue); }
.tier-star { --tier-color: var(--accent); }
