/* Astvision draft - dark UI tuned for phones first, since almost everyone
   will open this on a handset from a Telegram link. */

/* Two faces, imported here rather than linked from six page heads: every page
   already loads this file, so this is the one place the pair can be changed.
   Both fall back to a system stack, which is what an event held somewhere with
   no signal gets - the layout is sized in ems and holds either way.

   Golos Text carries Cyrillic properly, which most system UI stacks on Windows
   only half do; JetBrains Mono is for anything read as a number - scorelines,
   clock faces, standings columns - where the digits have to line up down a
   column and a 1 must not be half the width of an 8. */
@import url('https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap');

:root {
  --bg:        #161921;
  /* Not a flat fill: the board is long, and a light behind the top of it gives
     the page a horizon to hang the header off rather than a wall of black. */
  --bg-glow:   radial-gradient(120% 80% at 50% 0%, #202736 0%, #161921 60%);
  --surface:   #1a202a;   /* cards */
  --surface-2: #1c2330;   /* inputs, and panels inside cards */
  --surface-3: #212836;   /* chips, and the hover state of a row */
  --line:      #29313f;   /* the hairline between everything */
  --line-2:    #313849;   /* a border meant to be seen: inputs, buttons */
  --line-soft: #232936;   /* between rows of one table, quieter than --line */
  --text:      #e6e9f0;
  --text-2:    #c3c9d6;   /* body copy inside a card */
  --muted:     #9ca4b8;
  --muted-2:   #7c849b;   /* column headings, legends */
  --muted-3:   #68718a;   /* a value that is not there yet */
  --accent:    #ff7a2f;
  --accent-2:  #ff9d4d;   /* the accent on a dark fill, where the pure one glares */
  --accent-dim:#493222;   /* the accent as a border - a live match, an admin tag */
  --accent-bg: #312314;   /* the accent as a fill behind its own text */
  --gold:      #c98a4b;
  --blue:      #5b8cff;
  --good:      #4ade80;
  --bad:       #f4635b;
  --radius:    14px;
  --shadow:    0 2px 12px rgba(0,0,0,.35);
  --bar-h:     60px;     /* the sticky header, so the side panel can clear it */
  --sans: 'Golos Text', system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 var(--sans);
  -webkit-text-size-adjust: 100%;
  padding-bottom: env(safe-area-inset-bottom);
}

/* The glow belongs to the page rather than to the viewport, so it stays put at
   the top of a long schedule instead of following the scroll down it. */
body::before {
  content: ''; position: fixed; inset: 0; z-index: -1;
  background: var(--bg-glow);
}

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

/* Left to itself the scrollbar is the one piece of desktop chrome that stays
   light, and on a page this dark it reads as a seam down the edge. */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 8px; }
::-webkit-scrollbar-thumb:hover { background: #414c61; }

/* ---------- layout ---------- */

.wrap { max-width: 980px; margin: 0 auto; padding: 16px; }
.wrap--narrow { max-width: 420px; }

header.bar {
  position: sticky; top: 0; z-index: 20;
  /* Translucent rather than solid: the board scrolling under a bar you can
     half see through is what keeps a sticky header from reading as a lid. */
  background: rgba(22, 25, 33, .86);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
  padding: 0 20px; height: var(--bar-h);
  display: flex; align-items: center; gap: 12px;
}
/* The name is the one thing in the bar that may be squeezed. On a 375px phone
   a long Mongolian name plus a badge and two buttons does not fit, and without
   this the last button is pushed off the right edge and the whole page scrolls
   sideways to reach it. Everything else holds its size; the name ellipsises,
   which is why the mark is placed in reserved padding rather than as a flex
   sibling: a flex row has no single text run left to put an ellipsis on. */
header.bar h1 {
  position: relative; padding-left: 38px;
  font-size: 16px; margin: 0; font-weight: 600; letter-spacing: -.01em;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The mark, drawn rather than shipped: one letter on the accent gradient. A
   pseudo-element because it belongs to every page's header and none of them
   should have to carry a copy of it in their markup. */

header.bar .spacer { flex: 1; }

/* Who is signed in, drawn as a pill with their own disc on the end - the same
   colour the chat gives them, so a name is the same colour wherever it appears.
   The disc is a pseudo-element for the same reason the mark is. */
header.bar .who {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 4px 5px 4px 12px;
  border: 1px solid var(--line-2); border-radius: 999px;
  color: var(--text-2); font-size: 13px;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
header.bar .who::after {
  content: ''; flex: 0 0 auto;
  width: 24px; height: 24px; border-radius: 50%;
  /* As .avatar: --av is the whole background, framed by whoever built it. */
  background: var(--av, linear-gradient(160deg, #5b8cff, #2f4fa8));
}
header.bar .who:empty { display: none; }

header.bar .badge,
header.bar .who,
header.bar button,
header.bar .btn { flex: 0 0 auto; }

/* The nav link reads as a pill in the bar rather than as a bordered button:
   there is only ever one of them, and a button competes with the mark. */
header.bar .btn.ghost {
  border-color: transparent; background: var(--surface-3);
  padding: 7px 12px; font-weight: 500; color: var(--text);
}
header.bar .btn.ghost:hover { background: var(--line); }
header.bar .logout { border-color: transparent; background: none; color: var(--muted); padding: 7px 8px; }
header.bar .logout:hover:not(:disabled) { color: var(--text); filter: none; }

@media (max-width: 520px) {
  header.bar { gap: 8px; padding: 0 12px; }
  header.bar h1 { font-size: 15px; padding-left: 34px; }
  header.bar h1::before { width: 26px; height: 26px; border-radius: 8px; font-size: 14px; }
  /* The disc alone on a phone: the name is already in the draft board and the
     bar has four other things competing for 375px. */
  header.bar .who { padding: 3px; border-color: transparent; font-size: 0; gap: 0; }
  /* The phase badge is the first thing to go. Six controls do not fit 375px,
     and on the tournament board the title block underneath already says which
     phase the evening is in, in bigger type and with more of the story. */
  header.bar .phase-badge { display: none; }

  /* Signed in on a public page, the bar carries the most of any in the app: a
     nav link, who you are, a way back into the event and a way out of it. That
     is 420px of controls on a 375px phone. The wordmark goes (the mark stays,
     and the page under it says the name in 24px type) and so does the name
     pill, which the two buttons beside it already imply. Scoped to the signed-
     in public bar rather than done to every header: a visitor is the common
     case, has three fewer controls, and keeps the name. */
  header.bar--public { gap: 6px; }
  header.bar--signed h1 { font-size: 0; padding-left: 26px; }
  header.bar--signed .who { display: none; }
}

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px 22px 22px;
  margin-bottom: 14px;
}

h2 { font-size: 16px; margin: 0 0 12px; font-weight: 600; }
.muted { color: var(--muted); }
.small { font-size: 13px; }
.center { text-align: center; }

/* Anything read as a number rather than as a word. Applied by hand rather than
   to every digit on the page: a phone number in a chat line is prose, a
   scoreline is data, and only the second wants to be monospaced. */
.mono, .num, td.num, th.num,
.rtime, .ftally, .fscore, .fmapscore, .vstep,
.standings .place, .standings .pts {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}

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

button, .btn {
  font: inherit; font-weight: 600;
  background: var(--surface-2);
  color: var(--text);
  /* .btn is worn by anchors as often as by buttons - the nav pills in the bar
     are links - and an anchor arrives underlined. A pill with a rule under its
     label reads as a link that happens to sit in a box. */
  text-decoration: none;
  border: 1px solid var(--line-2);
  border-radius: 9px;
  padding: 9px 14px;
  cursor: pointer;
  transition: filter .12s, background .12s, border-color .12s;
}
button:hover:not(:disabled) { filter: brightness(1.18); }
button:disabled { opacity: .45; cursor: not-allowed; }

button.primary { background: var(--accent); border-color: var(--accent); color: #14100b; }
button.primary:hover:not(:disabled) { background: #ff8f4d; filter: none; }
button.danger  { background: transparent; border-color: var(--bad); color: var(--bad); }
button.ghost   { background: transparent; }
button.wide    { width: 100%; }

input, select, textarea {
  font: inherit;
  width: 100%;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--line-2);
  border-radius: 9px;
  padding: 10px 12px;
}
input::placeholder, textarea::placeholder { color: var(--muted-3); }
/* The accent itself, not the dim brown it borders with: a focus ring has to be
   seen from across the room by whoever is filling in scores. */
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }
label { display: block; font-size: 13px; color: var(--muted); margin: 12px 0 6px; }

.row { display: flex; gap: 8px; flex-wrap: wrap; }
.row > * { flex: 1; }

/* ---------- badges and notices ---------- */

/* A label rather than a shout: mono, small caps, and hollow. The filled
   variants below are for the two states worth a colour. */
.badge {
  display: inline-block;
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase;
  padding: 3px 7px; border-radius: 5px;
  background: transparent; color: var(--muted);
  border: 1px solid var(--line-2);
}
.badge.accent { background: var(--accent); color: #14100b; border-color: var(--accent); }
.badge.good   { background: #1d3327; color: var(--good); border-color: #2b4e38; }
.badge.bad    { background: rgba(244,99,91,.14); color: var(--bad); border-color: transparent; }

.notice {
  border-radius: 10px; padding: 10px 12px; margin-bottom: 12px;
  font-size: 14px;
  background: rgba(255,122,47,.10); border: 1px solid var(--accent-dim); color: #ffd0ab;
}
.notice.bad  { background: rgba(244,99,91,.12); border-color: var(--bad); color: #ffb3ae; }
.notice.good { background: rgba(74,222,128,.10); border-color: #2b4e38; color: #a6ebc3; }

/* ---------- progress ---------- */

.progress { height: 6px; background: var(--line); border-radius: 999px; overflow: hidden; }
.progress > i { display: block; height: 100%; background: var(--accent); transition: width .3s; }

/* ---------- rating list ---------- */

.rate-item {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 0; border-bottom: 1px solid var(--line);
}
.rate-item:last-child { border-bottom: 0; }
.rate-item .name { flex: 1; font-weight: 500; min-width: 0; overflow-wrap: anywhere; }
.rate-item.done .name { color: var(--muted); }

/* Score buttons: a horizontal scroller so ten targets stay tappable on a
   narrow phone instead of shrinking into unhittable slivers. */
.scores { display: flex; gap: 4px; overflow-x: auto; padding-bottom: 2px; scrollbar-width: none; }
.scores::-webkit-scrollbar { display: none; }
.scores button {
  flex: 0 0 auto;
  width: 34px; height: 38px; padding: 0;
  border-radius: 8px; font-size: 14px;
}
.scores button[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #221600; }

/* ---------- team crest ----------

   The crest goes in front of the team's name on every board in the app - the
   draft cards, the turn banner, the table, the bracket, the podium, the veto -
   so it is one rule sized per context through --crest rather than a class per
   site that would drift apart the first time one of them was touched.

   Masked to a circle and cropped to the box rather than trusting whatever
   framing the file arrives with: a captain uploads whatever their phone had.

   align-self opts it out of the row's baseline, which an image would otherwise
   join by its bottom edge, dragging the name beside it out of line. */
/* Rounded square, not a circle, and that distinction carries meaning: a team is
   a square everywhere in the app, a person is a circle. Once the two are told
   apart by shape, a row of crests and a row of faces never have to be read
   twice to work out which is which. */
.tlogo {
  flex: 0 0 auto; align-self: center;
  display: inline-block; vertical-align: middle;
  width: var(--crest, 20px); height: var(--crest, 20px);
  border-radius: calc(var(--crest, 20px) / 3.6);
  /* contain, not cover: a crest is an emblem of whatever shape its owner drew
     it, and cover fills the square by cutting the rest off - which on a wide
     wordmark means throwing away the word. Fitting it leaves transparent space
     top and bottom instead, and since nothing is painted behind it any more,
     that space is simply the card. A photograph wants cover; a logo does not. */
  object-fit: contain;
  /* Nothing behind it and nothing around it, for the reason the faces have
     nothing either: a crest is usually a cut-out - a white mark on nothing -
     and any colour set here shows through it as a tinted box the shape of the
     crop rather than as the emblem somebody uploaded. A crest with its own
     backdrop still fills the box, so this costs those nothing. */
}

/* A knockout side nobody has qualified for yet has no crest. It still takes the
   box, so the row lines up with the ones above and below that do. */
.tlogo.none { border: 1px dashed var(--line-2); background: transparent; }

/* A person: their photograph through --av, or failing that a disc tinted from
   their own name. Used by the chat, the presence chips and the header, so one
   player is one face - and one colour when they have no face - all evening. */
.avatar {
  flex: 0 0 auto;
  display: inline-block; vertical-align: middle;
  width: var(--face, 26px); height: var(--face, 26px);
  border-radius: 50%;
  /* The whole background, not just the image: a photograph arrives with the
     size and position that frame a head in it, and a gradient arrives bare. */
  background: var(--av, linear-gradient(150deg, #5b8cff, #2f4fa8));
}

/* Flex rows space the crest with their own gap. These three are inline text,
   where nothing else is doing it. */
table.standings .tlogo,
.turn .big .tlogo,
.veto .vstatus .tlogo { margin-right: 6px; }

/* ---------- draft board ---------- */

/* Wide enough for the whole of a player row on one line: a nick, the roster
   name after it, a Premier rating and an average. It was 280px before the
   ladders existed, and at that width a third of the rows wrapped -
   which left the six cards at different heights, the thing the empty slots
   further down exist to prevent. Fewer columns and a taller board is the price
   of every row reading the same way. */
.teams { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); }

.team { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px; }
.team.on-clock { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), var(--shadow); }
.team header { display: flex; align-items: baseline; gap: 8px; margin-bottom: 10px; --crest: 22px; }
.team header .tname { font-weight: 700; font-size: 14px; }

/* The captain's way into renaming their own team. Quiet until it is wanted:
   six of these across a board would otherwise read as the point of the card. */
.team header .tedit {
  flex: 0 0 auto; padding: 2px 6px; font-size: 12px; line-height: 1.2;
  background: none; border: 1px solid var(--line); color: var(--muted);
}
.team header .tedit:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }

/* ---------- the team editor ----------

   Drawn inside the card it belongs to rather than as a dialog, so it is obvious
   which of six teams is being renamed - and so there is no overlay to position
   on a phone. */

.tedit-panel {
  margin: 0 0 10px; padding: 10px;
  background: var(--bg); border: 1px solid var(--line); border-radius: 10px;
}
.tedit-panel label { margin-top: 0; }
.tedit-panel .tcount { text-align: right; margin-top: 4px; font-variant-numeric: tabular-nums; }
.tedit-panel .tlogo-label { margin-top: 10px; }
.tedit-panel p { margin: 8px 0 0; }

/* A scroller rather than a wrapping grid: the crest folder is whatever somebody
   dropped into it, and forty of them must not push the Save button off screen. */
.logo-grid {
  display: flex; gap: 8px; margin-top: 6px;
  overflow-x: auto; padding-bottom: 4px; scrollbar-width: none;
}
.logo-grid::-webkit-scrollbar { display: none; }

/* A tile is a button, so it arrives with the fill and the hairline every
   button has - behind a cut-out crest that is a filled disc where the emblem
   should be, at the one place in the app somebody is choosing between crests
   and needs to see them as they will actually be drawn. Both go; the ring that
   marks the chosen one is the box-shadow, which does not need a border. */
.logo-opt {
  flex: 0 0 auto; padding: 4px; border: 0; background: none;
  border-radius: 50%; line-height: 0;
}
/* Fitted, and a rounded square rather than a circle - the shape a team is
   everywhere else in the app. A circular mask over a wide crest clips the ends
   off the one picture somebody is trying to look at before choosing it. */
.logo-opt img { width: 40px; height: 40px; border-radius: 11px; object-fit: contain; }
.logo-opt.selected { box-shadow: 0 0 0 2px var(--accent); }

/* A file input styled as a button: the control browsers give you cannot be, and
   an unstyled one next to these looks like a bug. */
.logo-upload { display: inline-block; margin-top: 10px; }

.tedit-actions { display: flex; gap: 8px; margin-top: 12px; }
.tedit-actions button { flex: 1; }

.team header .total { margin-left: auto; color: var(--accent); font-weight: 700; font-variant-numeric: tabular-nums; }
.team ol { list-style: none; margin: 0; padding: 0; }
.team li { display: flex; gap: 8px; padding: 6px 0; border-top: 1px solid var(--line); font-size: 14px; }
.team li:first-child { border-top: 0; }
.team li .pn { flex: 1; overflow-wrap: anywhere; }
.team li .pv { color: var(--muted); font-variant-numeric: tabular-nums; }
.team li.captain .pn { font-weight: 700; }
.team li.empty { color: var(--muted); font-style: italic; }

/* ---------- available pool ---------- */

/* Wide enough for a nick, the roster name after it, two badges and an average
   on one line. It was 200px before those existed, and at that width every tile
   wrapped to three lines - which made the pool TALLER despite fitting more
   across, because six columns of 90px beats four columns of 45px. Fewer, wider,
   shorter is the trade. */
.pool { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); }
.pool button {
  display: flex; align-items: center; gap: 8px;
  text-align: left; padding: 10px 12px;
}
.pool button .pn { flex: 1; overflow-wrap: anywhere; font-weight: 500; }
.pool button .pv { color: var(--accent); font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---------- turn banner ---------- */

.turn {
  border-radius: var(--radius); padding: 14px 16px; margin-bottom: 14px;
  background: var(--surface); border: 1px solid var(--line);
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.turn.mine { background: linear-gradient(180deg, rgba(245,166,35,.18), rgba(245,166,35,.06)); border-color: var(--accent); }
.turn .big { font-size: 17px; font-weight: 700; --crest: 24px; }

/* ---------- tables ---------- */

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--line-soft); }
th {
  font-family: var(--mono); color: var(--muted-2); font-weight: 400;
  font-size: 10px; text-transform: uppercase; letter-spacing: .08em;
  border-bottom-color: var(--line);
}
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ---------- tournament: standings ----------

   Nine columns of numbers do not fit a phone, so the table scrolls inside its
   own box and the two lowest-value columns drop out below 560px. What survives
   is what people actually read off a table: played, won, lost, difference and
   points. */

.tscroll { overflow-x: auto; }

table.standings { min-width: 460px; }
table.standings td {
  vertical-align: middle; --crest: 26px;
  padding: 11px 6px; color: var(--text-2);
}
table.standings .place { color: var(--muted-3); font-weight: 600; width: 1%; }
table.standings .sname { font-weight: 500; font-size: 14px; }
/* Indented past the crest, so the captain's name hangs under the team's rather
   than under the picture in front of it. */
table.standings .scap { display: block; margin-left: calc(var(--crest) + 10px); }
table.standings .tlogo { margin-right: 10px; }
table.standings .pts { color: var(--muted-3); font-weight: 700; }
/* Orange only once there is something to count. A column of zeroes in the
   accent colour makes an empty table look like a result. */
table.standings tr.scored .pts { color: var(--accent-2); }
table.standings td.good { color: var(--good); }
table.standings td.bad  { color: var(--bad); }
table.standings tbody tr:hover td { background: var(--surface-3); }

/* The qualifying cut, drawn on the row rather than as a line under it: teams
   move between these bands as results land, and a border in the table body
   would have to be moved by JS to keep up. */
table.standings tr.qual td,
table.standings tr.bye  td { background: #1e2535; }
table.standings tr.qual .place { color: var(--blue); }
table.standings tr.bye  .place { color: var(--accent); }
table.standings tr.qual td:first-child { box-shadow: inset 3px 0 0 var(--blue); }
table.standings tr.bye  td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

/* Win / draw / loss, three narrow boxes on one line under a single label. */
.pts3 > input { flex: 1 1 0; min-width: 0; text-align: center; padding: 11px 4px; }

/* The pot and its currency: one wide box and one narrow one. The symbol is
   four characters at most, so it gets exactly the width that needs. */
.prizerow2 { flex-wrap: nowrap; }
.prizerow2 > #prize_pool { flex: 1 1 auto; min-width: 0; }
.prizerow2 > #prize_currency { flex: 0 0 56px; text-align: center; padding: 11px 4px; }

/* The admin's preview of the prize table, under the boxes that produce it. */
.prizeprev:not(:empty) { margin-top: 10px; }
.prizeprev .prow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 10px; align-items: baseline;
  padding: 6px 0; border-top: 1px solid var(--line);
  font-size: 13px;
}
.prizeprev .prow:first-child { border-top: 0; }
.prizeprev .pct { min-width: 44px; text-align: right; }

.swatch { display: inline-block; width: 10px; height: 10px; border-radius: 3px; vertical-align: -1px; }
.swatch.bye  { background: var(--accent); }
.swatch.qual { background: var(--line); }
.legend { margin-bottom: 0; }

@media (max-width: 560px) {
  table.standings { min-width: 340px; }
  table.standings .opt { display: none; }
}

/* ---------- tournament: the info panel ----------

   The board's left-hand column: what the tournament is, in the shape a wiki
   infobox uses. One column on a phone with the facts on top of the fixtures,
   two columns once there is room for both.

   The split is at 1200px rather than at the 1000px the chat panel docks at.
   Between the two the chat has already taken its column, and carving another
   260 off what is left would squeeze the standings table - nine columns of it
   - down to a horizontal scroll on a screen wide enough not to need one.

   The panel is drawn to the RIGHT of the board, by order rather than by
   markup: stacked, it belongs above the fixtures - it is what the board is
   about - and that is also the order a screen reader should meet it in. */

/* ---------- tournament: the page title ----------

   What this event is and where it has got to, above the board rather than
   inside it. The header bar names the app; this names the tournament, which is
   the thing somebody arriving from a Telegram link wants confirmed first.

   It is a band of its own, outside .wrap, so that once the chat docks beside
   the board the title still runs the full width above both columns instead of
   starting halfway across the page. The band therefore has to reproduce the
   two widths .wrap can take, and align to the chat panel's left edge - hence
   the 16px padding, which is the margin .side carries. */

.theadband { max-width: 980px; margin: 0 auto; padding: 0 16px; }
@media (min-width: 1000px) { .theadband { max-width: 1364px; } }

/* Title first, state beside it: the name of the event is what the page is,
   and the round it is on is a note about the name rather than a heading over
   it. On a phone the note wraps under the title on its own line. */
.thead {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px 18px;
  padding: 24px 0 20px; border-bottom: 1px solid var(--line);
}
.thead:empty { display: none; }
.thead .tstatus {
  display: flex; align-items: center; flex-wrap: wrap; gap: 16px;
  font-family: var(--mono); font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
}
.thead .tlive { display: inline-flex; align-items: center; gap: 6px; color: var(--good); }
.thead .tlive > i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
/* Before the first map and after the last, the same row reports a state rather
   than a heartbeat, so it drops to the muted colour. */
.thead .tlive.idle { color: var(--muted); }
.thead .tlive.over { color: var(--gold); }
/* Results are in but no map is being played right now - between rounds, or
   between a Saturday and the next one. The accent rather than the live green,
   because it is the tournament that is running and not a match. */
.thead .tlive.on { color: var(--accent); }
.thead .twhere { color: var(--muted); }
.thead h1 { margin: 0; font-size: 34px; line-height: 1.2; font-weight: 700; letter-spacing: -.025em; }

@media (max-width: 700px) {
  .thead { padding: 18px 0 14px; gap: 4px 14px; }
  .thead h1 { font-size: 24px; }
}

.tlayout { display: grid; gap: 0 16px; align-items: start; }
/* A grid item's min-width is auto, which means "never narrower than your widest
   unbreakable content". The info panel's rows are label-plus-value pairs in a
   monospaced face, and their combined min-content width is about 390px - so on
   a 375px phone the column refused to shrink and took the whole page sideways
   with it. Nothing inside needs that floor: the values wrap. */
.tlayout > * { min-width: 0; }
.tinfo:empty { display: none; }
/* Stacked, the panel keeps its own proportions rather than stretching a
   two-word label away from its value across a whole desktop. Wider than the
   column it becomes at 1200px, so this never fights the grid. */
.tinfo { max-width: 560px; }

@media (min-width: 1200px) {
  .tlayout { grid-template-columns: minmax(0, 1fr) 264px; }
  .tmain { order: 1; }
  .tinfo { order: 2; }
  /* Sticky, because the schedule below it is long and the format the fixtures
     are read against should not scroll away from them. Its own overflow, so a
     panel taller than the screen can still reach its last row. */
  .tinfo {
    position: sticky;
    top: calc(var(--bar-h) + 12px);
    max-height: calc(100vh - var(--bar-h) - 24px);
    overflow-y: auto;
    /* Room for the shadow the cards inside cast, which the overflow would
       otherwise clip along the left edge. */
    padding: 2px;
    margin: -2px;
  }
}

.infobox h2 { margin-bottom: 14px; }
.prizes h2 { margin: 0; }

/* Label left, value right, on one line each. minmax(0, auto) on the value so a
   long event name wraps inside its own column instead of pushing the label
   off the card. */
.infobox .irow {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 4px 16px; align-items: baseline;
  padding: 8px 0; border-bottom: 1px solid var(--line-soft);
  font-size: 12.5px;
}
.infobox .ilabel { color: var(--muted); }
/* Mono, because every one of these is read as a value rather than as prose -
   a date, a count, a points split - and they line up down the right edge. */
.infobox .ivalue {
  text-align: right; font-family: var(--mono); font-size: 12.5px;
  overflow-wrap: anywhere;
}

/* The notes under the rows: the caveat about the clock and the one about how
   points are counted. Muted and small - they are read once. */
.infobox .inote, .prizes .inote {
  margin: 10px 0 0; padding-top: 10px; border-top: 1px solid var(--line);
}
.infobox .inote + .inote { border-top: 0; padding-top: 0; margin-top: 6px; }

/* ---------- tournament: the prize pool ---------- */

/* The pot sits on the heading's line rather than under it as a banner: it is
   the subtitle of "Prizes", not a second heading, and the split below is what
   the card is actually for. */
.prizes .phead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-bottom: 18px;
}
.prizes .ppool {
  font-family: var(--mono); font-size: 12px; font-weight: 500; color: var(--accent-2);
}

/* Place and medal on the left, money on the right, the team underneath once
   there is one. The second row is a span of the whole width rather than a
   fourth column, so a long team name never squeezes the amount. */
.prizes .prizerow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px 10px; align-items: baseline;
  padding: 0 0 12px;
  --crest: 20px;
  /* Each placement carries its own colour - gold, silver, bronze - and the bar
     and the amount both read it from here rather than each being told twice.
     Keyed off the placement rather than off the row's position in the card:
     the heading is a child too, and counting siblings put every medal one
     place out. */
  --pc: var(--muted);
}
.prizes .prizerow.place-1 { --pc: var(--accent-2); }
.prizes .prizerow.place-2 { --pc: #b8c0d0; }
.prizes .prizerow.place-3 { --pc: var(--gold); }
.prizes .pplace  { color: var(--text-2); font-size: 13px; }
.prizes .pmedal  { font-size: 15px; }
.prizes .pamount { font-family: var(--mono); font-size: 13px; font-weight: 600; color: var(--pc); }

/* The share as a bar as well as a number. Two placements at 65/35 are a
   sentence; two bars are a picture of the same thing, and the picture is what
   somebody scanning the card actually takes in. */
.prizes .pbar {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 9px; margin-top: 7px;
}
.prizes .pbar > .ptrack {
  flex: 1; height: 4px; border-radius: 4px; background: var(--line); overflow: hidden;
}
.prizes .pbar > .ptrack > i { display: block; height: 100%; background: var(--pc); }
.prizes .ppct {
  font-family: var(--mono); font-size: 11px; color: var(--muted-2);
  width: 34px; text-align: right;
}
.prizes .pwinner {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 6px;
  margin-top: 7px;
}
.prizes .pwname  { font-weight: 600; font-size: 13px; }
/* A placement somebody has actually won. The tint is what separates the two
   halves of this table at a glance once the final is over. */
.prizes .prizerow.won .pwname { color: var(--good); }

/* ---------- tournament: podium ---------- */

.podium .prow {
  display: flex; align-items: baseline; gap: 10px;
  padding: 8px 0; border-top: 1px solid var(--line);
  --crest: 22px;
}
.podium .prow:first-of-type { border-top: 0; }
.podium .pmedal { font-size: 20px; line-height: 1; }
.podium .plabel { color: var(--muted); font-size: 13px; min-width: 54px; }
.podium .pteam  { font-weight: 700; }
.podium .pcap   { margin-left: auto; }

/* ---------- tournament: fixtures ---------- */

.round .rhead { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.round .rhead h2 { margin: 0; }
.round .rhead .spacer { flex: 1; }
.round .rhead .rcount { font-family: var(--mono); font-size: 11px; color: var(--muted); }

/* How far through the round the evening is, as a bar rather than only as
   "1 / 3". The count answers it precisely; the bar answers it at a glance from
   the other side of the room, which is how this board is usually read. */
.round .rbar {
  height: 3px; border-radius: 3px; background: var(--line);
  margin: 14px 0 18px; overflow: hidden;
}
.round .rbar > i { display: block; height: 100%; background: var(--accent); transition: width .3s; }

/* The round's kick-off, beside its heading. Tabular figures so the times line
   up down the page - a column of clock faces that wobbles is harder to scan
   than one that does not. */
.round .rhead .rtime {
  flex: 0 0 auto;
  font-size: 13px; font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

/* A map's own time. The maps of a best-of-two are an hour apart, so the map
   rows carry theirs; it claims the left gutter under the map number rather
   than a column, which would pull the scores off centre. */
.fixture .fmaptime {
  grid-column: 1; justify-self: start;
  padding-left: 18px;
  font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.fixtures { display: grid; gap: 8px; }

/* A fixture is the two teams once, then one row per map. A best-of-two puts two
   scorelines under one pair of names; a single-map fixture uses the same shape
   without the tally, so the knockout still reads as one line. */
.fixture {
  padding: 12px 14px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 11px;
}
.fixture.done { border-color: var(--line-2); }

/* The match being played right now. It gets the accent as a border and a
   warmth behind it, so a schedule of nineteen fixtures says which one the room
   is watching without anybody having to find the round heading first. */
.fixture.live {
  border-color: var(--accent-dim);
  background: linear-gradient(150deg, #251c15, var(--surface-2));
}

.fixture .fhead { display: flex; align-items: center; gap: 12px; }
/* The crest leads on both sides, the away one included - so the eye finds the
   same thing in the same order whichever half of the fixture it lands on. The
   right-hand cell pushes the pair to its edge rather than reversing them. */
.fixture .fteam {
  flex: 1; min-width: 0; font-size: 14px;
  display: flex; align-items: center; gap: 9px;
  --crest: 24px;
}
.fixture .fteam .fname {
  min-width: 0; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fixture .fteam.right { justify-content: flex-end; text-align: right; }
.fixture .fteam.won { font-weight: 700; color: var(--accent); }
.fixture .fteam.tbd { color: var(--muted); font-style: italic; }
/* A name with nowhere to wrap needs somewhere to break on a phone, where the
   ellipsis would otherwise eat most of it. */
@media (max-width: 560px) {
  .fixture .fteam .fname { white-space: normal; overflow-wrap: anywhere; }
}

/* The maps tally. Sits between the names where the score would be on a
   one-map fixture, so the eye finds the result in the same place either way -
   a chip rather than bare digits, because it is the one number on the card. */
.fixture .ftally {
  flex: 0 0 auto;
  font-family: var(--mono); font-size: 15px; font-weight: 700;
  letter-spacing: .04em; font-variant-numeric: tabular-nums;
  color: var(--muted-3);
  padding: 3px 12px; border-radius: 7px;
}
.fixture.live .ftally { color: var(--accent-2); background: var(--accent-bg); }
.fixture.done .ftally { color: var(--text); }
.fixture .ftally.idle { color: var(--muted-3); font-weight: 600; }

/* The compact fixture, for everybody without a job on the board: one line -
   teams and the score - with the maps as a muted footnote, the way a results
   site draws a match. The winner keeps the accent the full board uses and the
   loser recedes, so a column of finished fixtures reads at a glance. */
.fixture.simple .ftally { min-width: 62px; text-align: center; }
.fixture.simple .fteam.lost { color: var(--muted); }
.fixture.simple .fteam.lost .tlogo { opacity: .65; }
.fixture.simple .fmaps {
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--line);
  display: flex; justify-content: center; flex-wrap: wrap;
  gap: 8px;
}

/* One map: its tile, its name, its scoreline, in a chip of its own. The tile is
   the same one the veto and the full board draw, shrunk to sit on a footnote
   line - a row of bare scorelines says nothing about where anything was
   played, and a row of chips reads as a strip of maps at a glance. */
.fixture.simple .fmapchip {
  display: inline-flex; align-items: center; gap: 7px; min-width: 0;
  padding: 3px 9px 3px 4px; border-radius: 5px;
  background: var(--surface-3);
  font-size: 11.5px; color: var(--muted);
}
.fixture.simple .fmapchip .mapicon { width: 18px; height: 18px; border-radius: 4px; font-size: 7px; }
.fixture.simple .fmapchip .fmapscore {
  font-family: var(--mono); color: var(--text-2); font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* A map with no tile in front of it is a bare scoreline, so it does not need
   the left inset the tile was given room by. */
.fixture.simple .fmapchip:not(:has(.mapicon)) { padding-left: 9px; }
/* A map a best-of-three never needed. Struck through rather than dropped, so
   the fixture still shows what the veto settled on. */
.fixture.simple .fmapchip.dead { opacity: .45; text-decoration: line-through; }

/* The score columns sit dead centre and hold their width, so the scorelines of
   a Bo2 line up under each other and down the whole page. */
.fixture .fmaprow {
  display: grid;
  grid-template-columns: minmax(0,1fr) auto auto auto minmax(0,1fr);
  align-items: center; gap: 6px;
  margin-top: 6px;
}
.fixture .fmaprow > .fscore:first-of-type { justify-self: end; }

/* The map number claims the left gutter rather than a column of its own -
   a column would pull the scores off centre. */
.fixture .fmapno {
  grid-column: 1; justify-self: start;
  font-size: 11px; font-weight: 700; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.fixture .fvs { color: var(--muted); font-size: 12px; }

.fixture span.fscore {
  min-width: 26px; text-align: center;
  font-weight: 700; font-variant-numeric: tabular-nums;
}
.fixture input.fscore {
  width: 46px; padding: 5px 4px;
  text-align: center; font-variant-numeric: tabular-nums;
}

/* The map takes the right-hand slack: it belongs to its own scoreline, so
   unlike the stage tag it must not span the fixture. */
.fixture .fmaprow > .fmap,
.fixture .fmaprow > .fmap-read { grid-column: 5; min-width: 0; }

/* The admin's control and everyone else's chip are the same shape, so the row
   does not shift when the admin looks at it over somebody's shoulder. */
.fixture button.fmap,
.fixture .fmap-read {
  display: flex; align-items: center; gap: 7px;
  padding: 4px 8px; font-size: 13px; font-weight: 500;
  min-width: 0;
}
.fixture button.fmap { width: 100%; text-align: left; }
.fixture button.fmap.empty { color: var(--muted); font-weight: 400; }
.fixture button.fmap[aria-expanded="true"] { border-color: var(--accent); }
.fixture .fmap-read { border: 1px solid transparent; }
.fixture .mapname { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The map tile. A thumbnail if one has been dropped into public/images/maps,
   and otherwise a hued square with the map's initials - which is not a fallback
   so much as the normal case, and is enough to tell one map from another down a
   column of fixtures. */
.mapicon {
  flex: 0 0 auto;
  width: 22px; height: 22px; border-radius: 5px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 8px; font-weight: 800; letter-spacing: .3px;
  background: hsl(var(--h, 210) 42% 26%);
  color: hsl(var(--h, 210) 70% 76%);
  object-fit: cover;
}
.mapicon.none { background: var(--surface-2); color: var(--muted); font-size: 11px; }

/* A downloaded icon is line art on transparency and carries its own colour, so
   it gets a neutral backing rather than the tile's hue - and 'contain' rather
   than 'cover', so a non-square file is letterboxed instead of cropped. */
img.mapicon {
  background: rgba(0, 0, 0, .22);
  object-fit: contain;
  padding: 1px;
}

/* The veto pool picker in the admin panel: eleven checkboxes that have to fit
   without turning into a column half a screen long. */
.vpoolpick { display: grid; gap: 2px 10px; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
.vpoolpick .vpick { margin: 0; }

/* ---------- the veto ----------

   Collapsed to a single line by default. Fifteen fixtures each showing seven
   maps is a screen nobody can read, and only one veto is ever being taken at a
   time. */

.veto { margin-top: 8px; }

.veto .vhead {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 8px; font-size: 12px; font-weight: 600; text-align: left;
  background: none; border: 1px dashed var(--line);
}
.veto .vhead.mine { border-style: solid; border-color: var(--accent); color: var(--accent); }
.veto .vlabel {
  flex: 0 0 auto; font-size: 10px; font-weight: 800;
  text-transform: uppercase; letter-spacing: .5px; color: var(--muted);
}
.veto .vstatus {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  --crest: 16px;
}
.veto .vstep { flex: 0 0 auto; color: var(--muted); font-variant-numeric: tabular-nums; }
.veto.open .vhead { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }

.veto .vpool {
  display: grid; gap: 4px; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
  padding: 8px; border: 1px solid var(--line); border-top: 0;
  border-radius: 0 0 10px 10px;
}
.veto .vmap {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 8px; font-size: 13px; font-weight: 500; text-align: left;
  min-width: 0;
}
.veto .vmap .mapname { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A struck map stays in place rather than disappearing, so the pool does not
   reshuffle under a finger mid-veto and everyone can see what went. */
.veto .vmap.banned { opacity: .38; text-decoration: line-through; }
.veto .vmap.banned:disabled { cursor: default; }
.veto .vmap.picked { border-color: var(--good); color: var(--good); opacity: 1; }
.veto .vmap.decider { border-color: var(--accent); color: var(--accent); opacity: 1; }

.veto .vleft { padding: 8px 8px 0; margin: 0; }

.veto .vlog {
  list-style: none; margin: 8px 0 0; padding: 0 8px;
  font-size: 12px; color: var(--muted);
}
.veto .vlog li { display: flex; gap: 6px; padding: 2px 0; --crest: 14px; }
.veto .vlog li .vteam { font-weight: 600; color: var(--text); }
.veto .vlog li.pick .vmapname { color: var(--good); font-weight: 600; }
.veto .vlog li.ban .vmapname { text-decoration: line-through; }

.veto .vadmin { display: flex; gap: 8px; padding: 10px 8px 2px; }

/* ---------- per-map scoreboard ----------

   Built on <details>, so the read-only history page gets working disclosure
   with no JavaScript at all - archive.js is explicitly a renderer with nothing
   interactive in it, and this is the one thing on the board that has to fold.
   The head and body are the veto panel's, borrowed wholesale: it is the same
   shape doing the same job one row further down the fixture. */

.sb { margin: 4px 0 2px; }
.sb .sbhead {
  display: flex; align-items: center; gap: 8px; width: 100%; cursor: pointer;
  padding: 6px 8px; font-size: 12px; font-weight: 600; text-align: left;
  border: 1px dashed var(--line); border-radius: 10px;
  list-style: none;
}
.sb .sbhead::-webkit-details-marker { display: none; }
.sb .sblabel {
  flex: 0 0 auto; font-size: 10px; font-weight: 800;
  text-transform: uppercase; letter-spacing: .5px; color: var(--muted);
}
.sb .sbwho { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb[open] .sbhead { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }

.sb .sbbody {
  padding: 8px; border: 1px solid var(--line); border-top: 0;
  border-radius: 0 0 10px 10px;
}
.sb .sbnote { margin: 6px 2px 0; }

table.sbt { min-width: 420px; margin-bottom: 6px; }
table.sbt th, table.sbt td { padding: 8px 6px; }
table.sbt .sbteam {
  display: flex; align-items: center; gap: 7px;
  --crest: 20px; color: var(--text); font-size: 13px;
}
/* The name stays put while the numbers scroll under the thumb. Scroll far
   enough without this and every cell is an anonymous figure - the same reason
   the rating matrix pins its own first column. */
table.sbt .sbp {
  position: sticky; left: 0; z-index: 1;
  background: var(--surface); text-align: left; min-width: 132px;
}
table.sbt .sbnick { font-weight: 700; color: var(--text); }
table.sbt .sbreal { margin-left: 6px; font-size: .88em; color: var(--muted-2); }
/* An account that matched nobody in the roster. Said quietly rather than hidden:
   the row is the prompt for one player to go and fix their Steam link. */
table.sbt tr.unknown .sbnick { color: var(--muted); font-style: italic; font-weight: 500; }
table.sbt td.good { color: var(--good); }
table.sbt td.bad  { color: var(--bad); }
table.sbt tfoot td {
  border-top: 1px solid var(--line); color: var(--muted-2); font-weight: 600;
}

@media (max-width: 560px) {
  /* Assists, headshot rate, KAST and MVPs go, leaving name / K / D / +- / ADR -
     which is what people actually read off a scoreboard, and what fits. */
  table.sbt { min-width: 300px; }
  table.sbt .opt { display: none; }
  table.sbt th, table.sbt td { padding: 7px 4px; }
}

/* ---------- the admin's stat sheet ----------

   Wider than the profile sheet it borrows its shell from: that one is two text
   boxes, this one is a ten-by-five grid of numbers with a team header over each
   half, and squeezing that into a 420px card would make every column a
   two-line wrap. */

.stat-sheet { max-width: 720px; }
.stat-sheet .seg { min-width: 380px; margin-bottom: 10px; }
.stat-sheet .seg .sbp { min-width: 120px; }
.stat-sheet input.sein {
  width: 100%; min-width: 54px; margin: 0;
  padding: 6px 4px; text-align: right;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}
.stat-sheet #se-rounds { max-width: 120px; }

/* One waiting scoreboard. A card inside a card, which is what the veto pool
   and the rating matrix already do for a nested block. */
.sequeue { padding: 10px 12px; margin: 10px 0; background: var(--surface-2); }
.sequeue-head { align-items: center; gap: 8px; margin-bottom: 4px; }
.sequeue-act { align-items: center; gap: 8px; margin: 6px 0; }
.sequeue-act select { flex: 1; min-width: 0; margin: 0; }
.sequeue-open { margin: 0 0 10px; }

.sedemo { margin-bottom: 12px; }
.sedemo input[type="file"] { width: 100%; }

.sesecret { margin-top: 14px; }
.sesecret summary { cursor: pointer; font-weight: 600; font-size: 13px; }

/* The three lines to paste into the game server's config. Selectable and
   wrapping, because the whole point is copying it out. */
.secode {
  margin: 8px 0 0; padding: 8px 10px;
  background: var(--surface-3); border: 1px solid var(--line);
  border-radius: 8px; font-family: var(--mono); font-size: 11px;
  white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text-2);
  user-select: all;
}

/* The per-map button that opens the entry grid. Drops to its own line on a
   phone the same way the map picker does. */
.fixture .fmaprow .fstatedit { justify-self: start; }
@media (max-width: 520px) {
  .fixture .fmaprow .fstatedit { grid-column: 1 / -1; }
  .stat-sheet .seg { min-width: 320px; }
  .stat-sheet .seg .sbp { min-width: 96px; }
}

/* ---------- the map picker ----------

   Positioned in viewport coordinates rather than relative to the fixture: the
   schedule is a long scrolling page and the picker has to be able to open
   upward for a row near the bottom of it. */

.map-pop {
  position: fixed; z-index: 80;
  max-height: 60vh; overflow-y: auto; overscroll-behavior: contain;
  padding: 6px 10px 10px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.map-pop .grouphead {
  padding: 8px 2px 5px;
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px;
  color: var(--muted);
}

.map-grid { display: grid; gap: 4px; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
.map-opt {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; font-size: 13px; font-weight: 500;
  text-align: left; background: none; border: 1px solid transparent;
  min-width: 0;
}
.map-opt:hover:not(:disabled) { background: var(--surface); filter: none; }
.map-opt .mapname { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.map-clear {
  width: 100%; margin-top: 8px;
  background: none; border: 1px solid var(--line);
  font-size: 12px; font-weight: 600; color: var(--muted);
  padding: 7px 8px;
}

.fixture .fstage {
  display: block; margin-bottom: 6px;
  font-family: var(--mono);
  font-size: 10px; font-weight: 400; text-transform: uppercase; letter-spacing: .09em;
  color: var(--muted-2);
}

/* A best-of-three decider that was never needed. Struck through and dimmed so
   it reads as "did not happen" rather than "not played yet" - the two look the
   same on a schedule otherwise, and only one of them means the room is
   waiting. */
.fixture .fmaprow.dead { opacity: .45; }
.fixture .fmaprow.dead .fmapno { text-decoration: line-through; }
.fixture .fdead {
  grid-column: 2 / -1;
  font-style: italic; text-decoration: line-through;
}

/* Group heading above each standings table. */
h3.gname {
  margin: 16px 0 8px;
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px;
  color: var(--accent);
}
h3.gname:first-of-type { margin-top: 0; }

/* ---------- tournament: bracket ----------

   Columns left to right, one per round, scrolling sideways when the tree is
   wider than the screen. Deliberately not the connector-line bracket: the lines
   are what make a bracket hard to make responsive, and with six teams the tree
   is small enough to read without them. */

.bracket { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 4px; }
.bcol { flex: 0 0 190px; display: flex; flex-direction: column; gap: 8px; }

.bhead {
  font-family: var(--mono);
  font-size: 10px; font-weight: 400; text-transform: uppercase; letter-spacing: .09em;
  color: var(--muted-2);
}
/* The final is the one column worth naming in gold - it is what the whole
   bracket is pointing at. */
.bcol:last-child .bhead { color: var(--gold); }
.bhead.third { margin-top: 8px; color: var(--muted-2); }

/* Two rows in one bordered box, the divider between them rather than around
   them: a knockout fixture is a pair, and boxing each side separately would
   read as two things that happen to be adjacent. */
.match {
  border: 1px solid var(--line-2);
  border-radius: 10px;
  overflow: hidden;
}
.match.done { border-color: var(--accent-dim); }
.match .mrow {
  display: flex; align-items: center; gap: 9px;
  padding: 10px 12px; font-size: 13px;
  background: #202734; color: var(--muted);
  --crest: 18px;
}
.match .mrow + .mrow { border-top: 1px solid var(--line); background: var(--surface-2); }
.match .mteam { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.match .mrow:not(.tbd) .mteam { color: var(--text); }
.match .mrow.won .mteam { font-weight: 700; color: var(--accent); }
.match .mrow.tbd .mteam { color: var(--muted); font-style: italic; }
.match .mscore { font-family: var(--mono); font-weight: 700; font-variant-numeric: tabular-nums; }
.match .mmap {
  padding: 7px 12px; border-top: 1px solid var(--line); background: var(--surface-2);
  font-family: var(--mono); font-size: 11px;
}

/* The final, given the weight the design gives it: a warm wash behind the two
   names, so the end of the bracket looks like the end of the bracket. */
.bcol:last-child .match:not(.done) {
  border-color: var(--accent-dim);
}
.bcol:last-child .match:not(.done) .mrow { background: linear-gradient(150deg, #271f15, var(--surface-2)); }

@media (max-width: 520px) {
  .fixture { padding: 8px; }
  .fixture .fmaprow { gap: 4px; }
  .fixture input.fscore { width: 40px; }
  /* The map name drops below the scoreline rather than fighting it for the
     last 40px, which is all a phone has left after two score boxes. */
  .fixture .fmaprow { grid-template-columns: minmax(0,1fr) auto auto auto minmax(0,1fr); }
  .fixture .fmaprow > .fmap,
  .fixture .fmaprow > .fmap-read { grid-column: 1 / -1; text-align: center; margin-top: 4px; }
}

/* ---------- misc ---------- */

.stack > * + * { margin-top: 10px; }
pre.copyable {
  background: var(--bg); border: 1px solid var(--line); border-radius: 10px;
  padding: 12px; overflow-x: auto; font-size: 13px; white-space: pre; margin: 0;
}
.hidden { display: none !important; }

.toast {
  position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
  background: var(--surface-2); border: 1px solid var(--line);
  padding: 10px 16px; border-radius: 999px; box-shadow: var(--shadow);
  z-index: 100; font-size: 14px; max-width: 90vw;
}
.toast.bad { border-color: var(--bad); color: #ffb3ae; }

@media (max-width: 520px) {
  .wrap { padding: 12px; }
  /* The title band is not a .wrap, so it has to be told about this too or the
     event's name stands 4px to the right of the cards under it. */
  .theadband { padding: 0 12px; }
  .teams { grid-template-columns: 1fr; }
}

/* ---------- side panel: who is here, and the room chat ----------

   One markup, two shapes. The base rules are only what both share; the drawer
   and the docked column each live in their own breakpoint rather than one
   overriding the other, so neither inherits the other's positioning. The split
   is at 1000px - the width where the column can be spared without squeezing the
   board down to one team per row. */

.side {
  z-index: 60;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--surface);
}

/* Phone: a drawer over the page. The sliding transform lives only here, so the
   docked column further down never inherits an entrance it did not ask for. */
@media (max-width: 999px) {
  .side {
    position: fixed;
    top: 0; bottom: 0; left: 0;
    width: min(384px, 92vw);
    border-right: 1px solid var(--line);
    box-shadow: var(--shadow);
    transform: translateX(-101%);        /* -101% so the shadow clears too */
    transition: transform .2s ease;
  }
  .side.open { transform: none; }

  /* The drawer covers the page, so the page must not scroll underneath it. */
  body.side-open { overflow: hidden; }
}

.side-backdrop {
  position: fixed; inset: 0; z-index: 55;
  background: rgba(0,0,0,.55);
  opacity: 0; pointer-events: none; transition: opacity .2s;
}
.side-backdrop.open { opacity: 1; pointer-events: auto; }

.side-head {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 8px;
  padding: 14px 16px 12px; border-bottom: 1px solid var(--line);
}
.side-head h2 { margin: 0; }
.side-head .spacer { flex: 1; }
/* The online count: a pill with its own live dot, so the panel says at a
   glance that there is a room behind it rather than only a text box. */
.side-head .badge {
  display: inline-flex; align-items: center; gap: 6px;
  border-radius: 999px; padding: 2px 8px;
  font-size: 11px; letter-spacing: 0; text-transform: none;
}
.side-head .badge::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor;
}

/* Who is here, as a row of chips rather than a column of names. Thirty players
   listed vertically is most of a panel spent on a roster nobody reads twice;
   the four who are actually online fit on two lines, and the rest are a count.
   Capped so the conversation still gets the drawer even on a busy night. */
.side-people {
  flex: 0 0 auto; min-height: 0;
  overflow-y: auto;
  max-height: 22vh;
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  background: #171c24;
}
.side-none { margin: 0; padding: 2px; font-size: 13px; color: var(--muted); }

.person {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 3px 10px 3px 4px; font-size: 12px; border-radius: 999px;
  background: var(--surface-3); border: 1px solid var(--line);
  --face: 18px;
}
/* The same rule as the log's, for the same reason: a long nick used to be
   broken mid-word down a chip until one person was three rows of the roster.
   The cap is on the name rather than the chip so the disc and the tags beside
   it keep their room, and it is in ch so it tracks the font rather than a
   width somebody measured once. */
.person { max-width: 100%; }
.person .nm {
  min-width: 0; max-width: 15ch;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Everyone who is not here, summed rather than listed: on a thirty-player
   roster the absent names are the least interesting thing in the panel. */
.person.away {
  background: none; border-color: transparent; color: var(--muted-3);
  padding-left: 8px;
}

.tag {
  flex: 0 0 auto;
  font-family: var(--mono); font-size: 9.5px; font-weight: 400;
  letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
  border: 1px solid var(--line-2); border-radius: 4px; padding: 1px 4px;
}
.tag.me { color: var(--blue); border-color: #32436a; }
.person .tag { margin-left: 1px; }

.side-chat { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
.chat-head {
  flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
  padding: 14px 20px 12px; border-bottom: 1px solid var(--line);
}
.chat-head h2 { margin: 0; }
.chat-head .spacer { flex: 1; }

.chat-log { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 10px 16px; }
.chat-empty { color: var(--muted); font-size: 13px; text-align: center; padding: 18px 8px; margin: 0; }

/* A message is a disc and a column beside it. The disc is only drawn on the
   first line of a turn - consecutive lines from one person hang under it - so
   a burst of five messages reads as one person talking rather than five.

   The only gap here that does any work is the one above a new turn. Everything
   inside a turn is set tight: a name and the sentence under it are one thought,
   and a panel that puts ten pixels between them reads as a list of notices
   rather than a conversation. */
.msg {
  /* The disc, and the column it sits in, from one number - they have to agree,
     and as two literals they were free to drift.

     30px rather than the 22 it was: a disc this size is a photograph of
     somebody, and at 22 a face had 484 pixels to be recognisable in. The
     source is 256px either way, so nothing here is short of resolution; the
     circle was just smaller than a face needs. It costs 8px of the text
     column, which is the trade - and at 300px of panel the sentences still
     wrap where they did. */
  --face: 30px;
  display: grid; grid-template-columns: var(--face) minmax(0, 1fr);
  column-gap: 9px;                     /* column-gap, not gap: no row gap wanted */
  padding: 8px 0 0;
}
.msg.grouped { padding-top: 1px; }
/* Spanning both rows rather than sitting in the first. A disc is taller than
   the name beside it, and a grid row is as tall as the tallest thing in it -
   so left in row one it would stretch that row to its own height and open the
   same gap under every name in the log. */
.msg .avatar { grid-column: 1; grid-row: 1 / span 2; align-self: start; }
.msg.grouped .avatar { visibility: hidden; height: 0; }
.msg .mhead { grid-column: 2; display: flex; align-items: baseline; gap: 7px; line-height: 1.25; }
/* The speaker's own colour, set per message from the same palette their disc
   is drawn from, so a screen of chat can be told apart before it is read. The
   fallback is only for a name that arrives without one. */
.msg .who  { font-size: 12.5px; font-weight: 600; color: var(--who, #c084fc); }
/* A name is one line, cut with an ellipsis where it does not fit.

   It used to break wherever it ran out of room, which on a nick like
   "Cigarettes After Sex" meant three lines of head above a two-word message -
   the name taking more of the panel than anything anybody said. A clipped name
   is still the name: the disc beside it is the same colour every time that
   person speaks, and the whole of it is in the title for a hover or a long
   press.

   min-width because a flex item refuses to shrink below its own text without
   it, and this is the only item in the head that may give way at all: the
   badges and the tag are already 0 0 auto, and the clock joins them below. */
.msg .who {
  min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msg .time {
  flex: 0 0 auto;
  font-family: var(--mono); font-size: 10.5px; color: var(--muted-3);
}
.msg .mline { grid-column: 2; display: flex; align-items: flex-start; gap: 4px; }
.msg .text {
  flex: 1; font-size: 13px; line-height: 1.45; color: var(--text-2);
  overflow-wrap: anywhere; text-wrap: pretty;
}
.msg .text.big { font-size: 30px; line-height: 1.2; }
.msg .tag { align-self: center; }

/* Kept in the flow at all times so nothing reflows when it appears, but only
   painted where a pointer can ask for it. Half the room is on a phone, where
   there is no hover to reveal it with, so there it stays visible. */
.msg .del {
  flex: 0 0 auto;
  background: none; border: 0; padding: 0 2px;
  font-size: 11px; line-height: 1.7; color: var(--muted); opacity: .45;
}
.msg .del:hover:not(:disabled) { opacity: 1; color: var(--bad); filter: none; }

@media (hover: hover) and (pointer: fine) {
  .msg .del { opacity: 0; }
  .msg:hover .del { opacity: .5; }
  .msg .del:focus-visible { opacity: 1; }
}

.chat-form {
  position: relative;                  /* the emoji panel anchors to this */
  flex: 0 0 auto;
  display: flex; gap: 8px; align-items: center;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: #171c24;
}
.chat-form input { flex: 1 1 auto; padding: 9px 14px; font-size: 13px; }
.chat-form button { flex: 0 0 auto; padding: 9px 14px; font-size: 13px; }

/* Send is an arrow, not the word for it. The label was spending seventy pixels
   on something the arrow says in thirty-six, and in a panel this narrow they
   come out of the box people are actually trying to type in. Stretched rather
   than given a height, so it stays as tall as that box however the row sizes. */
.chat-form .primary {
  display: inline-flex; align-items: center; justify-content: center;
  align-self: stretch;
  width: 36px; padding: 0;
  font-size: 17px; line-height: 1;
}

/* The picker. Anchored to the form rather than to its own button, because a
   panel this narrow has no room to hang a floating menu off a 36px control -
   so it spans the width instead and opens upward over the log.

   Two panes behind two tabs: the faces, and the stickers that fly. The box is
   a column with the tabs pinned and the pane scrolling under them, rather than
   one scrolling box, or switching to a grid of pictures would scroll the way
   back to the emoji off the top of it. */
.chat-form .emoji-btn {
  width: 30px; height: 30px; padding: 0; border-radius: 50%;
  border-color: transparent; background: var(--line);
  font-size: 15px; line-height: 1; color: var(--muted);
}
.chat-form .emoji-btn[aria-expanded="true"] { border-color: var(--accent); }

.emoji-pop {
  position: absolute; z-index: 10;
  left: 8px; right: 8px; bottom: calc(100% + 4px);
  max-height: 44vh;
  display: flex; flex-direction: column;   /* tabs above, one scrolling pane */
  overflow: hidden;                        /* so the corners keep their round */
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.pop-pane {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain;
  padding: 4px 8px 8px;
}
.pop-tabs {
  flex: 0 0 auto; display: flex; gap: 4px;
  padding: 6px; border-bottom: 1px solid var(--line);
}
/* Two classes deep, because .chat-form button reaches in here with a padding
   and a font size meant for the composer's own controls. */
.pop-tabs .pop-tab {
  flex: 1 1 0; padding: 6px 10px;
  background: none; border: 1px solid transparent; border-radius: 8px;
  font-size: 12px; color: var(--muted);
}
.pop-tabs .pop-tab[aria-selected="true"] {
  background: var(--surface-3); border-color: var(--line); color: var(--text);
}

.emoji-pop .grouphead {
  padding: 8px 2px 4px;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .4px;
  color: var(--muted);
}

.emoji-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(36px, 1fr)); gap: 2px; }
.emoji-grid .emoji {
  height: 36px; padding: 0;
  background: none; border: 0; border-radius: 8px;
  font-size: 21px; line-height: 1;
}
.emoji-grid .emoji:hover:not(:disabled) { background: var(--surface); filter: none; }
.emoji-grid .emoji:active { background: var(--line); }

/* The stickers themselves, as tiles. Bigger cells than the emoji grid: these
   are photographs rather than glyphs, and at 36px nobody could tell which one
   they were about to throw. */
/* The top margin stands in for the heading the emoji groups have: without it
   the first row of pictures starts tight against the tabs above. */
.sticker-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
  gap: 6px; margin-top: 5px;
}
.sticker-grid .sticker {
  aspect-ratio: 1; padding: 4px;
  background: var(--surface-3); border: 1px solid var(--line); border-radius: 10px;
}
.sticker-grid .sticker img { width: 100%; height: 100%; object-fit: contain; display: block; }
.sticker-grid .sticker:hover:not(:disabled) { background: var(--surface); filter: none; }
.sticker-grid .sticker:active { background: var(--line); }

.side-toggle {
  position: fixed; z-index: 50;
  left: 14px; bottom: calc(14px + env(safe-area-inset-bottom));
  width: 52px; height: 52px; padding: 0;
  border-radius: 50%; font-size: 20px;
  box-shadow: var(--shadow);
}
.side-toggle .n {
  position: absolute; top: -3px; right: -3px;
  min-width: 20px; height: 20px; line-height: 19px; padding: 0 5px;
  border-radius: 999px; background: var(--accent); color: #221600;
  font-size: 11px; font-weight: 700;
}

/* Same courtesy the reaction images get: appear, do not slide. */
@media (prefers-reduced-motion: reduce) {
  .side, .side-backdrop { transition: none; }
}

@media (min-width: 1000px) {
  .layout {
    display: grid;
    grid-template-columns: 344px minmax(0, 1fr);
    align-items: start;            /* required, or the sticky aside stretches */
    /* Raised with the column rather than left where it was, so the wider panel
       is taken out of the margin either side and not out of the board, which
       still gets its full 980 at this width. */
    max-width: 1364px;
    margin: 0 auto;
  }
  .layout > .wrap { margin: 0; max-width: none; }

  .side {
    position: sticky;
    top: calc(var(--bar-h) + 12px);
    height: calc(100vh - var(--bar-h) - 24px);
    margin: 12px 0 12px 16px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    /* The chips row and the composer paint their own darker backgrounds, which
       would otherwise square off the corners this rounds. */
    overflow: hidden;
  }
  /* The chips take what they need and the conversation takes the rest. Sized
     the other way round, four names would be given a third of the column. */
  .side-people { max-height: 26vh; }
  .side-chat { flex: 1 1 auto; }
  .side-toggle, .side-backdrop, .side-close { display: none !important; }
}

/* Reaction images launched from a score button, and stickers thrown into the
   chat. position:fixed so they escape the scrolling rating list, and
   pointer-events:none so a floater drifting over the next player's row can
   never swallow a tap.

   A column, because a sticker carries the name of whoever threw it underneath
   it. The rating screen sends no name and gets a column of one. */
.floater {
  position: fixed;
  z-index: 200;
  pointer-events: none;
  user-select: none;
  display: flex; flex-direction: column; align-items: center;
  will-change: transform, opacity;
  filter: drop-shadow(0 4px 10px rgba(0,0,0,.45));
}
.floater img { display: block; object-fit: contain; }

/* Who threw it. On its own plate rather than as bare text: this drifts over
   whatever the page happens to be showing - a bright team card, a scoreboard,
   another sticker - and a name in somebody's own colour needs a dark ground
   under it to stay a name. Their colour, the same one their disc and their
   chat lines are drawn in, so it is clear who threw it before it is read. */
.floater-name {
  max-width: 100%;
  /* Clear of the picture, not tucked into it: these files run to the edge of
     their frame with no transparent margin, so anything negative here would
     put the plate on top of the artwork rather than under it. */
  margin-top: 3px;
  padding: 2px 9px;
  border-radius: 999px;
  background: rgba(20, 24, 31, .84);
  border: 1px solid var(--line-2);
  color: var(--who, var(--text));
  font-size: 11.5px; font-weight: 600; line-height: 1.6;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---------- the read-only board ----------
 *
 * The front page and the history pages draw the same thing through archive.js,
 * so its look lives here rather than in a <style> block one of them owns.
 *
 * Every rule is scoped to `.board`, the class archive.js puts on whatever it
 * draws into. The read-only board and the logged-in one share a visual
 * language and, inevitably, several class names - podium, fx, tname - and
 * without the namespace they share the rules too: an unscoped `.podium { }`
 * here turned the live board's podium card into a three-column grid. The two
 * class names that could not be namespaced out of trouble were renamed
 * instead (see .fx-side and table.gtable below): a bare `.side` or
 * `table.standings` rule elsewhere still matches this markup whatever this
 * section says, because scoping raises the specificity of the properties
 * declared here and says nothing about the ones that are not. */

.board .head-meta { color: var(--muted); font-size: 13px; margin-top: 4px; }

.board .podium { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.board .podium .pl {
  border: 1px solid var(--line); border-radius: 10px; padding: 12px;
  display: flex; align-items: center; gap: 10px;
}
.board .podium .pl img { border-radius: 6px; object-fit: contain; }
.board .podium .pl .medal { font-size: 22px; }
.board .podium .pl .tn { font-weight: 700; overflow-wrap: anywhere; }
.board .podium .pl .sub { color: var(--muted); font-size: 12px; margin-top: 2px; }

/* `gtable` rather than the `standings` this began as: the live board's
   standings carry a 460px min-width and a set of row states written for their
   own markup, none of which this table wants. The base `table` rules above are
   all it needs on top of these two. */
.board table.gtable td, .board table.gtable th { padding: 7px 6px; }
.board .tname { display: flex; align-items: center; gap: 7px; overflow-wrap: anywhere; }

.board .roundbox { margin-bottom: 14px; }
.board .roundbox .rhead { display: flex; align-items: baseline; gap: 8px; margin: 0 0 6px; }
.board .roundbox h3 { font-size: 13px; color: var(--muted); text-transform: uppercase;
                      letter-spacing: .4px; margin: 0; }

/* The round's kick-off, at the end of its heading. Pushed to the right rather
   than set after the title so the times make a column down the card - a page
   of rounds is scanned for "when do we play", and a ragged clock is read one
   line at a time. Accented for the same reason the live board accents it: it
   is the one thing on an unplayed round that is not grey. */
.board .roundbox .rhead .rtime {
  margin-left: auto; flex: 0 0 auto;
  font-size: 13px; font-weight: 700; color: var(--accent);
}

/* The caveat under the schedule heading, while the schedule is still a plan. */
.board .timenote { margin: -2px 0 12px; }
.board .fx {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  border: 1px solid var(--line); border-radius: 10px; padding: 9px 12px; margin-bottom: 6px;
}
.board .fx .fx-side { display: flex; align-items: center; gap: 7px; flex: 1; min-width: 0; }
.board .fx .fx-side.away { justify-content: flex-end; text-align: right; }
.board .fx .fx-side .nm { overflow-wrap: anywhere; }
.board .fx .score { font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap; }
.board .fx .score.mut { color: var(--muted); font-weight: 400; font-size: 12px; }
.board .fx .mapsline { flex-basis: 100%; color: var(--muted); font-size: 12px;
                       display: flex; gap: 12px; flex-wrap: wrap; }
.board .fx .win { color: var(--accent); }

/* 220px before the ladders existed. A row here carries the same thing the
   draft board's does - a nick, the roster name, a Premier rating, an average -
   so it needs the same room, or the rosters read as ragged two-line rows. */
.board .teamgrid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
.board .teamcard { border: 1px solid var(--line); border-radius: 10px; padding: 12px; }
.board .teamcard .th { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.board .teamcard .th img { border-radius: 6px; object-fit: contain; }
.board .teamcard .th .tn { font-weight: 700; overflow-wrap: anywhere; }
.board .teamcard ul { list-style: none; margin: 0; padding: 0; }
.board .teamcard li { display: flex; justify-content: space-between; gap: 8px;
                      padding: 3px 0; font-size: 13px; }
.board .teamcard li .mn { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.board .teamcard li .avg { color: var(--muted); font-variant-numeric: tabular-nums; }

/* A team name that is a link. Deliberately not styled as one: it sits inside
   a standings table and a podium where an underline on every row would be
   noise, so it takes the accent only under the cursor. */
.tlink { color: inherit; text-decoration: none; }
.tlink:hover { color: var(--accent-2); text-decoration: underline; }

/* The crest beside it, wrapped so the picture is a target too. inline-flex
   because it stands in for the image in three different parents - two flex
   rows and one inline table cell - and has to keep the crest's own sizing
   rules working in all of them. The crest carries margins of its own in the
   standings, so they are lifted onto the wrapper: a margin left on the image
   would sit inside the click target rather than beside it. */
.tlogolink {
  display: inline-flex; align-items: center;
  flex: 0 0 auto; align-self: center;
  line-height: 0; vertical-align: middle;
}
.tlogolink:hover { filter: brightness(1.25); }
table.standings .tlogolink { margin-right: 10px; }
table.standings .tlogolink .tlogo { margin-right: 0; }

/* ---------- the team page ----------
 *
 * /team.html: one team, its faces, and everything a name has ever won. The
 * only screen in the app built around photographs - a rating list of thirty
 * faces is slower to read than thirty names, but a page about five people is
 * the other way round.
 *
 * Nothing here is reused from the board on purpose. The board is a table that
 * has to fit six teams and forty maps on a phone; this is a poster.
 */

.tprofile { padding: 0; overflow: hidden; }

/* The strip of faces along the top.
 *
 * Scrolls sideways below about 560px rather than wrapping to two rows: five
 * portraits on one line is the shape this block is read as, and a 3+2 split
 * puts two people in a second row that looks like substitutes. */
.tstrip {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: 1px; background: var(--line);
  border-bottom: 1px solid var(--line);
  overflow-x: auto; scrollbar-width: none;
}
.tstrip::-webkit-scrollbar { display: none; }

.tp {
  /* The portrait is the point of this block, so it takes the room it can get:
     it grows with the viewport and stops at 128px, which is where five of them
     plus their names still sit comfortably inside the card. Everywhere else in
     the app a face is a thumbnail on a row; here it is the subject. */
  --face: clamp(84px, 13vw, 128px);
  min-width: 116px;
  display: flex; flex-direction: column; align-items: center;
  padding: 16px 8px 12px;
  /* Flat, and the card's own colour. Explicit rather than transparent because
     the strip paints the hairlines between cells with its own background
     showing through a 1px gap - a see-through cell would show those instead. */
  background: var(--surface);
}

/* The face. A photo when there is one, and the default silhouette when there
   is not - see profile.js, which draws both for every screen in the app. */
/*
 * A face: nothing behind it, and nothing around it.
 *
 * No border, and no plate either. A cut-out portrait - which is what an
 * esports photo usually is - has a transparent background, and any colour set
 * here would show through it as a tinted box the shape of the crop. So the
 * picture sits directly on whatever it is drawn on: the strip cell, a table
 * row, the profile sheet. A photo with its own backdrop still fills the box
 * because of object-fit, so this costs nothing for those.
 */
.face {
  width: var(--face, 72px); height: var(--face, 72px);
  border-radius: 12px; object-fit: cover; object-position: center top;
  flex: 0 0 auto;
}
/* The default silhouette, dimmed a little - a placeholder that shouted as
   loudly as a photograph would make a roster with two photos in it read as
   five. It is transparent for the same reason everything else here is. */
.face.none { opacity: .75; }

/* The face and the two buttons that only exist while a captain is editing.
   A wrapper rather than putting them on .tp, so they anchor to the picture
   instead of to the whole cell - a camera button floating beside a name is not
   obviously about the face above it. */
.tpf { position: relative; display: inline-flex; line-height: 0; }

/* The camera sits on the picture, bottom-right, the way it does on every phone
   the person editing this already owns. */
.tpshoot, .tpx {
  position: absolute; padding: 0; line-height: 1;
  border: 1px solid var(--line-2); border-radius: 50%;
  background: var(--surface-3); color: var(--text-2);
  cursor: pointer;
}
.tpshoot {
  right: -4px; bottom: -4px; width: 28px; height: 28px; font-size: 13px;
}
.tpshoot:hover { border-color: var(--accent); color: var(--accent-2); }
/* And the way back off, top-right so the two never overlap on a small face. */
.tpx { right: -4px; top: -4px; width: 22px; height: 22px; font-size: 13px; }
.tpx:hover { background: rgba(244, 99, 91, .16); color: var(--bad); border-color: transparent; }

.tpn {
  display: flex; align-items: center; gap: 4px;
  margin-top: 8px; max-width: 100%;
}
.tpname {
  font-weight: 600; font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The captain's crown, in the accent so one face in five is marked without a
   second line of text under it. */
.tpc { color: var(--accent); font-size: 12px; line-height: 1; }
/* The roster name under the nick, for the half of the room that knows somebody
   by one and not the other. */
.tpr {
  color: var(--muted-2); font-size: 11px; margin-top: 1px;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The age, last in the cell and just the number. Under a face, beside a name,
   there is nothing else two digits could be - and five cells across a 375px
   screen have no room for the word. It is on the hover instead, which is what
   the help cursor is advertising. Dimmer than the roster name above it: this
   is the smallest fact in the block, and it must not be read before the
   name is. */
.tpa {
  color: var(--muted-2); opacity: .8; font-size: 11px; margin-top: 2px;
  font-variant-numeric: tabular-nums; cursor: help;
}

/* The crest and the name, the block the page is titled by. */
.tident {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 16px;
  background: var(--surface-2);
}
.tident .tidn { min-width: 0; flex: 1; }
.tident .tidn h2 { margin: 0; font-size: 24px; overflow-wrap: anywhere; }
.tident .tidr { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }

/* Where a run ended, in one phrase. Gold, silver and bronze earn their own
   colour; everything else is the same quiet grey, because "out in the
   quarter-final" is a fact and not an achievement. */
.tout { font-size: 13px; font-weight: 600; color: var(--muted); white-space: nowrap; }
.tout.place-1 { color: var(--accent-2); }
.tout.place-2 { color: #c9d0e0; }
.tout.place-3 { color: var(--gold); }
.tout.live    { color: var(--good); }

/* The captain's editor, opened from the identity block. The panel itself is
   the draft board's - same classes, same shape - and this only gives it the
   room the card does not have, .tprofile having had its padding removed so the
   face strip could run edge to edge. */
.tprofile .tedit-panel { margin: 0 16px 14px; }

/* The trophy shelf. Only placements: a row of six discs for six group-stage
   exits would say nothing at all. */
.tcab {
  display: flex; gap: 8px; flex-wrap: wrap;
  padding: 12px 16px; border-top: 1px solid var(--line);
}
.tcabi {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 10px; border-radius: 999px;
  border: 1px solid var(--line-2); background: var(--surface-3);
  color: var(--text-2); font-size: 12px; text-decoration: none;
  max-width: 100%;
}
.tcabi:hover { border-color: var(--accent-dim); }
.tcabi.place-1 { border-color: var(--accent-dim); background: var(--accent-bg); }
.tcabm { font-size: 15px; line-height: 1; }
.tcabn { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The facts under the crest, one per row, label left and value right - the
   shape a profile page is read in, and the shape HLTV uses for the same job. */
.tfacts { padding: 4px 16px 14px; }
.tfact {
  display: flex; align-items: baseline; gap: 12px;
  padding: 9px 0; border-top: 1px solid var(--line-soft);
}
.tfact:first-child { border-top: 0; }
.tfk { color: var(--muted); font-size: 13px; }
.tfv { margin-left: auto; font-weight: 600; text-align: right; overflow-wrap: anywhere; }
.tfv a { color: var(--text); }

/* The career, as boxes. Five of them on a laptop, two or three on a phone -
   auto-fill rather than a fixed count, because the prize box is much the
   widest and a fixed grid would either clip it or waste the other four. */
.tsum {
  display: grid; gap: 10px; margin-bottom: 12px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.tsumi {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 14px;
}
.tsumv { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
.tsuml { color: var(--muted); font-size: 12px; margin-top: 2px; }
.tsums { color: var(--muted-3); font-size: 11px; margin-top: 4px; }

/* One fixture per row, read from this team's side: did we win, against whom,
   and by how much. The result letter is first because that is the column
   somebody scans down. */
.tfixes { margin-top: 4px; }
.tfix {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 2px; border-top: 1px solid var(--line-soft);
  font-size: 13px;
}
.tfix:first-child { border-top: 0; }
.tfixr {
  flex: 0 0 20px; height: 20px; border-radius: 6px;
  display: grid; place-items: center;
  font-size: 11px; font-weight: 700;
  background: var(--surface-3); color: var(--muted-2);
}
.tfix.win  .tfixr { background: rgba(74, 222, 128, .14); color: var(--good); }
.tfix.loss .tfixr { background: rgba(244, 99, 91, .14); color: var(--bad); }
.tfix.draw .tfixr { background: var(--surface-3); color: var(--text-2); }
.tfixs { flex: 0 0 auto; color: var(--muted); font-size: 12px; min-width: 92px; }
.tfixo { flex: 1; min-width: 0; }
.tfixo a { display: flex; align-items: center; gap: 6px; color: var(--text); text-decoration: none; }
.tfixo a:hover span { text-decoration: underline; }
.tfixo span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tfixm { font-family: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.tfixd { color: var(--muted-2); font-size: 11px; font-family: var(--mono); white-space: nowrap; }
.tfixd i { font-style: normal; color: var(--muted-3); }

/* The money on a cabinet row, beside the placement it was paid for. */
.tprize { color: var(--accent-2); font-variant-numeric: tabular-nums; }

/* The roster in full, with both ladders - the same row the draft board draws,
   with a small face in front of it. */
.troster { list-style: none; margin: 4px 0 0; padding: 0; }
.troster li {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 0; border-top: 1px solid var(--line-soft);
  --face: 34px;
}
.troster li:first-child { border-top: 0; }
.troster li .face { border-radius: 8px; }
.troster li .mn { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.troster li .avg { color: var(--muted); font-variant-numeric: tabular-nums; }


/* Below this the strip is scrolled rather than shrunk: the portrait only gives
   up the difference between the desktop size and 84px, and past that a phone
   swipes the strip sideways. A face squeezed down to fit five across a 375px
   screen is a thumbnail, and the whole point of this page is that it is not. */
@media (max-width: 560px) {
  .tp { min-width: 100px; --face: 84px; padding: 14px 6px 10px; }
  .tident { padding: 12px; gap: 10px; }
  .tident .tidn h2 { font-size: 20px; }
  .tfacts { padding: 4px 12px 12px; }
  .tcab { padding: 10px 12px; }
  .tfixs { min-width: 0; flex: 0 0 auto; }
  .tfixd { display: none; }
}

/* ---------- the tournament index ----------
 *
 * One row per tournament, the ongoing one marked as such. This is what the
 * front page used to be; it now lives behind the header's "Тэмцээнүүд" so a
 * visitor lands on a board rather than on a filing cabinet. */

.hlist { margin-top: 4px; }
.hrow {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 12px 4px; border: 0; border-bottom: 1px solid var(--line);
  background: transparent; text-align: left; border-radius: 0; cursor: pointer;
}
.hrow:last-child { border-bottom: 0; }
.hrow:hover { background: var(--surface-2); }
.hrow .hname { font-weight: 600; overflow-wrap: anywhere; }
.hrow .hsub { color: var(--muted); font-size: 12px; margin-top: 2px; }
.hrow .hwin { display: flex; align-items: center; gap: 6px; margin-left: auto;
              color: var(--muted); font-size: 13px; white-space: nowrap; }
.hrow .hwin img { border-radius: 5px; object-fit: contain; }

/* The age a picked birthday works out to, under the box it was picked in.
   The accent rather than the muted grey the hint above it is: it is an answer
   the sheet computed, not another instruction. */
.profile-age:empty { display: none; }
.profile-age { color: var(--accent-2); margin: -2px 0 2px; }

/* ---------- in-game name and Steam ----------
 *
 * Two things a player fills in about themselves, drawn wherever that player is
 * named. Both are secondary to the name they sit beside - somebody scanning a
 * team card is looking for who is in it, not for their nick - so neither may
 * take space away from the name or push a row onto a second line.
 */

/* The roster name, in brackets after the nick. Reads as an aside rather than
   as a second name: quieter, lighter, and set at the size the averages beside
   it are. The nick is the name - this is the note that says who that is. */
.alias {
  color: var(--muted-2); font-size: .88em; font-weight: 400;
  margin-left: 6px; overflow-wrap: anywhere;
}
.alias::before { content: '('; }
.alias::after  { content: ')'; }

/* A captain's row is bold; the note after their nick is not part of that. */
.team li.captain .alias { font-weight: 400; }

/* ---------- FACEIT level and Premier rating ----------
 *
 * Both are the real artwork now, as files under public/images - FACEIT's own
 * eleven level icons (ten levels and the Challenger crest) and Valve's Premier
 * plate. Referenced by URL the way the map thumbnails are, so the browser
 * fetches each one once and only for the levels the room actually has.
 *
 * What they replaced was a hand-drawn gauge and a hand-drawn plate, both of
 * which got close and neither of which got it exactly. There is nothing left
 * here to drift from the thing it is imitating.
 */

/* The level icon is an <img>, so it needs sizing and nothing else. */
.lvl {
  flex: 0 0 auto; align-self: center;
  width: 20px; height: 20px;
  display: inline-block; vertical-align: middle;
}
.lvl.mini { width: 17px; height: 17px; }

/* The Premier rating, built the way Leetify builds it.

   Read off their leaderboard rather than guessed at, because the plate this
   replaced was one flat grey for everybody - the icon set only ships the
   neutral one, so a 4,000 and a 34,000 looked identical and the band lived in
   the digits, where it was too quiet to read.

   Their structure is two leaning bars in the tier's colour, then the rating in
   a skewed box tinted the same. No plate image at all - which is why every
   tier can have its own colour, and why this needs no artwork to go stale.

   The palette is theirs, and theirs is Valve's: #b1c4d9 through #d32ce6 to
   gold are the same seven the game paints the rating in, in five-thousand
   steps.

   Sized in em off the badge's own font-size, exactly as Leetify does it, so
   the whole thing scales from one number. */
.prem {
  flex: 0 0 auto; align-self: center;
  /* stretch, not center: the bars and the label both take their height from
     this box rather than from their own text. The label sets a smaller
     font-size, so a height of 1em on IT would resolve against that smaller
     size - which is how the bars ended up half again taller than the label
     they are supposed to line up with. Leetify get away with 1em because
     their label and its text are two different elements; this is one, so the
     height comes from the parent instead. */
  display: inline-flex; align-items: stretch;
  height: 1em;
  /* The badge's height and its whole scale, in one number - and the same 20px
     the FACEIT icon beside it is, so the two sit on one line rather than one
     pixel apart. */
  font-size: 20px;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  line-height: 1; white-space: nowrap;
}

/* The two bars. A mask rather than an inline <svg>, so the path lives in this
   stylesheet once instead of in the markup thirty times over - the board
   repaints on every pick, and this is three hundred characters of it. */
.prem::before {
  content: '';
  flex: 0 0 auto;
  width: .53125em;                       /* height comes from the row's stretch */
  background: var(--stripe, var(--muted));
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 17 32'%3E%3Cpath d='M5.44 2.13A2.6 2.6 0 0 1 7.99 0h1.86a.6.6 0 0 1 .6.7L4.83 31.5a.6.6 0 0 1-.6.5h-2.3c-1 0-1.76-.9-1.58-1.89l5.1-27.98ZM11.82.99c.1-.57.6-.99 1.18-.99h2.93a.6.6 0 0 1 .59.7l-5.4 30.31c-.1.57-.6.99-1.18.99H7a.6.6 0 0 1-.59-.7L11.82.98Z'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 17 32'%3E%3Cpath d='M5.44 2.13A2.6 2.6 0 0 1 7.99 0h1.86a.6.6 0 0 1 .6.7L4.83 31.5a.6.6 0 0 1-.6.5h-2.3c-1 0-1.76-.9-1.58-1.89l5.1-27.98ZM11.82.99c.1-.57.6-.99 1.18-.99h2.93a.6.6 0 0 1 .59.7l-5.4 30.31c-.1.57-.6.99-1.18.99H7a.6.6 0 0 1-.59-.7L11.82.98Z'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* The label: skewed to lean with the bars, tinted, and pulled left so it tucks
   under them the way theirs does. */
.prem b {
  display: flex; align-items: center; justify-content: center;
  /* Wide enough for the longest rating there is, so every badge is the same
     width whatever is in it - a five-digit rating, a four-digit one and the
     blank all line up down a team card. Leetify's own 2.21875em is sized for
     a proportional face; this is measured for the mono one. */
  min-width: 4.4em;
  margin-left: -.25em; padding: 0 .18em 0 .26em;
  border: .0625em solid var(--edge, var(--line-2));
  border-radius: .09375em;
  background: var(--plate, var(--surface-3));
  color: var(--ink, var(--text-2));
  transform: skew(-10deg);
  font-size: .54em; font-weight: 700;
}

/* Valve's seven bands, in five-thousand steps, gold at the top. */
.prem[data-band="0"] { --stripe: #b1c4d9; --plate: #2c2f37; --edge: rgba(177,196,217, .6); --ink: #eef2f7; }
.prem[data-band="1"] { --stripe: #5e98d9; --plate: #061c36; --edge: rgba(94,152,217, .6); --ink: #8bc1ff; }
.prem[data-band="2"] { --stripe: #4c6aff; --plate: #060e37; --edge: rgba(76,106,255, .6); --ink: #8a9dfe; }
.prem[data-band="3"] { --stripe: #8847ff; --plate: #180638; --edge: rgba(136,71,255, .6); --ink: #b48bff; }
.prem[data-band="4"] { --stripe: #d32ce6; --plate: #320638; --edge: rgba(211,44,230, .6); --ink: #f177ff; }
.prem[data-band="5"] { --stripe: #eb4b4b; --plate: #380606; --edge: rgba(235,75,75, .6); --ink: #ff8686; }
.prem[data-band="6"] { --stripe: #ffd700; --plate: #383006; --edge: rgba(255,215,0, .6); --ink: #ffdf35; }

/* No rating: the same badge, greyed, with the dashes CS2 shows for unranked.
   A blank rather than a gap keeps a column of these aligned down a team card -
   and says "no rank" rather than "no room". */
.prem.none { --stripe: #485061; --plate: #232834; --edge: #343b4a; --ink: #68718a; }
.prem.none b::after { content: '— —'; }

/* The chat line, where the badge is a chip beside a name in a 300px panel. */
.prem.mini { font-size: 17px; }   /* matching the mini level icon */

/* The plate is the way out to a Steam profile, which is why it is sometimes an
   <a> and sometimes a <span> - a link for the players who have given us an
   account, plain artwork for the ones who have not. It has to read the same
   either way, so the anchor is stripped back to the badge it is wrapping: no
   underline, no link colour. Both of those would land on the digits, which are
   the one part of this thing that is meant to be read as a number.

   What is left to say "this goes somewhere" is the hover, and it is the whole
   plate that lifts - stripes, box and digits together, through the one filter
   on the anchor - because the badge is a single mark and half of it brightening
   would read as a rendering fault rather than as a target. */
a.prem { text-decoration: none; transition: filter .12s ease; }
a.prem:hover { filter: brightness(1.28); }
/* Keyboard focus needs an outline the hover does not, and the ring goes round
   the badge rather than round the skewed label - the label leans, and a ring
   that leans with it points at nothing. */
a.prem:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }

/* The strip in the profile sheet: what the two services said, and a way to
   ask again. */
/* The photograph, at the top of the profile sheet. The preview is the same
   .face the team page draws, at the size it draws it, so what somebody
   approves here is what the room will see. */
.profile-photo {
  display: flex; align-items: center; gap: 12px;
  margin-top: 6px; --face: 72px;
}
.profile-photo-act { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.profile-ranks { margin-top: 14px; }
.profile-ranks-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px;
}
.profile-ranks-row button { margin-left: auto; }

/* The admin table's column holds both, right-aligned with the other numbers.

   The pair is laid out by a span inside the cell rather than by the cell
   itself. A td set to display:flex stops being a table cell: it takes its
   height from its own contents instead of from the row, so the badges sat
   thirteen pixels short of a row the name and the Steam box beside them
   filled - three things on one line, none of them on the same centre. The
   span is the same wrapper the photo and Steam columns already use, and it
   leaves the cell a cell, middle-aligned with the rest of the row. */
#profiles .rankcell {
  display: flex; gap: 6px; justify-content: flex-end; align-items: center;
}

@media (max-width: 520px) {
  /* The roster name in brackets is what goes when a row runs out of phone.
     The nick answers which player this is, and the two ladders are the reason
     the row is being read at all - so those stay and the aside drops. Applies
     to both boards, because the Premier plate is twenty pixels wider than a
     plain number and 375px does not have twenty pixels spare. */
  .pool .alias,
  .team li .alias { display: none; }
}

/* The link out to a profile, where it is still drawn as a mark of its own: the
   admin's Steam column, beside the box the link was pasted into. A 22px target
   in a 28px cell - small enough to disappear next to the input, big enough to
   hit with a thumb. On the boards the Premier plate carries this instead. */
.steamlink {
  flex: 0 0 auto; align-self: center;
  display: inline-grid; place-items: center;
  width: 22px; height: 22px; border-radius: 6px;
  color: var(--muted-2); text-decoration: none; font-size: 13px; line-height: 1;
}
.steamlink:hover { background: var(--surface-3); color: var(--blue); }

/* The same link where there is room to read it: the panel's roster table, and
   the sheet's own "check this opens you". */
.steamlink.long,
#profile-link .steamlink {
  width: auto; height: auto; place-items: start;
  display: inline-block; padding: 3px 0;
  color: var(--blue); font-size: 13px; overflow-wrap: anywhere;
}
.steamlink.long:hover,
#profile-link .steamlink:hover { background: none; text-decoration: underline; }

/* The profile sheet. The counter sits tight under its box; the hint under the
   Steam box has room to breathe, because it is the line that decides whether
   somebody pastes the right thing. */
.profile-count { margin-top: 6px; text-align: right; font-variant-numeric: tabular-nums; }
.profile-hint  { margin: 8px 0 0; }
.profile-actions { margin-top: 18px; }

/* The nudge on the rating screen. A notice with a button in it, so the ask and
   the way to answer it are one thing rather than two. */
.profile-nudge { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.profile-nudge > span { flex: 1; min-width: 180px; }

/* The header button, with a dot on it while something is still missing. The
   dot rather than a word: the bar is already at its limit on a 375px phone,
   and the thing being communicated is "there is something here", which is all
   a dot ever says. */
.profile-btn { position: relative; }
.profile-btn.needs::after {
  content: ''; position: absolute; top: 4px; right: 4px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
}

@media (max-width: 520px) {
  /* The roster name is the first thing to go when a row runs out of width.
     The pool is a grid of tap targets with a number on the end of each, and
     the nick alone answers what it is asked - which player is this. */
  .pool .alias { display: none; }

  /* "Профайл" is 89px of the 375 the bar has, and it is the longest label in
     there. The pencil says the same thing in twenty - and it is the same
     pencil the draft board gives a captain for editing their team, so it is a
     mark this app has already taught rather than a new one. */
  .profile-btn { font-size: 0; padding: 7px 9px; }
  .profile-btn::before { content: '\270E'; font-size: 15px; line-height: 1; }
  .profile-btn.needs::after { top: 2px; right: 2px; }
}

/* ---------- the login sheet ----------
 *
 * The name picker is a dialog now rather than a card down the page: the front
 * page belongs to the tournament, and logging in is a thing a minority of the
 * people looking at it will ever do. Centred on a desktop, docked to the
 * bottom of a phone where the thumb is. */

.sheet-back {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(17, 21, 26, .72);
  backdrop-filter: blur(3px);
  display: grid; place-items: center; padding: 16px;
  /* visibility, not just opacity: a transparent dialog left in the tab order
     hands the keyboard to a name picker nobody opened. */
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .16s ease, visibility .16s;
}
.sheet-back.open { opacity: 1; visibility: visible; pointer-events: auto; }

.sheet {
  width: 100%; max-width: 420px; max-height: calc(100dvh - 32px);
  overflow-y: auto; margin: 0;
  transform: translateY(8px); transition: transform .16s ease;
}
.sheet-back.open .sheet { transform: none; }
.sheet-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.sheet-head h2 { margin: 0; flex: 1; min-width: 0; overflow-wrap: anywhere; }
.sheet-close {
  border: 0; background: none; color: var(--muted);
  font-size: 20px; line-height: 1; padding: 4px 6px; flex: 0 0 auto;
}
.sheet-close:hover:not(:disabled) { color: var(--text); filter: none; }

.namelist { max-height: 46vh; overflow-y: auto; margin-top: 8px; border-radius: 10px; }
.namelist button {
  display: flex; align-items: center; gap: 8px;
  width: 100%; text-align: left; border: 0; border-bottom: 1px solid var(--line);
  border-radius: 0; background: transparent; padding: 12px;
}
.namelist button:last-child { border-bottom: 0; }
.namelist .nm { flex: 1; overflow-wrap: anywhere; }
#pin { letter-spacing: 8px; text-align: center; font-size: 20px; }

/* Docked to the bottom edge on a phone, and it slides up from below rather
   than easing in place - the gesture a sheet is expected to make. */
@media (max-width: 520px) {
  .sheet-back { place-items: end center; padding: 0; }
  .sheet {
    max-width: none; transform: translateY(14px);
    border-radius: var(--radius) var(--radius) 0 0;
    /* Clear of the home indicator, and of nothing on a phone without one. */
    padding-bottom: max(22px, env(safe-area-inset-bottom));
  }
}

@media (prefers-reduced-motion: reduce) {
  .sheet-back, .sheet { transition: none; }
}
