/* ==========================================================================
   UNDEAD CARNIVAL - public homepage
   Presentation model: a full-width topbar over a single "comic panel" frame.
   All content lives inside that frame; the background art lives inside it
   too, so nothing has to be layered behind the page itself.
   ========================================================================== */

:root{
  /* The ONE font on this site. Every font-family/font shorthand below points
     here - nothing hardcodes a family, and no second face is ever loaded, so
     headings, body, labels and 9px microcopy are all the same typeface at
     different weights. `sans-serif` is the only other entry: it is the
     generic the browser falls back to if Roboto fails to fetch, not a
     second design choice. */
  --font:Roboto,sans-serif;

  --bg:#000;
  --panel:#08080B;          /* inside of the comic frame */
  --surface:#131315;
  --surface-2:#1C1C20;
  --line:#2A2A2F;
  --text:#F2F2F4;
  --muted:#95979E;
  --red:#E01B27;
  --red-dark:#B4141E;
  --green:#4BC26B;
  --amber:#E8A33D;
  /* name tiers */
  --gold:#FFC942;
  --silver:#9BB0CC;   /* cool + well under --text, or it reads as plain white */
  --bronze:#D08A4A;
  --diamond:#7FE6FF;
  --operator:#FF8A2B; /* staff Operators. Hotter and more saturated than
                         --bronze/--amber so the glow never reads as a medal. */
  --ban-red:#FF3A3A;

  --ink:#000;               /* the frame edge. Black on black: the panel shape
                               reads from where the ART stops, not from a
                               contrasting line. Nothing on this page is white. */
  --ink-w:5px;              /* frame edge thickness */
  --r-lg:14px;
  --shadow:0 12px 34px rgba(0,0,0,.6);
  --parallax:150px;         /* media bleed inset AND the JS clamp - keep equal to SHIFT */
  --row-h:44px;             /* one leaderboard row, incl. the 15px name line + border */
  --rows:12;                /* fixed board height, in rows */
  --conn-col:92px;          /* Connect button column in the server list */
  --topbar-nav-h:62px;      /* the section-nav row alone */
  --topbar-brand-h:76px;    /* the "Undead Carnival" strip above it */
  --topbar-brand-fs:56px;   /* wordmark size - the strip height is sized off this */
  --topbar-h:calc(var(--topbar-nav-h) + var(--topbar-brand-h)); /* full bar - what every other calc() below cares about */
  --footer-h:38px;          /* slim footer, subtracted from the locked panel */

  /* ---- z-index ladder. Every stacking value on the site is one of these.
     Nothing uses a negative z-index: the old build put the backdrop at -3 and
     depended on body's background propagating to the canvas to stay visible,
     which silently breaks the moment anything paints a background on html or
     body. The art now lives inside the frame, so the whole page is a single
     top-down stack. ---- */
  --z-media:0;              /* background art inside the frame */
  --z-veil:1;               /* darkening wash over the art */
  --z-content:2;            /* sections, cards, text */
  --z-sticky:3;             /* sticky table headers inside cards */
  --z-topbar:50;            /* the one fixed/sticky chrome element */

  /* ---- the comic frame outline. Deliberately NOT a rectangle: each corner
     and mid-edge point is nudged a few px so the four sides read as drawn by
     hand rather than stroked by a box. Values are in px (not %) so the wobble
     stays the same size whether the frame is 600px or 3000px tall. ---- */
  --shape-outer:polygon(
    3px 7px, 18% 1px, 41% 8px, 66% 2px, 88% 7px, calc(100% - 4px) 2px,
    calc(100% - 1px) 18%, calc(100% - 8px) 41%, calc(100% - 2px) 63%,
    calc(100% - 7px) 85%, calc(100% - 3px) calc(100% - 3px),
    79% calc(100% - 1px), 54% calc(100% - 8px), 27% calc(100% - 2px),
    7% calc(100% - 7px), 2px calc(100% - 2px),
    7px 78%, 1px 55%, 8px 33%, 2px 16%
  );
  --shape-inner:polygon(
    2px 3px, 21% 8px, 44% 1px, 63% 7px, 85% 2px, calc(100% - 2px) 6px,
    calc(100% - 7px) 21%, calc(100% - 1px) 44%, calc(100% - 8px) 66%,
    calc(100% - 2px) 88%, calc(100% - 6px) calc(100% - 2px),
    82% calc(100% - 7px), 57% calc(100% - 1px), 24% calc(100% - 8px),
    9% calc(100% - 2px), 6px calc(100% - 6px),
    1px 74%, 8px 51%, 2px 29%, 7px 12%
  );
}

*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  margin:0;background:var(--bg);color:var(--text);
  font-family:var(--font);font-weight:500;font-size:16px;line-height:1.5;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
/* Form controls and <code>/<pre>/<kbd>/<samp> each ship a UA font-family that
   does NOT inherit - a control renders in the system UI font and code renders
   monospace no matter what body says. This is the rule that actually makes
   "one font everywhere" true; the body declaration alone never reaches them.
   Anything added later inherits normally and needs no entry here. */
button,input,select,textarea,optgroup,code,pre,kbd,samp{font-family:var(--font)}
/* Set only while the mobile sidebar's full-screen takeover is open, so the
   page behind it can't scroll along underneath. */
body.rail-locked{overflow:hidden;height:100%}
a{color:inherit;text-decoration:none}
:focus-visible{outline:3px solid var(--red);outline-offset:3px;border-radius:8px}
.wrap{max-width:1180px;margin:0 auto;padding:0 22px}
.num{font-variant-numeric:tabular-nums}

/* themed scrollbars */
html{scrollbar-width:thin;scrollbar-color:#3C3C43 var(--bg)}
::-webkit-scrollbar{width:11px;height:11px}
::-webkit-scrollbar-track{background:var(--bg)}
::-webkit-scrollbar-thumb{background:#33333A;border-radius:99px;border:3px solid var(--bg)}
::-webkit-scrollbar-thumb:hover{background:var(--red)}
::-webkit-scrollbar-corner{background:var(--bg)}

/* ==========================================================================
   TOPBAR - section nav only, centered. Branding and the live player count
   both moved out (the count now heads the home sidebar); with nothing else
   in the bar, centering the nav is just justify-content on its container.
   ========================================================================== */
.topbar{position:sticky;top:0;z-index:var(--z-topbar);
  background:rgba(0,0,0,.92);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}
/* No bottom rule - the wordmark and the nav under it read as one bar, and a
   divider between them just cut the bar in half. */
.topbar-brand{display:flex;align-items:center;justify-content:center;
  height:var(--topbar-brand-h);transition:height .32s cubic-bezier(.22,.61,.36,1)}
/* line-height:1 so the strip height is the glyph height plus the padding
   baked into --topbar-brand-h, not Roboto's 1.5 default on top of it.

   The min() is a floor guard, not the design size: "UndeadCarnival" is 14
   characters of Roboto Black, so the flat size runs wider than a narrow phone
   and, being nowrap under body{overflow-x:hidden}, would silently clip the
   end of the word rather than scroll. 10vw only ever binds below ~460px.

   font-size (not transform:scale) so the strip's height and the wordmark stay
   in step - a scale would paint bigger while still reserving the old box.
   Both sides of that pair are transitioned, so crossing the 900px breakpoint
   eases between the two sizes instead of snapping. */
.topbar-brand-text{font-weight:900;font-size:min(var(--topbar-brand-fs),10vw);
  line-height:1;letter-spacing:-.03em;color:var(--text);white-space:nowrap;
  transition:font-size .32s cubic-bezier(.22,.61,.36,1)}
.topbar-brand-accent{color:var(--red)}
.topbar-in{display:flex;align-items:center;justify-content:center;
  max-width:1420px;margin:0 auto;padding:0 26px;height:var(--topbar-nav-h)}

.nav{display:flex;align-items:center;gap:4px;flex-wrap:nowrap;overflow-x:auto;
  scrollbar-width:none}
.nav::-webkit-scrollbar{display:none}
.nav-btn{font-family:inherit;cursor:pointer;background:transparent;color:var(--muted);
  border:0;border-radius:8px;padding:9px 15px;white-space:nowrap;
  font-weight:700;font-size:13.5px;
  transition:background .15s,color .15s}
.nav-btn:hover{color:var(--text);background:rgba(255,255,255,.06)}
.nav-btn[aria-selected="true"]{background:var(--red);color:#fff}

/* Plain call-to-action pill, e.g. the "View Discussion" buttons in the MOTD
   sections. Outline by default, .solid for the one primary action in a row. */
.btn{display:inline-flex;align-items:center;justify-content:center;
  font-family:inherit;font-weight:700;font-size:13px;cursor:pointer;
  color:var(--text);background:transparent;
  border:1px solid var(--line);border-radius:999px;
  padding:9px 17px;white-space:nowrap;
  transition:background .15s,border-color .15s,color .15s}
.btn:hover{border-color:var(--muted);background:rgba(255,255,255,.05)}
.btn.solid{color:#fff;background:var(--red);border-color:var(--red)}
.btn.solid:hover{background:var(--red-dark);border-color:var(--red-dark)}

/* ==========================================================================
   THE COMIC PANEL
   .frame is a padded box filled with --ink and clipped to one irregular
   outline; .frame-in sits inside it clipped to a *differently* irregular
   one, so the edge varies in width the way a brushed panel border does
   rather than reading as a stroked rectangle.

   Both are black, and so is the page, so there is no drawn line anywhere -
   the panel shape reads purely from where the ARTWORK stops. Full bleed, so
   the art is as large as the viewport allows; the readable text column is
   re-constrained by .frame-body instead.
   ========================================================================== */
/* The page is exactly one viewport tall: topbar + panel, nothing below.
   .stage is given the remaining height explicitly and .frame/.frame-in fill
   it, so there is no document overflow and therefore no page scrollbar. Any
   list that outgrows its box scrolls INSIDE its own card instead. */
.stage{padding:0;background:var(--bg);
  height:calc(100vh - var(--topbar-h) - var(--footer-h))}
.frame{position:relative;margin:0;height:100%;
  padding:var(--ink-w);background:var(--ink);
  clip-path:var(--shape-outer)}
.frame-in{position:relative;height:100%;background:var(--panel);
  clip-path:var(--shape-inner);overflow:hidden}

/* Background art. Two stacked layers so a section change crossfades rather
   than hard-cutting one layer's background-image.

   The parallax translate is applied by JS to .media (the parent) and the
   ambient drift by CSS to .media-layer (the child). Keep them on separate
   elements: both write `transform`, so sharing an element means one silently
   overwrites the other. */
.media{position:absolute;inset:calc(var(--parallax) * -1) 0;
  z-index:var(--z-media);pointer-events:none}
.media-layer{position:absolute;inset:0;opacity:0;
  background:#141417 center/cover no-repeat;
  transition:opacity 1.4s ease;
  will-change:transform,opacity;
  transform:scale(1.06);
  animation:ambient 34s ease-in-out infinite alternate}
.media-layer.on{opacity:1}
/* Mostly a horizontal sweep now, with only a slight scale change. The image
   is inset by --parallax on every side, so a +/-3% translate can never pull
   an edge into frame. One full pass takes most of a slide's 14s on screen,
   so the movement reads as drift rather than motion. */
@keyframes ambient{
  0%  {transform:scale(1.10) translate3d(-3%,-0.6%,0)}
  100%{transform:scale(1.14) translate3d(3%,0.6%,0)}
}
/* pure black at 0% and 100% buries the hard top/bottom edge of the art */
.veil{position:absolute;inset:0;z-index:var(--z-veil);pointer-events:none;
  background:linear-gradient(180deg,
    #000 0%, rgba(0,0,0,.74) 10%, rgba(0,0,0,.50) 32%,
    rgba(0,0,0,.52) 66%, rgba(0,0,0,.78) 88%, #000 100%)}

/* The frame is full-bleed, so the readable column is constrained here. This is
   also the flex column every section stretches into, which is what lets the
   inner lists take "whatever height is left" rather than a magic number.
   height:100% needs an unbroken chain of definite heights above it -
   .stage -> .frame -> .frame-in all carry one. */
.frame-body{position:relative;z-index:var(--z-content);
  max-width:1320px;margin:0 auto;padding:22px 30px 26px;
  height:100%;display:flex;flex-direction:column;min-height:0}

/* ==========================================================================
   SECTIONS - exactly one visible at a time. Hidden sections are display:none
   so they take no space at all; the active one becomes the flex child that
   absorbs the panel's leftover height, so every section is exactly as tall as
   the panel and switching never resizes anything.
   ========================================================================== */
.section{display:none;min-height:0}
/* opacity-only on purpose - a transform here (even one that animates back to
   none) makes the section a containing block for any position:fixed
   descendant for as long as .is-active stays set, which is indefinitely
   while a tab is open. That silently broke the mobile sidebar drawer, which
   needs its fixed elements anchored to the real viewport, not this box. */
.section.is-active{display:flex;flex-direction:column;flex:1;min-height:0;
  animation:sectionIn .4s cubic-bezier(.22,.61,.36,1) both}
@keyframes sectionIn{from{opacity:0}to{opacity:1}}

/* cards. No backdrop-filter: it used to blur a fixed backdrop, which forced a
   full-size repaint of the blur on every scroll frame. A flat translucent
   fill over the art reads the same and costs nothing. */
.card{background:rgba(12,12,15,.86);
  border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow);
  overflow:hidden;display:flex;flex-direction:column;min-height:0}

/* ==========================================================================
   LEADERBOARD - tabs live inside the card, directly on top of the table
   ========================================================================== */
.tabs{flex:0 0 auto;display:flex;gap:6px;flex-wrap:wrap;padding:10px 12px;
  background:rgba(12,12,15,.96);border-bottom:1px solid var(--line)}
.tab{flex:0 1 auto;cursor:pointer;font-family:inherit;
  background:transparent;border:1px solid transparent;border-radius:8px;color:var(--muted);
  font-weight:700;font-size:13.5px;padding:7px 15px;
  transition:background .15s,color .15s,border-color .15s}
.tab:hover{color:var(--text);background:rgba(255,255,255,.05)}
.tab[aria-selected="true"]{background:var(--red);border-color:var(--red);color:#fff}

/* Fixed at 15 rows: it never shrinks below and never grows past, so a 25-row
   board scrolls inside the card and switching tabs never moves the page. */
/* Takes whatever height the card has left rather than a fixed row count, so
   the board fills the panel on a tall window and scrolls on a short one. */
.table-wrap{overflow:auto;flex:1;min-height:0}
.table-wrap.board,.table-wrap.bans{flex:1;min-height:0}
/* table-layout:fixed makes the browser share the width out from the header
   row instead of letting long content push the table wider than its box, so
   the board fits without a horizontal scrollbar. */
table{width:100%;table-layout:fixed;border-collapse:collapse;
  font-size:13px;font-variant-numeric:tabular-nums}
thead th{position:sticky;top:0;z-index:var(--z-sticky);background:rgba(12,12,15,.96);
  text-align:right;padding:10px 10px;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;
  font-size:10.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:var(--muted);border-bottom:1px solid var(--line)}
th.nosort{cursor:default}
th.sortable{cursor:pointer;user-select:none;transition:color .14s,background .14s}
th.sortable:hover{color:var(--text);background:#22222899}
th.sortable.sorted{color:var(--text)}
th.sortable.sorted .sort-arrow{color:var(--red)}
.sort-arrow{display:inline-block;margin-left:6px;font-size:8px;vertical-align:middle;
  color:transparent}
th.sortable:hover .sort-arrow{color:var(--muted)}
th.sortable.sorted:hover .sort-arrow{color:var(--red)}
thead th:nth-child(1),thead th:nth-child(2){text-align:left}
tbody td{padding:9px 10px;text-align:right;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;border-bottom:1px solid rgba(42,42,47,.75)}
tbody td:nth-child(1),tbody td:nth-child(2){text-align:left}
/* table-layout:fixed reads column widths off the FIRST row - the thead - not
   off tbody cells, so the widths below have to live on the th's themselves.
   Scoped to the leaderboard specifically: without them, #/Player (neither of
   which carries a width elsewhere) split the leftover space evenly with
   every stat column, which is how Player - three short words - ended up as
   wide as a whole stat column and change. The ban table has its own column
   shape and doesn't want this. */
.table-wrap.board thead th:nth-child(1){width:80px;text-align:left}
.table-wrap.board thead th:nth-child(2){width:200px;text-align:left}
tbody tr:last-child td{border-bottom:0}
tbody tr{transition:background .12s}
tbody tr:hover{background:rgba(224,27,39,.10)}
/* Rank number and (global ladder only) the shift chip beside it - see the
   isGlobal check in render(). Flex rather than relying on inline flow so the
   two sit on one baseline with a consistent gap regardless of chip content. */
td.pos{display:flex;align-items:center;gap:6px;color:var(--muted);width:80px}
tbody tr.prow:nth-of-type(1) td.pos .pos-n{color:var(--gold);font-weight:700}
tbody tr.prow:nth-of-type(2) td.pos .pos-n{color:var(--silver);font-weight:700}
tbody tr.prow:nth-of-type(3) td.pos .pos-n{color:var(--bronze);font-weight:700}
/* No overflow:hidden here - it would crop the tier glow off the name. The
   table scrolls inside .table-wrap, so a long name widens rather than clips. */
td.player{font-weight:500;font-size:15px;max-width:300px}
td.score{color:var(--red);font-weight:700}
/* ==========================================================================
   PLAYER ROW ACCORDION
   The breakdown is fetched on first open (?api=player) and cached for the
   session, so 100 rows x ~40 datapoints never ship with the page. A row's
   open/closed state is shown by the red row tint alone - no caret glyph. */
tr.prow{cursor:pointer}
tr.prow:focus-visible{outline:2px solid var(--red);outline-offset:-2px}
tr.prow.open{background:rgba(224,27,39,.10)}

tr.detail-row > td{padding:0;background:rgba(0,0,0,.45);border-bottom:0}
tr.detail-row.open > td{border-bottom:1px solid var(--line)}
/* 0fr -> 1fr animates to the content's real height without measuring it. */
.dp-shell{display:grid;grid-template-rows:0fr;
  transition:grid-template-rows .32s cubic-bezier(.22,.61,.36,1)}
tr.detail-row.open .dp-shell{grid-template-rows:1fr}
.dp-inner{overflow:hidden;min-height:0}
.dp-shell > *{min-height:0}

.dp-group{padding:12px 14px 4px}
.dp-group + .dp-group{padding-top:2px}
.dp-group h5{margin:0 0 8px;font-size:9px;font-weight:800;letter-spacing:.13em;
  text-transform:uppercase;color:var(--red)}
/* auto-fit tiles: ~8 across on desktop, 3 on a phone, no media query needed */
.dp-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(86px,1fr));gap:6px}
.dp-tile{background:rgba(255,255,255,.03);border:1px solid rgba(42,42,47,.7);
  border-radius:7px;padding:7px 9px;min-width:0}
.dp-tile .v{display:block;font-weight:800;font-size:14px;line-height:1.15;color:var(--text);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dp-tile .l{display:block;margin-top:2px;font-size:9.5px;color:var(--muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Versus/Co-op/Survival rows: two real lists, one per remaining group,
   side by side - reuses the team detail panel's .td-lists/.tl-row look
   rather than inventing a second list style. */
.dp-lists{padding:12px 14px 6px}
.dp-list{list-style:none;margin:0;padding:0}
.dp-msg{padding:14px;margin:0;color:var(--muted);font-size:12.5px}
.dp-foot{display:flex;align-items:center;justify-content:space-between;gap:14px;
  flex-wrap:wrap;padding:10px 14px 13px}
.dp-note{font-size:11.5px;color:var(--muted)}
.dp-link{font-size:11.5px;font-weight:700;color:var(--muted);
  border-bottom:1px solid transparent;transition:color .15s,border-color .15s}
.dp-link:hover{color:var(--red);border-bottom-color:var(--red)}

.table-foot{flex:0 0 auto;display:flex;justify-content:space-between;gap:14px;flex-wrap:wrap;
  padding:11px 18px;border-top:1px solid var(--line);
  font-size:12.5px;color:var(--muted);font-variant-numeric:tabular-nums}

/* ==========================================================================
   NAME TIERS
   Priority, highest first: banned > staff (operator or diamond) > Steam group
   member > podium medal. uc_name_class() in includes/site_data.php picks
   exactly one of these per name, so the rules never have to out-specify each
   other. The two staff colours are one rank, not two - which of them a name
   gets is the role on their site_staff row, not a higher standing. Group
   membership
   deliberately beats the podium medals - it's an earned, ongoing status, and
   a top-3 finish shouldn't bury that signal under gold/silver/bronze. Every
   glow is a two-layer recipe - a tight, near-opaque ~4px pass plus a short
   ~11px wash - which reads as glow. Widening that second radius turns it
   into bloom; don't.
   ========================================================================== */

/* 1. active ban - red and struck through, always, whatever else they are */
.player.banned{
  color:var(--ban-red);text-decoration:line-through;text-decoration-thickness:2px;
  text-shadow:0 0 4px rgba(255,58,58,1),0 0 11px rgba(255,58,58,.5)}

/* 2. staff - diamond. Beats everything below. */
@keyframes diamond-shift{
  0%,100%{color:var(--diamond);text-shadow:0 0 4px rgba(127,230,255,1),0 0 11px rgba(127,230,255,.55)}
  50%    {color:#FFFFFF;text-shadow:0 0 5px rgba(255,255,255,1),0 0 13px rgba(127,230,255,.8)}
}
.player.diamond{font-weight:800;animation:diamond-shift 3s ease-in-out infinite}

/* 2b. staff - Operator. Same rank as diamond, different colour, and built to
   the same two-layer recipe so the two read as siblings rather than as one
   outranking the other. */
@keyframes operator-shift{
  0%,100%{color:var(--operator);text-shadow:0 0 4px rgba(255,138,43,1),0 0 11px rgba(255,138,43,.55)}
  50%    {color:#FFC489;text-shadow:0 0 5px rgba(255,196,137,1),0 0 13px rgba(255,138,43,.85)}
}
.player.operator{font-weight:800;animation:operator-shift 3s ease-in-out infinite}

/* 3. active Steam group member - overrides the podium medals below */
.player.member{color:var(--green);
  text-shadow:0 0 4px rgba(75,194,107,.9),0 0 10px rgba(75,194,107,.4)}

/* 4. podium, for everyone else in the top 3 */
.player.gold{color:var(--gold);font-weight:700}
.player.silver{color:var(--silver);font-weight:700}
.player.bronze{color:var(--bronze);font-weight:700}

/* team rows: a banned owner repaints the name red and kills the tag */
.player.owner-banned{color:var(--ban-red);
  text-shadow:0 0 4px rgba(255,58,58,.95),0 0 11px rgba(255,58,58,.45)}

/* tags are white until the owner is in the group, then gold */
.team-tag{font-weight:800;color:#FFFFFF;letter-spacing:.03em;margin-right:6px;font-size:.9em}
.team-tag .brk{color:var(--muted);font-weight:600}
.team-tag.gold{color:var(--gold);text-shadow:0 0 4px rgba(255,201,66,.95),0 0 10px rgba(255,201,66,.45)}
.team-tag.dim{color:#5A5A62}
.team-tag.dim .brk{color:#44444B}

@media (prefers-reduced-motion:reduce){
  .player.diamond{animation:none;color:var(--diamond);
    text-shadow:0 0 4px rgba(127,230,255,.85)}
  .player.operator{animation:none;color:var(--operator);
    text-shadow:0 0 4px rgba(255,138,43,.85)}
}

/* ==========================================================================
   SERVER BROWSER
   ========================================================================== */
.filters{flex:0 0 auto;display:flex;gap:7px;padding:12px 18px;flex-wrap:wrap;align-items:center;
  border-bottom:1px solid rgba(42,42,47,.75)}
.chip{font-family:inherit;font-size:12.5px;font-weight:500;padding:6px 13px;border-radius:999px;
  border:1px solid var(--line);background:var(--surface-2);color:var(--muted);
  cursor:pointer;transition:all .15s}
.chip:hover{color:var(--text);border-color:#3C3C43}
.chip[aria-pressed="true"]{background:var(--red);border-color:var(--red);color:#fff}
ul.plain{list-style:none;margin:0;padding:0}

.srv-cols{display:grid;grid-template-columns:62px minmax(0,1fr) 148px 62px 52px;
  gap:10px;align-items:center;padding:0 14px}
/* Header and rows share one flex line - the expandable part on the left, a
   fixed Connect column on the right - so both .srv-cols grids are handed the
   exact same width and the columns line up without hardcoded offsets. */
.srv-line{display:flex;align-items:center;gap:10px;padding-right:14px;transition:background .12s}
.srv-line>.srv-cols{flex:1 1 auto;min-width:0}
.srv-line>.btn-connect,.srv-line>.conn-off,.srv-line>.c-conn{flex:0 0 var(--conn-col)}
.srv-head{flex:0 0 auto;border-bottom:1px solid var(--line);background:var(--surface-2);
  padding-top:7px;padding-bottom:7px;
  font-size:10.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}
.srv-head .r{text-align:right}
.srv-head .c-conn{text-align:center}
#srvList{flex:1;min-height:0;overflow-y:auto}
.srv-item{border-bottom:1px solid rgba(42,42,47,.4)}
.srv-item:last-child{border-bottom:0}
.srv-item.is-hidden{display:none}
.srv-item:nth-child(even) .srv-line{background:rgba(255,255,255,.018)}
.srv-line:hover{background:rgba(224,27,39,.10)}
.srv-item.open .srv-line{background:rgba(224,27,39,.14)}
.srv{width:100%;border:0;font:inherit;color:inherit;text-align:left;cursor:pointer;
  padding-top:6px;padding-bottom:6px;font-size:12.5px;line-height:1.5;background:transparent}
.srv-item.off .srv{color:var(--muted);opacity:.55}
.conn-off{text-align:center;font-size:11px;font-weight:700;letter-spacing:.05em;
  text-transform:uppercase;color:#55555C}
/* Gamemode badge, from the live A2S keywords. Colour-coded per mode; a
   server that advertises no mode gets the neutral .m-none treatment rather
   than being labelled with a guess. */
.gm{font-size:9px;text-align:center;font-weight:800;letter-spacing:.03em;
  border-radius:3px;padding:3px 2px;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;border:1px solid transparent}
.gm.m-coop     {color:#5FD07E;background:rgba(75,194,107,.13);border-color:rgba(75,194,107,.4)}
.gm.m-versus   {color:#FF6A72;background:rgba(224,27,39,.15); border-color:rgba(224,27,39,.45)}
.gm.m-survival {color:#F0B44F;background:rgba(232,163,61,.13);border-color:rgba(232,163,61,.42)}
.gm.m-scavenge {color:#7FC4FF;background:rgba(90,160,255,.13);border-color:rgba(90,160,255,.4)}
.gm.m-realism  {color:#C79BFF;background:rgba(160,110,255,.13);border-color:rgba(160,110,255,.4)}
.gm.m-mutation {color:#FF9ED2;background:rgba(255,110,190,.12);border-color:rgba(255,110,190,.38)}
.gm.m-none     {color:#5A5A62;background:var(--surface-2);border-color:var(--line)}
/* name on line one, ip:port on line two - the main row carries the address so
   you never have to open a server to read where it is */
.srv .nm-wrap{display:flex;flex-direction:column;gap:1px;min-width:0}
.srv .nm{display:flex;align-items:center;gap:6px;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;font-weight:500}
.srv .ip{font-size:10.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.srv .mp{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pop{font-size:12px;text-align:right;font-variant-numeric:tabular-nums}
.pop.full{color:var(--red)}
.bar{height:5px;background:var(--surface-2);border-radius:999px;overflow:hidden}
.bar i{display:block;height:100%;background:var(--green);border-radius:999px;
  transition:width .4s cubic-bezier(.22,.61,.36,1)}
.bar i.hot{background:var(--amber)}
.bar i.full{background:var(--red)}

/* VAC secure badge - live A2S_INFO data, shown on online rows only */
.vac-badge{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
  font-size:9px;font-weight:800;letter-spacing:.06em;line-height:1;
  color:var(--green);border:1px solid rgba(75,194,107,.55);border-radius:4px;
  padding:2px 4px;background:rgba(75,194,107,.12);
  text-shadow:0 0 4px rgba(75,194,107,.8)}

.srv-detail{display:grid;grid-template-rows:0fr;
  transition:grid-template-rows .3s cubic-bezier(.22,.61,.36,1)}
.srv-item.open .srv-detail{grid-template-rows:1fr}
.srv-detail>.inner{overflow:hidden}
.detail-pad{padding:12px 14px 14px;background:rgba(0,0,0,.34);border-top:1px solid var(--line)}
.btn-connect{display:inline-flex;align-items:center;justify-content:center;gap:7px;
  padding:6px 12px;border-radius:999px;
  background:var(--red);border:0;color:#fff;font:700 12px var(--font);cursor:pointer;
  transition:background .15s,transform .15s}
.btn-connect:hover{background:var(--red-dark);transform:translateY(-1px)}
.pl-cols{display:grid;
  grid-template-columns:22px minmax(0,1fr) 84px 66px 100px 66px 58px 66px;
  gap:10px;padding:4px 2px;font-size:12.5px;font-variant-numeric:tabular-nums}
.pl-rank{display:flex;align-items:center;justify-content:flex-end;gap:5px}
.pl-head{font-size:10px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  color:var(--muted);border-bottom:1px solid var(--line);padding-bottom:5px;margin-bottom:2px}
.pl-head .r,.pl .r{text-align:right}
.pl{border-bottom:1px solid rgba(42,42,47,.35)}
.pl:last-child{border-bottom:0}
.pl .n{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pl .i{color:var(--muted);font-size:11px}
.detail-msg{padding:10px 2px;color:var(--muted);font-size:12.5px}
.srv-empty{padding:22px 18px;text-align:center;color:var(--muted);font-size:13.5px}

/* ==========================================================================
   BAN LIST
   ========================================================================== */
.ban-search,.srv-search{flex:1 1 220px;min-width:160px;font:inherit;font-size:12.5px;color:var(--text);
  background:var(--surface-2);border:1px solid var(--line);border-radius:999px;
  padding:8px 15px;transition:border-color .15s}
.ban-search::placeholder,.srv-search::placeholder{color:var(--muted)}
.ban-search:focus,.srv-search:focus{outline:none;border-color:var(--red)}
.srv-search{flex:1 1 240px}

/* Same table-layout:fixed rule as the leaderboard table:
   widths have to live on the thead th's, not the tbody td's, or every column
   splits the table width evenly - which is how Ban reason, free text that
   actually needs room, ended up as cramped as the four-character Length
   column next to it and got clipped almost immediately. */
/* Order: status, player, date issued, reason, length, expires in, on record.
   Reason is the ONLY column left without a width, so it absorbs whatever the
   six fixed ones don't claim - that is what keeps free text readable while
   the short, predictable columns stay at their natural size. */
.table-wrap.bans thead th:nth-child(1){width:104px;text-align:left}
.table-wrap.bans thead th:nth-child(2){width:168px}
.table-wrap.bans thead th:nth-child(3){width:108px}
.table-wrap.bans thead th:nth-child(4){text-align:left}
.table-wrap.bans thead th:nth-child(5){width:96px}
.table-wrap.bans thead th:nth-child(6){width:88px}
.table-wrap.bans thead th:nth-child(7){width:84px}
/* Reason moved from column 2 to 4, so it falls outside the global
   "first two columns are left-aligned" rule and has to say so itself. */
.table-wrap.bans td.reason{overflow:hidden;text-overflow:ellipsis;text-align:left}
/* td.player globally refuses overflow:hidden so the leaderboard's tier glow
   isn't cropped - but a ban row's name carries no tier class and so no glow,
   and with seven columns to fit there is no spare width for a long name to
   run into the date column with. Clip it here and nowhere else. */
.table-wrap.bans td.player{max-width:none;overflow:hidden;text-overflow:ellipsis}
.table-wrap.bans td.len{color:var(--red);font-weight:700}
.table-wrap.bans td.exp{color:var(--muted)}

/* Bans on record. Escalating severity on the digit only - deliberately no
   pill, border or background, because the status pill at the start of the
   row is what should carry the row's visual weight. 4 is really "4 or
   more"; the class is clamped in banRecordCell(). */
.rec{font-weight:800}
.rec-1{color:var(--muted)}
.rec-2{color:var(--gold)}
.rec-3{color:var(--operator)}
.rec-4{color:var(--ban-red)}
.ban-empty{text-align:center !important;color:var(--muted);
  padding:26px 18px !important;white-space:normal !important}

/* Shared prev/next pager - the ban list and the (now paginated) team list. */
.pager{display:flex;align-items:center;gap:10px}
.pager .pg{font:inherit;font-size:10px;line-height:1;cursor:pointer;
  color:var(--muted);background:var(--surface-2);
  border:1px solid var(--line);border-radius:6px;padding:5px 9px;
  transition:background .14s,color .14s,border-color .14s}
.pager .pg:hover:not(:disabled){color:#fff;background:var(--red);border-color:var(--red)}
.pager .pg:disabled{opacity:.35;cursor:default}

.status-pill{display:inline-flex;align-items:center;font-size:10.5px;font-weight:800;
  letter-spacing:.05em;text-transform:uppercase;padding:3px 10px;border-radius:999px}
.status-pill.active{color:var(--ban-red);background:rgba(255,58,58,.12);
  border:1px solid rgba(255,58,58,.45);text-shadow:0 0 4px rgba(255,58,58,.65)}
.status-pill.expired{color:var(--muted);background:rgba(255,255,255,.05);border:1px solid var(--line)}
/* A ban an admin lifted is not the same as one that ran out - it gets its own
   colour so moderation history stays readable at a glance. */
.status-pill.unbanned{color:#7FC4FF;background:rgba(90,160,255,.12);
  border:1px solid rgba(90,160,255,.42)}

/* ==========================================================================
   TOP TEAMS - one full-width champion tile over a 4-up grid
   ========================================================================== */
.team-grid{display:grid;gap:14px}
.team-rest{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.team-tile{position:relative;background:rgba(12,12,15,.9);border:1px solid var(--line);
  border-radius:var(--r-lg);padding:16px 18px;box-shadow:var(--shadow);
  display:flex;flex-direction:column;gap:12px;
  transition:border-color .18s,transform .18s}
.team-tile:hover{border-color:#3C3C43;transform:translateY(-2px)}

/* the champion tile keeps a permanent gold rim + halo */
@keyframes champ-glow{
  0%,100%{box-shadow:0 0 0 1px rgba(255,201,66,.6),0 0 14px rgba(255,201,66,.24),var(--shadow)}
  50%    {box-shadow:0 0 0 1px rgba(255,201,66,.9),0 0 22px rgba(255,201,66,.4),var(--shadow)}
}
.team-tile.champ{border-color:rgba(255,201,66,.6);padding:20px 22px;
  background:linear-gradient(180deg,rgba(255,201,66,.07),rgba(12,12,15,.92) 60%);
  animation:champ-glow 3.4s ease-in-out infinite}
.team-tile.champ:hover{border-color:rgba(255,201,66,.9)}

.tt-rank{font-weight:900;font-size:12px;letter-spacing:.14em;color:var(--muted);
  font-variant-numeric:tabular-nums}
.team-tile.champ .tt-rank{color:var(--gold)}
.tt-id{display:flex;align-items:baseline;gap:2px;min-width:0;flex-wrap:wrap}
/* No overflow:hidden here - it would crop the tier glow off the name.
   teams.name is varchar(24), so wrapping is the safe overflow behaviour. */
.tt-name{font-weight:700;font-size:18px;line-height:1.2;min-width:0;overflow-wrap:anywhere}
.team-tile.champ .tt-name{font-size:clamp(22px,2.6vw,30px);font-weight:900;letter-spacing:-.015em}
.team-tile.champ .team-tag{font-size:.62em}

.tt-stats{display:flex;flex-wrap:wrap;gap:18px;margin-top:auto}
.team-tile.champ .tt-stats{gap:34px}
.tt-stats .v{display:block;font-weight:900;font-size:17px;line-height:1.15;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px}
.team-tile.champ .tt-stats .v{font-size:21px}
.tt-stats div:first-child .v{color:var(--red)}
.tt-stats .l{display:block;font-size:10px;color:var(--muted);text-transform:uppercase;
  letter-spacing:.07em;margin-top:3px}

/* ==========================================================================
   FOOTER - outside the frame, flat black, no rules or boxes
   ========================================================================== */
footer{background:var(--bg);font-size:13.5px;color:var(--muted)}
footer .wrap{max-width:1320px;padding:0 30px}

.foot-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:34px;padding:34px 0 24px}
.foot-col h4{margin:0 0 12px;font-size:10.5px;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted)}

.f-ctry{display:grid;grid-template-columns:22px minmax(0,1fr) 48px;gap:10px;
  align-items:center;padding:4px 0}
.f-ctry img{width:22px;height:15px;object-fit:cover;border-radius:2px;background:var(--surface-2)}
.f-ctry .cn{font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.f-ctry .cv{font-size:12px;text-align:right;color:var(--muted)}

.f-links{list-style:none;margin:0;padding:0}
.f-links li{margin-bottom:8px}
.f-links li:last-child{margin-bottom:0}
.f-links a{font-size:13.5px;color:var(--muted);transition:color .15s;cursor:pointer}
.f-links a:hover{color:var(--red)}

.foot-bottom{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;
  padding:0 0 30px;font-size:12.5px}

/* ==========================================================================
   FOOTER - one slim line under the panel, inside the viewport lock
   ========================================================================== */
.foot{height:var(--footer-h);background:var(--bg);display:flex;align-items:center}
.foot-in{display:flex;align-items:center;width:100%;
  max-width:1320px;margin:0 auto;padding:0 30px;
  font-size:11.5px;color:#6E7076}
.foot .fl{font-weight:700;color:var(--muted);white-space:nowrap}

/* ==========================================================================
   MOTION
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
  .section.is-active{animation:none}
  /* open/close instantly rather than not at all */
  .dp-shell{transition:none}
  .sp-body,.team-detail-card,.show-panel{transition:none}
  .topbar-brand,.topbar-brand-text{transition:none}
  .media-layer{transition:none;transform:none !important}
  .bar i{transition:none}
  .team-tile,.team-tile:hover{transition:none;transform:none}
  .team-tile.champ{animation:none;
    box-shadow:0 0 0 1px rgba(255,201,66,.7),0 0 18px rgba(255,201,66,.3),var(--shadow)}
  html{scroll-behavior:auto}
}

/* ==========================================================================
   RANK SHIFT
   The score cron stores rank_shift as old_rank - new_rank, so positive means
   the player climbed. Only the global ladder tracks it (mode boards rank off
   the stored rank_versus/rank_coop/rank_survival columns instead, which carry
   no history) - see uc_mode_rank_bits() vs uc_global_rank_bits() in
   queries.php. The chip renders inside the # cell (td.pos), not a column of
   its own.
   ========================================================================== */
.pos-n{min-width:24px;font-weight:700;color:var(--text)}
.shift{display:inline-block;font-size:10px;font-weight:800;letter-spacing:.02em;line-height:1;
  padding:2px 5px;border-radius:4px;font-variant-numeric:tabular-nums}
.shift.up{color:var(--green);background:rgba(75,194,107,.12);
  text-shadow:0 0 4px rgba(75,194,107,.7)}
.shift.down{color:var(--ban-red);background:rgba(255,58,58,.12);
  text-shadow:0 0 4px rgba(255,58,58,.6)}
.shift.flat{color:#4A4A52;background:transparent}

/* ==========================================================================
   HOME
   ========================================================================== */
.home-grid{display:grid;grid-template-columns:minmax(0,1fr) 330px;gap:16px;
  flex:1;min-height:0}
/* Stays stretched so .motd has a definite height to shrink within on a short
   window; the cards inside it hug their own content (see .motd below) rather
   than padding themselves out to fill it. */
.home-main{display:flex;flex-direction:column;gap:14px;min-height:0}
/* The rail renders as one continuous panel rather than a stack of separate
   floating cards - each section keeps its own padding, but the card chrome
   (background/border/radius/shadow) now lives on the rail itself, with thin
   rules between sections instead of gaps between boxes.

   align-self:start + max-height:100% is what stops the panel's bottom edge
   from floating below its last row: as a grid item it would otherwise stretch
   to the row's full height, drawing border and background down past where
   Recent bans ends. Hugging its content instead, and only capping (and
   scrolling) once the cards actually outgrow the panel. */
.home-rail{display:flex;flex-direction:column;align-self:start;max-height:100%;
  min-height:0;overflow-y:auto;overflow-x:hidden;
  background:rgba(12,12,15,.86);border:1px solid var(--line);
  border-radius:var(--r-lg);box-shadow:var(--shadow)}
.home-rail .card{background:transparent;border:0;border-radius:0;box-shadow:none;
  border-bottom:1px solid var(--line)}
.home-rail .card:last-child{border-bottom:0}
.home-rail .rail-live,
.home-rail .rail-card{flex:0 0 auto}

/* Off-canvas trigger + scrim - inert on desktop, wired up under the
   max-width:900px query below where the rail itself goes fixed. One button
   throughout: it morphs from a burger into an X (see aria-expanded rules
   below) rather than handing off to a separate close button, so the tap
   target never moves and never ends up buried under other chrome. */
.rail-toggle{display:none;position:fixed;right:18px;bottom:18px;z-index:70;
  width:48px;height:48px;padding:0;align-items:center;justify-content:center;
  flex-direction:column;gap:5px;border-radius:50%;border:1px solid var(--line);
  background:var(--red);box-shadow:var(--shadow);cursor:pointer}
.rail-toggle span{display:block;width:20px;height:2px;background:#fff;border-radius:2px;
  transition:transform .25s ease,opacity .15s ease}
.rail-toggle[aria-expanded="true"] span:first-child{transform:translateY(7px) rotate(45deg)}
.rail-toggle[aria-expanded="true"] span:nth-child(2){opacity:0}
.rail-toggle[aria-expanded="true"] span:last-child{transform:translateY(-7px) rotate(-45deg)}
.rail-backdrop{display:none;position:fixed;inset:0;z-index:65;
  background:rgba(0,0,0,.55);opacity:0;pointer-events:none;transition:opacity .25s ease}
.rail-backdrop.open{opacity:1;pointer-events:auto}

/* --- MOTD ---
   flex:0 1 auto, not 1 1 auto: the card ends where its content ends, instead
   of stretching to eat the column's leftover height and leaving a tall blank
   panel below the last section. It can still SHRINK - on a short window it
   gives way and scrolls internally rather than pushing the stat row off the
   panel. Leftover height in the column is simply background art now, which is
   what the space under the stat row already was. */
.motd{flex:0 1 auto;min-height:0;padding:22px 24px;gap:0;overflow-y:auto}
.motd-head{display:flex;align-items:baseline;justify-content:space-between;
  gap:16px;flex-wrap:wrap;margin-bottom:12px}
.motd-brand{margin:0;display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  font-weight:900;font-size:clamp(21px,2.4vw,28px);letter-spacing:-.015em}
.motd-brand-word{color:var(--text)}
.motd-updated{flex:0 0 auto;font-size:11px;font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;color:var(--muted);white-space:nowrap}
/* Opening paragraphs, stacked short rather than one dense block - the same
   shape a Steam group's front page or a Ko-fi pitch uses, where each line is
   its own beat and the eye travels down instead of across. */
.motd-lead{margin:0 0 24px}
.motd-lead p{margin:0 0 11px;font-size:15px;color:#C9CBD2;line-height:1.6}
.motd-lead p:last-child{margin-bottom:0}
.motd p{margin:0 0 9px;font-size:13.5px;color:var(--muted);line-height:1.6}
.motd p:last-of-type{margin-bottom:0}

/* A plain stacked list, like a Steam group announcement post - a heading per
   topic, a short intro line, then the specifics as a real list, with a thin
   rule separating one topic from the next. No colour, no boxes: the
   separators alone are enough to tell the topics apart. */
.motd-sections{display:flex;flex-direction:column}
.motd-section{padding:17px 0;border-bottom:1px solid var(--line)}
.motd-section:first-child{padding-top:0}
.motd-section:last-child{padding-bottom:0;border-bottom:0}
.motd-section-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.motd-section h3{margin:0 0 7px;font-size:15px;font-weight:800;
  letter-spacing:-.005em;color:var(--text)}
.motd-date{flex:0 0 auto;font-size:9.5px;font-weight:800;letter-spacing:.06em;
  text-transform:uppercase;color:#55555C}
.motd-section p{font-size:12.5px}
.motd-section ul{margin:8px 0 0;padding-left:19px}
.motd-section li{font-size:12.5px;color:var(--muted);line-height:1.65;margin-bottom:4px}
.motd-section li:last-child{margin-bottom:0}
.motd-section li.motd-li-bare{list-style:none}
/* A slash command reads as code, not prose. Red rather than plain text: these
   are the thing you actually have to type, so they should catch the eye ahead
   of the sentence explaining them. No side margins - the chip sits inline
   mid-sentence now, and the surrounding spaces already space it. */
.motd-cmd{display:inline-block;padding:1px 6px;border-radius:5px;
  background:rgba(224,27,39,.14);border:1px solid rgba(224,27,39,.42);
  font:700 11px/1.6 var(--font);letter-spacing:.02em;
  color:#FF6B72}

/* The one or two lines nobody gets to say they missed. */
.motd-strong{font-weight:800;color:var(--ban-red)}

/* Per-section link to the Steam thread the rules actually live in. Outlined,
   not solid - the solid pill is spoken for by the community links up top. */
.motd-more{margin-top:11px;padding:7px 14px;font-size:12px}

/* --- the three rotating stat panels --- */
/* Short, fixed-height cards so the bottom of this row lines up with the
   bottom of the right-hand rail instead of stretching past it. */
.show-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;
  flex:0 0 auto;height:110px}
/* The card itself is static - no opacity or transform on it, so it never
   blinks. Only .sp-body (the text) fades and moves on each rotation. */
.show-panel{padding:10px 16px;justify-content:center;gap:0;overflow:hidden}
.sp-body{display:flex;flex-direction:column;min-width:0;
  opacity:0;transform:translateY(8px);
  transition:opacity .28s ease,transform .28s ease}
.sp-body.in{opacity:1;transform:none}
.sp-val{display:block;font-weight:900;line-height:1;letter-spacing:-.02em;
  font-size:clamp(22px,2.5vw,32px);color:var(--text);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Label and sub used to be two stacked lines; now one row so the pair reads
   as a single short sentence even though each half is coloured separately. */
.sp-flavor{display:flex;align-items:baseline;min-width:0;margin-top:7px;
  white-space:nowrap;overflow:hidden}
.sp-label{flex:0 0 auto;font-size:12.5px;font-weight:700;color:var(--red)}
.sp-dot{flex:0 0 auto;margin:0 6px;font-size:11px;color:var(--muted)}
.sp-sub{flex:1 1 auto;min-width:0;font-size:11.5px;color:var(--muted);
  overflow:hidden;text-overflow:ellipsis}
.sp-sub.who{color:var(--gold);font-weight:700;
  text-shadow:0 0 4px rgba(255,201,66,.55)}

/* --- right rail --- */
/* Short banner at the top of the rail - just the live player count, moved
   out of the topbar now that the topbar is nav-only. It carries no separate
   heading: "<n> Players online" reads as one line in the MOTD brand's own
   weight/scale, so it IS this card's title. White throughout - only the
   number takes the green->red population colour, so the tint is the one
   thing that changes and the eye goes straight to it. The max size is a
   step under the MOTD's 28px because this sits in a 330px rail and has to
   survive a four-digit count without wrapping. */
/* Top padding matches .motd's 22px (both sit behind a 1px card border, so the
   two headlines start on the same line across the grid); the sides stay
   narrower because this column is half the width. */
.rail-live{padding:22px 16px 13px;gap:0}
.rail-live-body{display:flex;align-items:baseline;justify-content:center;
  gap:.3em;text-align:center;color:var(--text);
  font-weight:900;font-size:clamp(18px,2vw,23px);letter-spacing:-.015em;
  line-height:1.15}
.rail-live-count{font-weight:inherit}
.rail-live .l{color:var(--text);font-weight:inherit}

/* Three secondary figures under the headline count - servers up, open slots,
   players active in the last day. No dividers between them - just even
   spacing - so they read as one compact panel, not four. */
.rail-live-stats{display:flex;margin-top:10px;padding-top:10px;
  border-top:1px solid var(--line)}
.rls-item{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;
  text-align:center}
.rls-v{font-size:13px;font-weight:800;color:var(--text);white-space:nowrap}
.rls-l{font-size:9px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted);white-space:nowrap}

.rail-card{padding:14px 16px 12px;gap:0}
.rail-card h4{flex:0 0 auto;margin:0 0 10px;font-size:10px;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted)}
.rail-body{display:flex;flex-direction:column;gap:1px}

.r-player{display:grid;grid-template-columns:20px minmax(0,1fr) auto;gap:9px;
  align-items:center;padding:3px 0;font-size:12.5px}
.r-player img{width:20px;height:13px;object-fit:cover;border-radius:2px;background:var(--surface-2)}
.r-player .rn{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}
.r-player .rt{font-size:11px;color:var(--muted);white-space:nowrap}

.r-staff{display:grid;grid-template-columns:26px minmax(0,1fr) auto;gap:9px;
  align-items:center;padding:5px 0;font-size:12.5px}
.rs-avatar{width:26px;height:26px;border-radius:50%;object-fit:cover;
  background:var(--surface-2);flex:0 0 26px}
.rs-avatar-blank{display:block;border:1px solid var(--line)}
.rs-id{display:flex;flex-direction:column;min-width:0;gap:1px;overflow:hidden}
/* No colour of its own - .rs-name also carries .player + its tier class, and
   those own the colour/glow so a staff name looks the same here as it does on
   a leaderboard row. overflow:hidden would crop that glow, so the name is
   allowed to run and .rs-id clips instead. */
.rs-name{min-width:0;text-overflow:ellipsis;white-space:nowrap}
.rs-role{font-size:10px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;
  color:var(--diamond);white-space:nowrap}
.rs-role.operator{color:var(--operator)}
/* Steam level - a small pill rather than plain text, so it doesn't read as
   part of the role label next to it. */
.rs-level{font-size:10.5px;font-weight:800;color:var(--muted);
  background:var(--surface-2);border:1px solid var(--line);border-radius:999px;
  padding:1px 8px;white-space:nowrap;font-variant-numeric:tabular-nums}

/* One compact line per ban, same shape as .r-player - name, how long ago,
   how long the ban runs. The left rail carries the status colour so active
   bans read at a glance without needing the reason text. */
.r-ban{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:9px;
  align-items:center;padding:3px 0 3px 9px;font-size:12.5px;
  border-left:2px solid var(--line)}
.r-ban.active  {border-left-color:var(--ban-red)}
.r-ban.expired {border-left-color:#3B3B41}
.r-ban.unbanned{border-left-color:rgba(90,160,255,.6)}

.rb-name{font-weight:500;color:var(--text);
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rb-when{font-size:11px;color:var(--muted);white-space:nowrap}
.rb-len{font-size:10px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;
  white-space:nowrap;color:var(--muted)}
.r-ban.active .rb-len{color:var(--ban-red)}
.r-ban.unbanned .rb-len{color:#7FC4FF}

/* ==========================================================================
   TEAMS - roster list on the left, full breakdown on the right
   ========================================================================== */
.team-split{display:grid;grid-template-columns:340px minmax(0,1fr);gap:16px;
  flex:1;min-height:0}
/* .card is already a flex column (see .card above); the head stays its
   natural size and the list is the only part that grows, so the card's
   overall height - fixed to match the detail column via the shared grid row
   - never changes when a different team is picked. Only #teamList scrolls.
   Capped at roughly head + 20 rows + the pager foot (TEAMS_PAGE_SIZE) rather
   than stretching to fill the whole locked panel - on a tall viewport a
   full page of rows stopped well short of the panel's bottom edge, leaving
   a dead gap between the last row and the pager. Still stretches to match
   the detail column up to that cap, and still shrinks on a short viewport
   exactly as before (max-height only ever pulls it in, never pads it out). */
.team-list-card{min-height:0;padding:6px;max-height:min(100%, 960px)}
.team-list-head{flex:0 0 auto;padding:8px 9px 6px}
.team-list-cols{display:grid;grid-template-columns:26px minmax(0,1fr) auto;
  gap:10px;padding:0 11px 7px;font-size:9.5px;font-weight:800;
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
  border-bottom:1px solid var(--line)}
.tlc-score{text-align:right}
.team-list{overflow-y:auto;min-height:0;flex:1;display:flex;flex-direction:column;gap:2px}

/* Right column: the grid stretches this to the row height, and the detail
   card inside now fills it (flex:1 1 auto below) rather than sizing to its
   own roster. Both columns therefore end on the same line - previously the
   card stopped at its content height while the list card ran the full height
   beside it, so a small team left the list visibly overhanging. The card
   itself scrolls, so this wrapper must not. Same max-height cap as
   .team-list-card, for the same reason - without it this column kept
   stretching to the full locked panel after the list was capped, so the two
   columns no longer lined up. */
.team-detail-col{display:flex;flex-direction:column;min-height:0;overflow:hidden;
  max-height:min(100%, 960px)}

.team-row{display:grid;grid-template-columns:26px minmax(0,1fr) auto;
  gap:10px;align-items:center;width:100%;
  font:inherit;color:inherit;text-align:left;cursor:pointer;
  background:transparent;border:1px solid transparent;border-radius:9px;
  padding:9px 11px;transition:background .14s,border-color .14s}
.team-row:hover{background:rgba(255,255,255,.04);border-color:var(--line)}
.team-row[aria-selected="true"]{background:rgba(224,27,39,.14);border-color:rgba(224,27,39,.5)}
.tr-rank{font-size:11px;font-weight:900;letter-spacing:.06em;color:var(--muted);
  font-variant-numeric:tabular-nums}
.team-row[aria-selected="true"] .tr-rank{color:var(--red)}
.tr-id{display:flex;align-items:baseline;gap:2px;min-width:0}
.tr-name{font-size:14px;font-weight:700;min-width:0;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.tr-score{font-size:12.5px;font-weight:700;color:var(--red)}

.team-detail-card{flex:1 1 auto;min-height:0;overflow-y:auto;
  padding:20px 22px;gap:0;
  opacity:0;transform:translateY(6px);
  transition:opacity .35s ease,transform .35s ease}
.team-detail-card.in{opacity:1;transform:none}
.td-head{display:flex;align-items:baseline;justify-content:space-between;
  gap:14px;flex-wrap:wrap;flex:0 0 auto}
.td-id{display:flex;align-items:baseline;gap:3px;min-width:0;flex-wrap:wrap}
/* no overflow:hidden - it would crop the tier glow off the team name */
.td-name{font-weight:900;font-size:clamp(20px,2.4vw,28px);letter-spacing:-.015em;
  overflow-wrap:anywhere}
.td-rankbadge{font-size:10.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted);background:var(--surface-2);border:1px solid var(--line);
  border-radius:999px;padding:4px 11px;white-space:nowrap}


/* --- everything below the roster ---------------------------------------
   A stat grid over two side-by-side lists. This block exists to fill the
   space under the roster, which was previously blank on small teams. */
.td-bottom{flex:0 0 auto;margin-top:18px;padding-top:16px;border-top:1px solid var(--line)}

/* Four label/value lists in two columns. This replaced a stat grid whose
   wide tiles wrapped awkwardly and read as unrelated numbers. */
.td-lists{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:20px 26px}
.td-list h5{margin:0 0 10px;font-size:9.5px;font-weight:800;
  letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}

/* Score-by-mode rows carry a share bar; highlight rows carry a player name.
   Both use .tl-row so the two columns line up with each other. */
.tl-row{display:grid;grid-template-columns:78px minmax(0,1fr) auto 38px;
  gap:10px;align-items:center;padding:5px 0;font-size:12px;
  border-bottom:1px solid rgba(42,42,47,.4)}
.tl-row:last-child{border-bottom:0}
.tl-row.best{grid-template-columns:96px minmax(0,1fr) auto}
/* plain label/value, no bar */
.tl-row.pair{grid-template-columns:minmax(0,1fr) auto}
.tl-row.pair .tl-label{white-space:normal}
.tl-label{color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tl-who{font-weight:600;color:var(--text);
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tl-bar{height:6px;border-radius:99px;background:var(--surface-2);overflow:hidden}
.tl-bar i{display:block;height:100%;border-radius:99px}
.seg-versus{background:var(--red)}
.seg-coop{background:var(--green)}
.seg-survival{background:var(--amber)}
.tl-val{font-weight:700;color:var(--text);white-space:nowrap}
.tl-pct{color:var(--muted);text-align:right;white-space:nowrap}
.tl-empty{margin:0;font-size:12px;color:var(--muted)}

/* flex:0 0 auto, NOT min-height:0. The detail card is a flex column and this
   list now has content after it; letting it shrink below its natural height
   made the roster rows spill out and overlap the lists underneath. The card
   itself scrolls, so nothing here needs to compress. */
.tm-list{display:flex;flex-direction:column;flex:0 0 auto}
.tm-row{display:grid;
  grid-template-columns:minmax(0,1.6fr) 92px 82px 72px 72px 78px 84px 84px;
  gap:10px;align-items:center;padding:8px 0;
  border-bottom:1px solid rgba(42,42,47,.5);font-size:12.5px;
  font-variant-numeric:tabular-nums}
.tm-row:last-child{border-bottom:0}
.tm-head{position:sticky;top:0;background:rgba(12,12,15,.96);z-index:var(--z-sticky);
  font-size:9.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);border-bottom:1px solid var(--line);padding:6px 0}
.tm-who{display:flex;align-items:center;gap:7px;min-width:0}
.tm-flag{width:19px;height:13px;object-fit:cover;border-radius:2px;
  background:var(--surface-2);flex:0 0 19px}
/* text-overflow does nothing without overflow:hidden, so this used to not
   truncate at all - a long name plus its tag and flag simply ran on across
   the rank and score columns. overflow:hidden was originally left off to
   stop the tier glow being cropped; the padding/negative-margin pair buys
   the glow its 13px (the widest blur radius any tier uses) back on the top,
   bottom and left WITHOUT moving the text or changing the row's geometry.
   No padding on the right: that edge is where the ellipsis lands, so the
   glow past it should be cut. */
.tm-name{display:block;font-weight:600;font-size:13.5px;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  padding:13px 0 13px 13px;margin:-13px 0 -13px -13px}
.tm-name:hover{text-decoration:underline}
.tm-rank{display:flex;align-items:center;gap:6px;font-weight:700}
.tm-rank.none{color:#55555C;font-size:11px;font-weight:500;font-style:italic}
.tm-c{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--muted)}
.tm-c.c-score{color:var(--red);font-weight:700}
.tm-c.c-seen{font-size:11.5px}

/* name + tag as one unit, so ellipsis on the name never eats the tag.
   Flex on the inner span, not on the <td>. */
.name-cell{max-width:none}
.nc-in{display:flex;align-items:center;gap:6px;min-width:0;
  font-weight:500;font-size:14px}
.nc-in .player{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.nc-in .player{min-width:0}
.rn-wrap{display:flex;align-items:center;gap:5px;min-width:0}

/* Every player name on the site is a link to their Steam profile. Color
   comes from the `a{color:inherit}` reset up top, so this only adds the
   hover affordance a static name never needed. */
.name-link:hover{text-decoration:underline}

/* ==========================================================================
   RESPONSIVE - the two split layouts collapse before the panel does
   ========================================================================== */
@media (max-width:1100px){
  .home-grid{grid-template-columns:minmax(0,1fr) 290px}
  .team-split{grid-template-columns:290px minmax(0,1fr)}
  .tm-row{grid-template-columns:minmax(0,1.5fr) 84px 74px 66px 66px 70px 76px}
  .tm-row > :last-child{display:none}   /* drop "Last seen" first */
  .td-lists{grid-template-columns:minmax(0,1fr);gap:18px}
}
@media (max-width:900px){
  /* A step down from the desktop wordmark, not a different treatment - the
     bar is the same shape, just tightened up for a narrow screen. */
  :root{--topbar-brand-fs:46px;--topbar-brand-h:66px}
  /* Below this the page stops being viewport-locked and scrolls normally -
     a phone cannot usefully show a rail, a roster and a board at once. */
  .stage{height:auto;min-height:calc(100vh - var(--topbar-h) - var(--footer-h))}
  .frame,.frame-in{height:auto}
  .frame-body{height:auto}
  .home-grid,.team-split{grid-template-columns:minmax(0,1fr)}
  /* The topbar no longer sticks on mobile - it scrolls away with the page
     like any other content, so it can never overlap the sidebar takeover and
     the two no longer have to coordinate z-index/stacking-context tricks. */
  .topbar{position:static}
  /* The sidebar leaves the document flow entirely and becomes a full-screen
     takeover, opened by the floating burger button and closed by the same
     button (now morphed into an X), the scrim, an Escape press, or picking
     a different section. With the topbar no longer sticky there is nothing
     fixed above it to sit below, so it simply covers the full viewport.
     Sliding in from the right (translateX) keeps the "comes in from the
     right" motion the earlier partial-width drawer had. */
  .home-main{width:100%}
  .rail-toggle{display:flex}
  .rail-backdrop{display:block;top:0}
  .home-rail{position:fixed;top:0;right:0;bottom:0;z-index:68;
    /* out of flow now, so the desktop hug-your-content pair goes back off */
    align-self:auto;max-height:none;
    width:100%;overflow-y:auto;overflow-x:hidden;
    background:var(--panel);border:0;border-radius:0;box-shadow:none;
    transform:translateX(100%);transition:transform .3s cubic-bezier(.22,.61,.36,1)}
  .home-rail.open{transform:translateX(0)}
  /* Stacked single-column panels need real height of their own, not a share
     of the fixed 3-across row height - that squeezed each one down to ~50px. */
  .show-row{grid-template-columns:minmax(0,1fr);height:auto;gap:10px}
  .show-panel{padding:18px 18px}
  .table-wrap.board,.table-wrap.bans{max-height:60vh}
  .team-list{max-height:280px}
  .tm-row{grid-template-columns:minmax(0,1fr) 78px 72px;gap:8px}
  .tm-row > :nth-child(n+4){display:none}
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width:980px){
  .team-rest{grid-template-columns:repeat(2,1fr)}
  .foot-cols{grid-template-columns:1fr 1fr;gap:26px}
  .frame-body{padding:20px 22px 24px}
}
@media (max-width:640px){
  /* fewer rows fit on a phone, so the matched panel height comes down too */
  /* No --topbar-brand-* here on purpose - the 4x wordmark set in the 900px
     block above carries through to phones unchanged. */
  :root{--parallax:100px;--conn-col:74px;--ink-w:4px;--topbar-nav-h:56px}
  .frame-body{padding:16px 14px 20px}
  .topbar-in{padding:0 14px}
  .rail-live{padding:10px 14px 11px}
  /* No font-size override - the headline line is the rail's title on phones
     too, and its clamp already bottoms out at a size that fits the drawer. */
  .nav-btn{padding:8px 12px;font-size:12.5px}
  /* Map and the progress bar drop out - the bar duplicates the players
     figure right next to it - so the name column, the one thing worth
     reading at a glance, gets the room both of them used to take. */
  .srv-cols{grid-template-columns:40px minmax(0,1fr) 50px}
  .srv-cols .mp,.srv-head .c-map,.srv-cols .bar,.srv-head .srv-cols>:nth-child(5){display:none}
  .btn-connect{padding:6px 8px;font-size:11px}
  .pl-cols{grid-template-columns:20px minmax(0,1fr) 68px 56px 72px}
  .pl-cols > :nth-child(n+6){display:none}
  /* Phone: status, player, reason, length. Date issued, expires-in and
     on-record are dropped - all three are still on every row in the desktop
     view. They're zeroed as well as hidden because a display:none column in
     a fixed-layout table still reserves its declared width otherwise, which
     is exactly the dead space that squeezed reason before. */
  /* thead th, not bare th: the desktop widths above are themselves scoped
     that way, so a shorter selector here would lose to them on specificity
     and the phone widths would silently never apply. */
  .table-wrap.bans thead th:nth-child(3),.table-wrap.bans tbody td:nth-child(3),
  .table-wrap.bans thead th:nth-child(n+6),.table-wrap.bans tbody td:nth-child(n+6){
    display:none;width:0;padding:0}
  /* Reason is the column that actually needs the room here, so it's the only
     one left unsized and takes everything the other three don't. Status and
     length are pinned as tight as their content allows, and player is capped
     by percentage so a long name can't crowd the reason out. */
  .table-wrap.bans thead th:nth-child(1){width:74px}
  .table-wrap.bans thead th:nth-child(2){width:27%}
  .table-wrap.bans thead th:nth-child(5){width:52px}
  /* Horizontal padding drops from 7px to 3px on this table only - at four
     columns in a phone's width, the gutters were costing more room than the
     text they were separating. */
  .table-wrap.bans thead th,.table-wrap.bans tbody td{padding:8px 3px}
  .table-wrap.bans td.reason{white-space:normal;line-height:1.35;
    overflow-wrap:anywhere}
  .status-pill{font-size:9px;padding:2px 6px;letter-spacing:.02em}

  /* Boards on a phone: rank (with the shift chip riding along on the global
     tab), player and the headline score. Everything else lives in the row's
     breakdown, which is one tap away and reflows to a few tiles per line.
     Back to FIXED here, not auto: auto-layout sizes a column off its content's
     min-content width, and a long nowrap username has no valid break point,
     so its min-content IS its full text width - the column (and the whole
     table) grew to fit it, shoving the score column off-screen, no matter
     what overflow/ellipsis rules sat on the name span. Fixed layout makes
     column width independent of content, so overflow:hidden actually gets a
     chance to clip. The hidden columns are zeroed out explicitly (not just
     display:none) so they don't go on reserving a share of the row's width -
     that reservation was what caused the original dead-space problem this
     block used to work around with `auto`. */
  .table-wrap.board table{table-layout:fixed}
  .table-wrap.board th:nth-child(n+4),
  .table-wrap.board td:nth-child(n+4){display:none;width:0;padding:0}
  /* The score column gets a guaranteed width of its own rather than sharing
     "auto" with the name column - otherwise the two would just split
     whatever's left 50/50, squeezing a 7-digit score as tight as the name. */
  .table-wrap.board th:nth-child(3),
  .table-wrap.board td:nth-child(3){width:82px}
  .table-wrap.board .name-cell{width:auto}
  table{font-size:12px}
  thead th,tbody td{padding:8px 7px}
  td.pos{width:60px;gap:4px}
  .dp-tiles{grid-template-columns:repeat(auto-fit,minmax(78px,1fr))}
  .dp-group{padding:10px 10px 2px}
  .team-rest{grid-template-columns:1fr}
  .team-tile.champ .tt-stats{gap:18px}
  .foot-cols{grid-template-columns:1fr;gap:22px}
}
