/* ==========================================================================
   Gravijet — statistics
   Design language: Minecraft's own GUI.

   Grounded in a specific screen, not "gaming vibes": the multiplayer server
   list and the Statistics screen. That is exactly what this site is — a long
   list of entries and numbers over the pause-menu dirt — so the game already
   solved it, and the rules are lifted rather than invented:

     - Dirt background, tiled and darkened, as the pause menu draws it.
     - Content sits in ENTRIES: near-black translucent rows with a hard 2px
       black edge and a faint top-inner highlight. Hover outlines them white,
       the way the game highlights the selected server. Nothing floats.
     - Real widgets (buttons, the status window) use the GUI grammar: raised
       bevels — light top-left, dark bottom-right, 2px, never blurred. Item
       slots are the inverse (dark top-left) and hold only icons, never text,
       because that is the one thing the game never does.
     - The palette is the game's 16 chat colours (§0-§f) plus the GUI greys.
       Rank colours already arrive in that palette, so the site and the server
       speak the same one. Gold marks the leader; green wins, red loses.
     - Text carries Minecraft's hard drop shadow at 25% of its own colour —
       the single most recognisable thing about the game's typography. Light
       text on dark ground only; the dark labels inside a grey panel have none.
     - The type scale is quantised to the font's pixel grid (8/16/24/32/48).
       A pixel font at 17px is mush: the game scales in whole pixels, so do we.
     - image-rendering: pixelated. Nothing is ever smoothed.
     - The chrome belongs to the picture too: the pointer and the scrollbar are
       drawn on the same 2px grid, so nothing on screen is stock browser.

   Sound follows the same principle one floor down — see sound.js. The game runs
   its whole interface on one click and repitches it; so do we.
   ========================================================================== */

:root {
  /* --- GUI greys, from the inventory window --- */
  --panel: #c6c6c6;
  --panel-hi: #ffffff;
  --panel-lo: #555555;
  --slot: #8b8b8b;
  --slot-hi: #ffffff;
  --slot-lo: #373737;
  --outline: #000000;

  /* --- the button, and the three text colours the game gives it --- */
  --btn: #6c6c6c;
  --btn-hover: #7b7b88;  /* the hovered texture: lighter, faintly blue */
  --btn-off: #4c4c4c;
  --btn-fg: #ffffff;
  --btn-fg-hi: #ffffa0;  /* Button.getFGColor() while hovered — NOT chat yellow */
  --btn-fg-off: #a0a0a0; /* ...and while disabled */

  --label: #404040;      /* the colour of the word "Inventory" */
  --label-dim: #6b6b6b;

  /* --- the entry surface: the server-list row ---
     Opaque enough that the dirt stops competing with the text. At 0.62 every
     letter had a noisy brown texture showing through it, which is most of why
     the page read as hard to follow. */
  --entry: rgba(0, 0, 0, 0.84);
  --entry-hi: rgba(0, 0, 0, 0.93);

  /* --- the 16 chat colours (§0-§f) --- */
  --mc-dark-blue: #0000aa;
  --mc-dark-green: #00aa00;
  --mc-dark-aqua: #00aaaa;
  --mc-dark-red: #aa0000;
  --mc-dark-purple: #aa00aa;
  --mc-gold: #ffaa00;
  --mc-gray: #aaaaaa;
  --mc-dark-gray: #555555;
  --mc-blue: #5555ff;
  --mc-green: #55ff55;
  --mc-aqua: #55ffff;
  --mc-red: #ff5555;
  --mc-light-purple: #ff55ff;
  --mc-yellow: #ffff55;
  --mc-white: #ffffff;

  /* semantic — chosen from that palette, not invented */
  --win: var(--mc-green);
  --loss: var(--mc-red);

  --font: 'Monocraft', ui-monospace, monospace;
  /* A Minecraft screen is a narrow window centred on the dirt, never a
     full-width dashboard. At 1180 the rows were mostly empty middle — a match
     read "BedFight ... 600px of nothing ... LOSS" — which is what "poured in"
     looks like. Pulling the column in costs no content and gives the dirt back
     its job of framing the window. */
  --container: 1000px;
  /* The HUD strip's own width — see .hud-inner. Wider than the menu column
     because the header is a HUD, not a screen. */
  --hud: 1240px;
  --gut: 16px;
}

/* ------------------------------------------------------------------ reset */
* { box-sizing: border-box; margin: 0; padding: 0; border-radius: 0; }
html { -webkit-text-size-adjust: 100%; }
body {
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  color: var(--mc-white);
  background: #0d0a07 url('../img/dirt.png') repeat;
  background-size: 64px 64px;
  image-rendering: pixelated;
  overflow-x: hidden;
  /* Sticky footer: the column is the page, and #app absorbs the slack, so a
     short page (a 404, an empty ladder) still puts the footer on the floor
     instead of halfway up the screen. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
#app { flex: 1 0 auto; }
.footer { flex-shrink: 0; }
img { display: block; max-width: 100%; image-rendering: pixelated; }
a { color: inherit; text-decoration: none; }
button { font: inherit; border: none; background: none; color: inherit; }
/* Every bit of text on this site carries a hard pixel shadow. Left on through a
   selection it smears the glyphs across the highlight and the words stop being
   readable at exactly the moment somebody is trying to copy them — so the shadow
   comes off, and the highlight is a flat block with real contrast under it. */
::selection { background: var(--mc-green); color: #000; text-shadow: none; }
::-moz-selection { background: var(--mc-green); color: #000; text-shadow: none; }
a:focus-visible, button:focus-visible, input:focus-visible { outline: 2px solid var(--mc-white); outline-offset: 2px; }

/* ----------------------------------------------------------------- cursors */
/* The crosshair, everywhere — it's what you're looking at while you play, so
   it's what the mouse is here. Nothing changes on hover: the game doesn't swap
   pointers to tell you a thing is interactive, it lights the thing up, and this
   design already does that (white outline on buttons, white edge on rows).
   Minecraft draws the crosshair by inverting what's behind it, which CSS can't
   do to a cursor; the black outline does that job the only way the web allows.
   The keyword after each url() is the fallback if a browser rejects the image. */
/* Links and buttons are named explicitly rather than left to inherit from body:
   the browser's own sheet says a:link { cursor: pointer }, and an inherited
   value loses to any rule that actually matches — including the browser's. */
body, a[href], button, summary, [role='button'] { cursor: url('../img/crosshair.png') 16 16, crosshair; }
/* The one exception. A crosshair is for aiming at a thing; typing means aiming
   between two letters, and that's what an I-beam is for. */
input, textarea { cursor: url('../img/cursor-text.png') 16 16, text; }

/* -------------------------------------------------------------- scrollbar */
/* The game does have this widget, so it's lifted rather than invented: a black
   trough and a raised grey thumb — a #8b8b8b body lit #c6c6c6 from the top-left.
   Same grammar as .raised, so it belongs to the buttons. Chromium needs the
   pseudo-elements; Firefox only takes the two colours and draws its own shape,
   which is why the bevel is stated twice and can't be shared. */
html { scrollbar-color: #8b8b8b #000; }  /* inherits to every inner scroller */
::-webkit-scrollbar { width: 14px; height: 14px; }
::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: #000; }
::-webkit-scrollbar-thumb {
  background: #8b8b8b;
  box-shadow: inset 2px 2px 0 #c6c6c6, inset -2px -2px 0 #555555;
}
::-webkit-scrollbar-thumb:hover { background: #9b9b9b; }

/* ------------------------------------------------- the three core widgets */
/* ENTRY — the server-list row. Where all text and data live. */
.entry {
  background: var(--entry);
  border: 2px solid var(--outline);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, 0.09);
}
a.entry:hover, .entry.hoverable:hover { background: var(--entry-hi); border-color: var(--mc-white); }

/* RAISED — real GUI widgets: buttons, the status window. */
.raised {
  background: var(--panel);
  border: 2px solid var(--outline);
  box-shadow: inset 2px 2px 0 var(--panel-hi), inset -2px -2px 0 var(--panel-lo);
  color: var(--label);
}
/* SUNKEN — an item slot. Icons only, never text. */
.slot {
  background: var(--slot);
  box-shadow: inset 2px 2px 0 var(--slot-lo), inset -2px -2px 0 var(--slot-hi);
}

/* --------------------------------------------------------------- typography */
/* Quantised to the pixel grid. */
h1 { font-size: 48px; line-height: 1; }
h2 { font-size: 32px; line-height: 1; }
h3 { font-size: 24px; line-height: 1; }
h4 { font-size: 16px; line-height: 1; }
h1, h2, h3, h4 { font-weight: 700; }

/* Minecraft's drop shadow: 25% of the text's own colour, offset one pixel.
   One class per colour it's used with — the game computes this per glyph. */
.sh      { text-shadow: 2px 2px 0 #3f3f3f; }
.sh-gray { text-shadow: 2px 2px 0 #2a2a2a; }
.sh-gold { text-shadow: 2px 2px 0 #3f2a00; }
.sh-yel  { text-shadow: 2px 2px 0 #3f3f15; }
.sh-grn  { text-shadow: 2px 2px 0 #153f15; }
.sh-red  { text-shadow: 2px 2px 0 #3f1515; }
.sh-pur  { text-shadow: 2px 2px 0 #3f153f; }

/* ------------------------------------------------------------------ layout */
.container { width: 100%; max-width: var(--container); margin: 0 auto; padding: 0 var(--gut); }

/* SEPARATION — by tone, not by a line.
   Air alone didn't work: with nothing marking where one block ended and the next
   began, the gaps read as empty rather than as structure. But a drawn rule was
   worse — a lit 2px band spanning the whole page shouted louder than anything it
   divided, and a bright grey bar belongs to no part of this game. Minecraft
   separates a screen by darkening the region between its header and footer; you
   read the boundary off the change in ground, not off a stripe. So the sections
   alternate, and the edge of each band is the separator. */
.section { padding: 34px 0; }
.section:nth-child(even) { background: rgba(0, 0, 0, 0.45); }

/* A thin divider where a band can't reach — inside a panel, or between filters
   and their table. It sits on flat dark ground rather than on dirt, so it needs
   far less light than the page-wide version to register. `.hr` and not `.rule`:
   the rulebook's own entries are `.rule` (see the rules section far below), and
   a shared name meant this 2px height was crushing every written rule into a
   sliver with its text spilling out — the "rules overlap, unreadable" bug. */
.hr { height: 2px; background: rgba(255, 255, 255, 0.13); }
.section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 20px; flex-wrap: wrap; }
.section-head h2 { color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.section-head p { color: var(--mc-gray); font-size: 16px; max-width: 44ch; text-shadow: 2px 2px 0 #2a2a2a; }

.eyebrow { display: inline-block; font-size: 16px; color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; margin-bottom: 6px; }

/* --------------------------------------------------------------------- nav */
/* The in-game HUD strip. */
/* Opaque, not 82%: content scrolled underneath and read straight through the
   bar. A sticky header has to be a lid, not a tint. */
.nav { position: sticky; top: 0; z-index: 50; background: #0a0a0a; border-bottom: 2px solid var(--outline); box-shadow: 0 2px 0 rgba(255, 255, 255, 0.1); }

/* The HUD is not the menu window, and it was being made to live inside it.
   --container is 1000px because a Minecraft *screen* is a narrow column over the
   dirt — but the game's HUD has never obeyed that: the hotbar and the effect
   icons span the whole display no matter how narrow the pause menu is. Forcing
   the header into 968px of usable width meant the brand, six destinations, the
   player count, a sign-in button and a 200px search field were fighting over
   about 40px less than they needed, and the row simply ran out of room.
   So the strip gets its own width. Content stays in the column. */
.hud-inner {
  width: 100%; max-width: var(--hud); margin: 0 auto; padding: 0 var(--gut);
  display: flex; align-items: center; gap: 16px; height: 56px;
}
/* Never shrink: the nav is a flex row, and letting the brand give way clips it
   to "GRAVIJE". The search field is the one that should yield. */
.brand { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.brand img { width: 32px; height: 32px; image-rendering: auto; } /* the logo isn't pixel art */
.brand .word { font-size: 16px; font-weight: 700; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; white-space: nowrap; }
.brand .word em { font-style: normal; color: var(--mc-aqua); }

/* min-width:0 lets this shrink below its contents, which is what stops it
   shoving the account off the end of the row — but the links inside are
   `nowrap`, so shrinking alone made them spill straight out of the box and run
   underneath everything to the right. That overlap was the bug: signed out
   there was room and it never showed, signed in the avatar, the name, two doors
   and a close button took the slack away and the destinations printed on top of
   them. Contain the overflow and let it scroll instead — nothing is lost, and
   nothing can ever be drawn over anything else again. */
.nav-links {
  display: flex; gap: 2px; min-width: 0;
  overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
}
.nav-links::-webkit-scrollbar { display: none; }
/* The gutter is reserved on every link, not added to the active one.
   `content: '> '` on :active-only widened whichever link was selected, so every
   link to its right slid sideways on navigation — the row visibly jolted each
   time you clicked it. The game's own menus don't do that: the selector arrow
   sits in a column that is always there and is only sometimes painted. */
.nav-links a {
  position: relative; font-size: 16px; color: var(--mc-gray);
  padding: 4px 7px 4px 19px; text-shadow: 2px 2px 0 #2a2a2a; white-space: nowrap;
}
.nav-links a::before {
  content: '>'; position: absolute; left: 7px; color: var(--mc-yellow);
  text-shadow: 2px 2px 0 #3f3f15; opacity: 0;
}
.nav-links a:hover { color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; }
.nav-links a:hover::before { opacity: 0.45; }
.nav-links a.active { color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.nav-links a.active::before { opacity: 1; }

.nav-right { margin-left: auto; display: flex; align-items: center; gap: 12px; flex-shrink: 0; min-width: 0; }

/* The navigation tier — the second row under the identity strip. It carries the
   destinations, the search and the live count, so the top row can be only the
   brand and the account. A hairline above it, not a heavy border: it is one
   header in two registers, not two separate bars stacked. */
.subnav-inner {
  width: 100%; max-width: var(--hud); margin: 0 auto; padding: 4px var(--gut) 6px;
  display: flex; align-items: center; gap: 16px; min-height: 42px;
  border-top: 2px solid rgba(255, 255, 255, 0.06);
}
.subnav-inner .nav-links { flex: 1 1 auto; }
.subnav-right { margin-left: auto; display: flex; align-items: center; gap: 12px; flex-shrink: 0; min-width: 0; }
body[data-surface='player'] .subnav-inner,
body[data-surface='staff'] .subnav-inner { border-top-color: rgba(255, 170, 0, 0.14); }
/* An empty tab strip (a signed-out console, tabs not yet painted) should not
   leave a bare rule floating under the mark. */
.subnav-inner:has(.nav-links:empty) { border-top: 0; min-height: 0; padding: 0; }

.live-pill { display: inline-flex; align-items: center; gap: 8px; font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.live-dot { width: 8px; height: 8px; background: var(--mc-dark-gray); }
.live-pill.on { color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.live-pill.on .live-dot { background: var(--mc-green); }

/* --------------------------------------------------- the other two chromes */
/* Same grammar — bevels, hard shadows, the sixteen colours — but three
   different screens of the game, because they are three different places.
   The site is the HUD over the world. The player dashboard is your inventory.
   The console is none of that: it is the debug screen, and it should never be
   mistaken at a glance for the page that sells the server. */

/* --- the player's dashboard --- */
/* Dropped a shade below the public site: the pause menu darkens the world
   behind it, and this is the same gesture — you have stepped out of the game
   to look at your own things. */
body[data-surface='player'] { background-color: #060606; }
body[data-surface='player'] .nav { background: #0e0e0e; }
.brand-back .word { color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.brand-back:hover .word { color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }

/* You, in the header — the one thing this surface is about. */
.you { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.you img { width: 24px; height: 24px; }
.you-id { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.you-name { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; white-space: nowrap; }
.you-rank { font-size: 16px; text-shadow: 2px 2px 0 #2a2a2a; white-space: nowrap; }
a.you-link { color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; }
.you-head { width: 48px; height: 48px; flex-shrink: 0; }

/* --- the staff console --- */
/* No dirt. The pause menu's dirt says "a game is running behind this"; nothing
   is running behind this, and the texture was the single loudest thing telling
   a moderator they were still on the marketing site. Flat ground, and a gold
   rule under the strip instead of a white one — the console borrows the
   colour the game uses for "pay attention to this line". */
body[data-surface='staff'] { background: #070707; }
body[data-surface='staff'] .nav {
  background: #101010;
  border-bottom: 2px solid var(--mc-gold);
  box-shadow: 0 2px 0 rgba(255, 170, 0, 0.16);
}
body[data-surface='staff'] .section:nth-child(even) { background: none; }
body[data-surface='staff'] .footer { background: rgba(0, 0, 0, 0.5); border-top: 2px solid rgba(255, 170, 0, 0.28); }

.console-mark { display: flex; flex-direction: column; line-height: 1.1; flex-shrink: 0; }
.cm-name { font-size: 16px; font-weight: 700; color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00; letter-spacing: 1px; }
.cm-sub { font-size: 16px; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }
body[data-surface='staff'] .nav-links a::before { color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00; }
body[data-surface='staff'] .nav-links a:hover { color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00; }

/* search — a sunken text field */
.search { position: relative; flex-shrink: 1; min-width: 0; }
.search-input {
  width: 200px; max-width: 100%; height: 32px; padding: 0 8px;
  background: #000; border: 2px solid var(--slot-lo);
  color: var(--mc-white); font: 400 16px var(--font); text-shadow: 2px 2px 0 #3f3f3f;
}
.search-input::placeholder { color: #6b6b6b; text-shadow: none; }
.search-input:focus { outline: none; border-color: var(--mc-white); }
.search-results {
  position: absolute; top: 36px; right: 0; width: 300px; z-index: 60;
  background: rgba(0, 0, 0, 0.94); border: 2px solid var(--mc-white); padding: 2px;
}
.search-results:empty { display: none; }
.search-row { display: flex; align-items: center; gap: 8px; padding: 5px 6px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.search-row:hover, .search-row.sel { background: var(--mc-dark-blue); }
.search-row img { width: 24px; height: 24px; }
.search-row .nm { font-size: 16px; }
.search-row .rk { font-size: 16px; margin-left: auto; }

/* ----------------------------------------------------------------- buttons */
/* The game's button as the texture actually is: a 2px black outline, a light
   top-left bevel, a body that darkens toward the bottom, a dark bottom-right.
   What makes it unmistakably Minecraft's is the pair of states the game spends
   its own code on — Button.getFGColor() returns 0xffffa0 while hovered and
   0xa0a0a0 while disabled — together with the white outline its hovered texture
   carries. Chat yellow (#ffff55) is a different colour doing a different job;
   it marks selection here, and only hover gets #ffffa0. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-size: 16px; padding: 9px 16px; min-height: 40px;
  border: 2px solid var(--outline);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.10) 0%, rgba(255, 255, 255, 0) 48%, rgba(0, 0, 0, 0.16) 100%) var(--btn);
  box-shadow: inset 2px 2px 0 rgba(255, 255, 255, 0.42), inset -2px -2px 0 rgba(0, 0, 0, 0.45);
  color: var(--btn-fg); text-shadow: 2px 2px 0 #3f3f3f;
}
.btn:hover {
  background-color: var(--btn-hover);
  border-color: var(--mc-white);
  color: var(--btn-fg-hi); text-shadow: 2px 2px 0 #3f3f28;
}
/* Pressed: the bevel flips, so the lit edge moves to the bottom-right and the
   body's gradient turns over with it. */
.btn:active {
  box-shadow: inset -2px -2px 0 rgba(255, 255, 255, 0.30), inset 2px 2px 0 rgba(0, 0, 0, 0.50);
  background-image: linear-gradient(180deg, rgba(0, 0, 0, 0.16) 0%, rgba(255, 255, 255, 0) 52%, rgba(255, 255, 255, 0.08) 100%);
}
.btn-primary { background-color: #3c8527; } /* the green "Play" button */
.btn-primary:hover { background-color: #4a9c30; }

/* A small square GUI button — currently just the sound toggle. */
/* -------------------------------------------------------------------- hero */
.hero { padding: 32px 0 24px; }
.hero-grid { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: 24px; align-items: start; }
.hero-copy h1 { color: var(--mc-white); text-shadow: 3px 3px 0 #3f3f3f; }
.hero-copy h1 .acid { color: var(--mc-yellow); text-shadow: 3px 3px 0 #3f3f15; display: block; }
.hero-copy .lede { color: var(--mc-gray); font-size: 16px; max-width: 44ch; margin: 16px 0 22px; text-shadow: 2px 2px 0 #2a2a2a; }
.hero-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.hero-tags { display: flex; gap: 16px; margin-top: 20px; flex-wrap: wrap; }
.chip { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.chip b { color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; font-weight: 400; }

/* The status window — a real GUI panel, the one place the grey shows. */
.readout { padding: 8px; }
.readout-bar { padding: 2px 4px 8px; font-size: 16px; color: var(--label); font-weight: 700; display: flex; justify-content: space-between; gap: 8px; }
.readout-body { }
/* Inside the grey panel the game uses dark text with NO shadow — the shadow is
   for light text on the world behind it. White-on-#c6c6c6 would be the exact
   mistake this design language exists to avoid. */
.readout-big { display: flex; align-items: center; gap: 12px; padding: 14px 12px; }
.readout-big .v { font-size: 48px; font-weight: 700; color: #000; line-height: 1; }
.readout-big .u { font-size: 16px; color: var(--label); line-height: 1.25; }
.readout-rows { margin-top: 8px; }
.rr-row { display: flex; justify-content: space-between; gap: 12px; padding: 6px 4px; font-size: 16px; color: var(--label); }
.rr-row + .rr-row { border-top: 2px solid rgba(0, 0, 0, 0.13); }
.rr-row .v { font-weight: 700; color: #000; }

/* ------------------------------------------------------------ telemetry row */
.telemetry { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.telemetry .cell { padding: 14px 12px; }
.telemetry .cell .v { font-size: 24px; font-weight: 700; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.telemetry .cell .l { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; margin-top: 4px; }

/* --------------------------------------------------------- mode list */
/* The multiplayer server list, which is what this actually is: pick a mode and
   go. Icon, name and blurb left; the numbers right, on their own column so they
   line up down the list instead of drifting with the text beside them. */
.mode-list { display: flex; flex-direction: column; gap: 4px; }
.mode-row { display: flex; align-items: center; gap: 16px; padding: 14px 16px; }
.mode-row .glyph { width: 32px; height: 32px; color: var(--mc-aqua); flex-shrink: 0; }
.mode-row .glyph svg { width: 32px; height: 32px; }
.mode-row .m-id { flex: 1; min-width: 0; }
.mode-row .m-id h3 { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.mode-row .tag { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; margin-top: 3px; }
.mode-row .m-foot { display: flex; gap: 12px; flex-shrink: 0; }
.mode-row .m-foot > div { min-width: 116px; text-align: right; }
.mode-row .m-foot .mv { font-size: 16px; font-weight: 700; color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; }
.mode-row .m-foot .ml { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; margin-top: 3px; }
.mode-row .m-badge { width: 44px; text-align: right; flex-shrink: 0; }
.mode-row.enter:hover h3 { color: var(--btn-fg-hi); text-shadow: 2px 2px 0 #3f3f28; }
.mode-row.soon { opacity: 0.6; }

.badge { display: inline-block; font-size: 16px; color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; }
.badge.soon { color: var(--mc-light-purple); text-shadow: 2px 2px 0 #3f153f; }
.badge.live { color: var(--mc-green); text-shadow: 2px 2px 0 #153f15; }

/* ------------------------------------------------------------- leaderboard */
/* Two ranks of control, and they have to look it. The mode tabs are the raised
   button: they change which ladder you're on. The metric chips below only
   re-sort the one you're already on, so they're a flat inline toggle. Both were
   .btn before — two rows of identical grey widgets stacked on each other, with
   nothing saying they did different jobs. */
/* The controls sit on their own darker ground, the way the game floors its
   header and footer apart from the content between them. That band is what
   separates the filters from the table — the table needs no rule above it once
   the thing above it is visibly a different surface. */
.lb-controls {
  background: rgba(0, 0, 0, 0.45);
  border: 2px solid rgba(0, 0, 0, 0.6);
  padding: 12px;
  margin-bottom: 18px;
}
.lb-tabs { display: flex; gap: 6px; flex-wrap: wrap; }
#lb-filters:not(:empty) { margin-top: 12px; padding-top: 12px; border-top: 2px solid rgba(255, 255, 255, 0.1); }

/* Ranking-by and filtering-to are two different questions, so each gets its own
   labelled row. As one flat run of identical chips they read as a single
   choice, and picking a kit looked like picking a stat. */
.lb-row { display: flex; align-items: flex-start; gap: 12px; }
.lb-row + .lb-row { margin-top: 10px; padding-top: 10px; border-top: 2px solid rgba(255, 255, 255, 0.08); }
.lb-row-label {
  flex: 0 0 auto;
  width: 40px;
  padding-top: 6px;
  font-size: 16px;
  color: var(--mc-dark-gray);
  text-shadow: 2px 2px 0 #1a1a1a;
}
.lb-row .chips { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; min-width: 0; }
.lb-tab { font-size: 16px; padding: 9px 16px; min-height: 40px; display: inline-flex; align-items: center; }
/* Disabled the way the game disables: not faded out, but a flatter, darker
   widget with grey text. Opacity would have dimmed the outline too, and the
   outline is what tells you it's still a button. */
.btn:disabled, .lb-tab.dead, .lb-tab.dead:hover {
  background-color: var(--btn-off);
  background-image: none;
  border-color: var(--outline);
  box-shadow: inset 2px 2px 0 rgba(255, 255, 255, 0.14), inset -2px -2px 0 rgba(0, 0, 0, 0.3);
  color: var(--btn-fg-off); text-shadow: 2px 2px 0 #282828;
}
/* selected = pressed in, like a toggled game option */
.lb-tab.active {
  background: var(--mc-dark-gray);
  box-shadow: inset -2px -2px 0 rgba(255, 255, 255, 0.28), inset 2px 2px 0 rgba(0, 0, 0, 0.5);
  color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15;
}
/* The secondary rank: sunken, not raised. Deliberately quieter than a button. */
.metric-chip {
  font-size: 16px; padding: 5px 10px; min-height: 30px;
  display: inline-flex; align-items: center;
  background: rgba(0, 0, 0, 0.55); border: 2px solid rgba(255, 255, 255, 0.11);
  color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a;
}
.metric-chip:hover { border-color: var(--mc-white); color: var(--btn-fg-hi); text-shadow: 2px 2px 0 #3f3f28; }
.metric-chip.active {
  background: var(--mc-dark-gray); border-color: var(--mc-white);
  color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15;
}

/* The board: a list of server-list entries. */
.board { display: flex; flex-direction: column; gap: 4px; }
.board-head, .board-row { display: grid; align-items: center; gap: 8px; }
.board-head { padding: 0 10px 4px; }
.board-head span { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.board-head span.r { text-align: right; }
.board-row { padding: 6px 10px; min-height: 48px; }
.rank-badge { font-size: 16px; font-weight: 700; color: var(--mc-gray); text-align: center; text-shadow: 2px 2px 0 #2a2a2a; }
/* only the leader is called out — in the game's gold */
.board-row.top1 .rank-badge { color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00; }

.player-cell { display: flex; align-items: center; gap: 8px; min-width: 0; }
.player-cell img { width: 32px; height: 32px; flex-shrink: 0; }
.player-cell .pc-name { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.player-cell .pc-rank { font-size: 16px; text-shadow: 2px 2px 0 #2a2a2a; }
.board-row .val { text-align: right; font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.board-row .val.primary { font-weight: 700; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.online-dot { width: 8px; height: 8px; background: var(--mc-green); flex-shrink: 0; display: inline-block; }

/* ------------------------------------------------------------- player page */
.crumb { display: inline-flex; align-items: center; gap: 6px; font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; margin: 14px 0; }
.crumb::before { content: '< '; color: var(--mc-yellow); }
.crumb:hover { color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; }

.profile-hero { display: grid; grid-template-columns: 168px 1fr; gap: 8px; }
/* the skin sits in an item slot — the one place a slot is correct: it holds a picture */
.skin-wrap { display: grid; place-items: center; padding: 12px; }
.skin-wrap img { height: 196px; width: auto; }
.p-id { padding: 16px 18px; display: flex; flex-direction: column; justify-content: center; }
.p-id .p-rank { font-size: 16px; text-shadow: 2px 2px 0 #2a2a2a; }
.p-id h1 { font-size: 32px; color: var(--mc-white); text-shadow: 3px 3px 0 #3f3f3f; margin: 6px 0 10px; }
.p-status { display: inline-flex; align-items: center; gap: 8px; font-size: 16px; width: fit-content; }
.p-status.online { color: var(--mc-green); text-shadow: 2px 2px 0 #153f15; }
.p-status.offline { color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.p-meta { display: flex; gap: 20px; flex-wrap: wrap; margin-top: 14px; }
.p-meta .pm .k { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.p-meta .pm .v { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }

.p-summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 8px; }
.sum-tile { padding: 14px 16px; }
.sum-tile .v { font-size: 32px; font-weight: 700; color: var(--mc-white); text-shadow: 3px 3px 0 #3f3f3f; }
.sum-tile .l { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; margin-top: 4px; }

/* Each mode is a container window with a title bar. */
.panel { margin-top: 12px; }
.panel-head { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-bottom: 2px solid var(--outline); background: rgba(0, 0, 0, 0.4); }
.panel-head .glyph { width: 24px; height: 24px; color: var(--mc-aqua); }
.panel-head .glyph svg { width: 24px; height: 24px; }
.panel-head h3 { font-size: 24px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.panel-head .ph-sub { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; margin-top: 3px; }
.panel-head .ph-right { margin-left: auto; }
.panel-body { padding: 16px 18px 18px; }

/* The Statistics screen is a LIST — label left, value right, one per line — and
   tiles were the mistake. A fixed grid of backgrounded tiles can't help leaving
   holes: Practice's 9 values orphaned a tenth cell, FFA's 4 left a gap, and an empty
   cell with a background on it reads as a bug rather than as nothing. A list has
   no cells to leave empty. Three columns keeps each label near its own number;
   at two the value drifts half a panel away from what it's naming. */
.stat-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 28px; }
.stat {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 7px 2px; border-bottom: 2px solid rgba(255, 255, 255, 0.07);
}
.stat .l { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
/* Hierarchy here is weight and colour, not size — the game's own list does the
   same, and a pixel font can't be scaled between 16 and 24 without going mushy. */
.stat .v { font-size: 16px; font-weight: 700; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; white-space: nowrap; }
.stat .v.win { color: var(--mc-green); text-shadow: 2px 2px 0 #153f15; }
.stat .v.loss { color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515; }

/* Each block inside a panel gets the separator too — Win/loss, Kits and Recent
   matches are different things and ran straight into one another. */
.block { padding: 18px 0 0; margin-top: 18px; border-top: 2px solid rgba(255, 255, 255, 0.13); }
.block-label { font-size: 16px; color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; margin-bottom: 10px; }

/* The win/loss bar is the XP bar: a sunken trough with a hard fill. */
.wl-bar { height: 16px; display: flex; background: var(--mc-dark-red); box-shadow: inset 2px 2px 0 rgba(0,0,0,0.55); }
.wl-bar .w { background: var(--mc-green); }
.wl-legend { display: flex; justify-content: space-between; font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; margin-top: 6px; }

/* kits */
/* 220, not 172: at 172 the cards came out 188px wide and "FireballFight" plus
   its elo needs 181px of the 168px inside one, so the longest kit names ellipsed
   away — and a truncated kit name is not a kit name. */
.kit-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 4px; }
.kit-card { padding: 10px; background: rgba(0, 0, 0, 0.32); }
.kit-card:hover { background: rgba(0, 0, 0, 0.5); }
.kit-card .kc-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
/* "FireballFight" plus its elo overran the card and collided with the next one
   ("FireballFight 1.00NoDebuff"). A flex item won't shrink below its content
   unless it's told it may. */
.kit-card .kc-name { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kit-card .kc-elo { font-size: 16px; color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; flex-shrink: 0; }
.kit-card .kc-stats { display: flex; gap: 10px; margin-top: 8px; }
.kit-card .kc-stats .ks { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.kit-card .kc-stats .ks b { color: var(--mc-white); font-weight: 400; text-shadow: 2px 2px 0 #3f3f3f; }
.kit-card .kc-bar { height: 8px; margin-top: 9px; background: var(--mc-dark-red); box-shadow: inset 1px 1px 0 rgba(0,0,0,0.55); }
.kit-card .kc-bar .w { height: 100%; background: var(--mc-green); }

/* matches — chat-log rows */
.matches { display: flex; flex-direction: column; gap: 3px; }
.match { display: grid; grid-template-columns: 8px 1fr auto; align-items: center; gap: 10px; padding: 8px; background: rgba(0, 0, 0, 0.32); }
.match .res { width: 8px; height: 30px; }
.match .res.w { background: var(--mc-green); }
.match .res.l { background: var(--mc-red); }
.match .m-info .mk { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.match .m-info .mo { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.match .m-info .mo a { color: var(--mc-aqua); }
.match .m-info .mo a:hover { color: var(--mc-yellow); }
.match .m-right { text-align: right; }
.match .m-right .rr { font-size: 16px; }
.match .m-right .rr.w { color: var(--mc-green); text-shadow: 2px 2px 0 #153f15; }
.match .m-right .rr.l { color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515; }
.match .m-right .rd { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }

/* empty / soon */
.empty { padding: 18px 8px; color: var(--mc-gray); font-size: 16px; text-shadow: 2px 2px 0 #2a2a2a; }
.soon-state { display: flex; align-items: center; gap: 12px; padding: 16px 12px; }
.soon-state .glyph { width: 32px; height: 32px; color: var(--mc-light-purple); flex-shrink: 0; }
.soon-state .glyph svg { width: 32px; height: 32px; }
.soon-state h4 { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.soon-state p { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; margin-top: 4px; }

/* directory */
.dir-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(236px, 1fr)); gap: 6px; }
.dir-card { display: flex; align-items: center; gap: 10px; padding: 8px 10px; }
.dir-card img { width: 32px; height: 32px; flex-shrink: 0; }
.dir-card .dn { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; display: flex; align-items: center; gap: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dir-card .dr { font-size: 16px; text-shadow: 2px 2px 0 #2a2a2a; }
.dir-card .dt { margin-left: auto; text-align: right; font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; line-height: 1.3; flex-shrink: 0; }

/* ------------------------------------------------------------------ footer */
.footer { margin-top: 28px; padding: 22px 0 36px; background: rgba(0, 0, 0, 0.62); border-top: 2px solid var(--outline); }
.footer-inner { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; align-items: center; }
.footer .f-copy { color: var(--mc-gray); font-size: 16px; text-shadow: 2px 2px 0 #2a2a2a; margin-top: 6px; }
.footer .f-links { display: flex; gap: 16px; }
.footer .f-links a { color: var(--mc-gray); font-size: 16px; text-shadow: 2px 2px 0 #2a2a2a; }
.footer .f-links a:hover { color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; }

/* ------------------------------------------------------------------- rules */
.rule-text {
  color: var(--mc-gray); font-size: 16px; line-height: 1.55;
  text-shadow: 2px 2px 0 #2a2a2a; max-width: 68ch; margin: 0;
}
/* A ladder reads like the Statistics screen: which offence, what happens, how
   long. Colour carries the severity — a ban is not a mute in a different font. */
.ladder { display: flex; flex-direction: column; }
.rung {
  display: grid; grid-template-columns: 46px 1fr auto; gap: 12px; align-items: baseline;
  padding: 7px 2px; border-bottom: 2px solid rgba(255, 255, 255, 0.07);
}
.rung:last-child { border-bottom: 0; }
.rung .rn { color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }
.rung .rt { color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00; }
.rung .rt.hard { color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515; }
.rung .rd { color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
/* A rung with no duration configured is not a punishment. Grey it rather than
   let it sit in the same white as the ones that do something. */
.rung .rd.unset { color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }

/* The rulebook is now long enough that landing at the top of it and scrolling
   is a bad way to answer "can I use a minimap". Six section links, the width of
   the column, in the order the sections come — the game's own tab strip. */
.rule-toc { display: flex; flex-wrap: wrap; gap: 2px; margin-bottom: 16px; }
.rule-toc a {
  font-size: 16px; padding: 5px 9px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a;
  border: 2px solid var(--slot-lo); background: #0d0d0d;
}
.rule-toc a:hover { color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; border-color: var(--mc-white); }
.rule-sec { scroll-margin-top: 112px; } /* the two-tier header is sticky and would eat the heading */
.rule-intro {
  color: var(--mc-gold); font-size: 16px; line-height: 1.5; text-shadow: 2px 2px 0 #3f2a00;
  max-width: 68ch; margin: 0 0 4px;
}
.rule { padding: 12px 0; border-bottom: 2px solid rgba(255, 255, 255, 0.07); scroll-margin-top: 112px; }
.rule:last-child { border-bottom: 0; padding-bottom: 0; }
.rule-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 5px; }
.rule-head h4 { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
/* Says "this one has a ladder behind it" without printing the ladder twice. */
.rule-tag {
  font-size: 16px; color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00;
  border: 2px solid rgba(255, 170, 0, 0.35); padding: 0 5px; line-height: 1.3;
}
/* The part that answers "yes but what about…". Dimmer than the rule itself:
   it is the footnote, and it must not read as another rule. */
.rule-detail {
  color: var(--mc-dark-gray); font-size: 16px; line-height: 1.5;
  text-shadow: 2px 2px 0 #1a1a1a; max-width: 68ch; margin: 6px 0 0;
}
.rule .block { margin-top: 10px; }
.rule-stamp { color: var(--mc-dark-gray); font-size: 16px; text-shadow: 2px 2px 0 #1a1a1a; margin-top: 14px; }

/* ------------------------------------------------------------------- forms */
.container.narrow { max-width: 760px; }
.frow { margin-bottom: 14px; }
.flabel {
  display: block; margin-bottom: 5px; font-size: 16px; line-height: 1.4;
  color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a;
}
/* A sunken slot, like every text field in the game. */
.fld {
  width: 100%; font: inherit; font-size: 16px; padding: 8px 10px;
  background: rgba(0, 0, 0, 0.75); color: var(--mc-white);
  border: 2px solid rgba(0, 0, 0, 0.6);
  box-shadow: inset 2px 2px 0 rgba(0, 0, 0, 0.5), inset -2px -2px 0 rgba(255, 255, 255, 0.12);
  text-shadow: 2px 2px 0 #3f3f3f;
}
.fld:focus { outline: none; border-color: var(--mc-white); }
textarea.fld { resize: vertical; min-height: 84px; line-height: 1.5; }
select.fld { cursor: url('../img/crosshair.png') 16 16, crosshair; }
.factions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 14px; }
.fmsg { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.fmsg.bad { color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515; }
.fmsg.ok { color: var(--mc-green); text-shadow: 2px 2px 0 #153f15; }

/* Question and answer, as read by whoever has to judge it. */
.qa-row { padding: 8px 0; border-bottom: 2px solid rgba(255, 255, 255, 0.07); }
.qa-row:last-child { border-bottom: 0; }
.qa-q { font-size: 16px; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; margin-bottom: 3px; }
.qa-a { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; line-height: 1.5; white-space: pre-wrap; }

/* --------------------------------------------------------------- account */
/* One avatar, and everything about you behind it. See shell.js accountMenu:
   the five loose pieces this replaces are what made the strip overlap itself.
   Fixed 24px avatar and a caret — this is now the one part of the header whose
   width does not depend on how long somebody's Discord name is. */
.acct { position: relative; flex-shrink: 0; }
.acct-btn {
  display: flex; align-items: center; gap: 4px; height: 32px; padding: 0 4px;
  background: none; border: 2px solid transparent; cursor: pointer;
}
.acct-btn:hover, .acct-btn[aria-expanded='true'] { border-color: var(--slot-lo); background: #000; }
.acct-btn img { width: 24px; height: 24px; image-rendering: auto; border-radius: 50%; }
.am-blank { width: 24px; height: 24px; background: var(--slot-lo); display: block; }
.acct-caret { font-size: 16px; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; line-height: 1; }
.acct-btn:hover .acct-caret, .acct-btn[aria-expanded='true'] .acct-caret { color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }

.acct-menu {
  position: absolute; top: 38px; right: 0; z-index: 60; min-width: 176px;
  background: rgba(0, 0, 0, 0.94); border: 2px solid var(--mc-white); padding: 2px;
  display: flex; flex-direction: column;
}
.acct-menu[hidden] { display: none; }
.am-head {
  display: flex; flex-direction: column; line-height: 1.2;
  padding: 6px 8px; border-bottom: 2px solid rgba(255, 255, 255, 0.12); margin-bottom: 2px;
}
.am-name { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.am-rank { font-size: 16px; color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00; }
.am-rank.am-none { color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }
.am-item {
  font: inherit; font-size: 16px; text-align: left; padding: 5px 8px;
  background: none; border: 0; cursor: pointer;
  color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a;
}
.am-item:hover { background: var(--mc-dark-blue); color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; }
.am-out { color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }
.am-out:hover { background: none; color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515; }

.who-btn { font-size: 16px; padding: 6px 12px; min-height: 32px; display: inline-flex; align-items: center; }
.dcount {
  margin-left: 7px; padding: 0 5px; font-size: 16px;
  background: var(--mc-dark-red); color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f;
}

/* -------------------------------------------------------------------- misc */
/* The loader is the game's loading bar: a sunken trough filling green. */
.loader { padding: 44px 0; }
.loader .ring { height: 16px; background: var(--slot); box-shadow: inset 2px 2px 0 var(--slot-lo), inset -2px -2px 0 var(--slot-hi); position: relative; overflow: hidden; }
.loader .ring::after { content: ''; position: absolute; top: 3px; bottom: 3px; left: 3px; background: var(--mc-green); animation: load 1.2s steps(10) infinite; }
@keyframes load { from { width: 0; } to { width: calc(100% - 6px); } }
.loader::after { content: 'Loading...'; display: block; margin-top: 8px; font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }

.notice { padding: 56px 0; }
.notice h2 { color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; margin-bottom: 8px; }
.notice p { color: var(--mc-gray); font-size: 16px; text-shadow: 2px 2px 0 #2a2a2a; max-width: 52ch; }
.notice a { color: var(--mc-aqua); }
.notice a:hover { color: var(--mc-yellow); }

/* -------------------------------------------------------------- responsive */
@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; }
  .telemetry { grid-template-columns: repeat(2, 1fr); }
  .p-summary { grid-template-columns: repeat(2, 1fr); }
  .stat-row { grid-template-columns: repeat(2, 1fr); }
}
/* The strip gives way in the order the things on it can afford to. Between the
   HUD's full width and a phone there is a band where it all still technically
   fits but only just, which is what "cramped" was — so the pieces that are
   saying the same thing twice go first, long before anything is dropped. */
@media (max-width: 1180px) {
  /* "3 players online" -> "3 online". The unit is obvious from the number. */
  .lp-word { display: none; }
  .search-input { width: 148px; }
}
@media (max-width: 1020px) {
  .nav-links a { padding: 4px 5px 4px 17px; }
  .nav-links a::before { left: 5px; }
}
@media (max-width: 860px) {
  /* Now it genuinely does not fit. Search is the one thing with a keyboard
     shortcut and a home on another page, so it is the one that goes. */
  .search { display: none; }
}

@media (max-width: 620px) {
  /* The links used to be hidden here, which left a phone with no navigation at
     all — survivable with three of them and the footer as a backstop, not with
     six. They drop to their own row instead and scroll sideways if they must. */
  .hud-inner { flex-wrap: wrap; row-gap: 8px; height: auto; padding-top: 8px; padding-bottom: 8px; gap: 10px; }
  .nav-links {
    order: 3; width: 100%; margin-left: 0; gap: 2px;
    overflow-x: auto; scrollbar-width: none;
  }
  .nav-links::-webkit-scrollbar { display: none; }
  .nav-links a { padding: 4px 6px 4px 18px; white-space: nowrap; }
  .nav-links a::before { left: 6px; }
  /* brand + live count + search will not fit on one line at 390px; the count
     is already on the home page, so the nav keeps brand and search. */
  .live-pill { display: none; }
  /* "GRAVIJET STATS" wraps at this width; the suffix is the part to lose. */
  .brand .word em { display: none; }
  .search { display: flex; flex: 1; min-width: 0; }
  .search-input { width: 100%; min-width: 0; }
  .telemetry, .p-summary { grid-template-columns: 1fr; }
  /* The row can't hold four things side by side this narrow: the numbers go
     under the name, and the blurb goes — the name and its stats are the point. */
  .mode-row { flex-wrap: wrap; gap: 8px 12px; }
  .mode-row .m-id { flex: 1 0 60%; }
  .mode-row .tag { display: none; }
  .mode-row .m-foot > div { min-width: 0; text-align: left; }
  .mode-row .m-foot { gap: 20px; flex: 1 0 100%; }
  .stat-row { grid-template-columns: 1fr; }
  /* Four numbers and a name don't fit in 390px — the name lost, and came out as
     "Tops...", which is the one column nobody can afford to lose. So below this
     width a board keeps the rank, the player, and the single metric it's sorted
     by; the rest are a tap away on the profile. Each board sets its own columns
     inline (they differ per mode), hence the !important — there's no stylesheet
     selector that can outrank a style attribute. */
  .board-row, .board-head { grid-template-columns: 32px 1fr 72px !important; }
  .board-row .val.sec { display: none; }
  .board-head span.r ~ span.r { display: none; }
  .profile-hero { grid-template-columns: 1fr; }
  .hero-copy h1, .notice h2 { font-size: 32px; }
}
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }

/* --------------------------------------------------------------- link code */
.linkcode { margin-top: 14px; }
/* The one thing on the page that has to survive being read off a screen and
   retyped into a chat box, so it gets the size to match. */
.lc-code {
  font-size: 32px; letter-spacing: 4px; color: var(--mc-yellow);
  text-shadow: 3px 3px 0 #3f3f15; text-align: center; padding: 14px 0;
  background: rgba(0, 0, 0, 0.55); border: 2px solid rgba(255, 255, 255, 0.11);
  user-select: all;
}
.lc-steps { margin: 12px 0 0; padding-left: 22px; color: var(--mc-gray); font-size: 16px; line-height: 1.7; text-shadow: 2px 2px 0 #2a2a2a; }
.lc-steps b { color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.lc-ttl { margin-top: 8px; font-size: 16px; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }

/* -------------------------------------------------------------- dashboards */
/* Shared by both dashboards: the same widgets, on two different grounds. */

/* A count and what it counts. The Statistics screen's own shape — the number
   first and large, the label under it in grey — rather than a card with a
   border, which is a thing this game does not have. */
.ptiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 14px; }
.ptiles-4 { grid-template-columns: repeat(4, 1fr); }
.ptile { display: flex; flex-direction: column; gap: 2px; padding: 12px 14px; }
a.ptile:hover .pt-n { color: var(--btn-fg-hi); }
.pt-n { font-size: 24px; font-weight: 700; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.pt-l { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }

/* Something that needs attention but is not an error: the colour the game uses
   on a low-health heart, not the one it uses on a crash. */
.pwarn {
  margin-top: 12px; padding: 10px 12px; font-size: 16px;
  color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00;
  background: rgba(255, 170, 0, 0.09); border: 2px solid rgba(255, 170, 0, 0.3);
}
.pwarn a { color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; text-decoration: underline; }

/* --- the console's queue --- */
.qrow { align-items: center; }
.glyph-sm { width: 20px; height: 20px; flex-shrink: 0; }
.glyph-sm svg { width: 20px; height: 20px; }
/* Old enough that somebody should have dealt with it by now. Stated in the
   colour, so a full queue can be triaged without reading a single line of it. */
.dr.stale { color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00; }

/* --- alts --- */
.altrow { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.alt {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px;
  font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f;
  background: var(--entry); border: 2px solid rgba(255, 255, 255, 0.09);
}
.alt:hover { border-color: var(--mc-white); }
.alt img { width: 20px; height: 20px; }

@media (max-width: 900px) {
  .ptiles, .ptiles-4 { grid-template-columns: repeat(2, 1fr); }
}

/* Where a record came from, when that changes how to read it — an application
   imported from the Discord bot is history, not something you can rule on. */
.tagged { color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }

/* ------------------------------------------- filing, uploads, moderation UI */

/* A crumb back to the list a form belongs to, now that the forms live inside
   the dashboard rather than at their own address. */
.crumb {
  display: inline-block; margin-bottom: 12px; font-size: 16px;
  color: var(--mc-aqua); text-shadow: 2px 2px 0 #10353f;
}
.crumb:hover { color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; }

/* The apply picker: rows that lead to a form. No question count, no status
   badge — the count belonged to nobody's decision and the status is on the
   Applications tab where a decision is actually the subject. */
.mode-row .m-go { font-size: 16px; color: var(--mc-aqua); text-shadow: 2px 2px 0 #10353f; flex-shrink: 0; }
.mode-row:hover .m-go { color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; }

/* Screenshot uploader on a question that takes them. */
.uploader { margin: 6px 0 4px; }
.upl-btn { position: relative; overflow: hidden; }
.upl-hint { font-size: 16px; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; margin-left: 8px; }
.upl-list { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.upl-item {
  display: flex; align-items: center; gap: 6px; padding: 4px 6px;
  background: #000; border: 2px solid var(--slot-lo);
}
.upl-item img { width: 40px; height: 40px; object-fit: cover; image-rendering: auto; }
.upl-name { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; max-width: 16ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.upl-x { font: inherit; font-size: 16px; background: none; border: 0; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; cursor: pointer; }
.upl-x:hover { color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515; }

/* An online player, on the console look-up. */
.online-dot { display: inline-block; width: 9px; height: 9px; background: var(--mc-green); box-shadow: 0 0 0 2px #10230f; vertical-align: middle; }

/* Punish and rank controls on the player card. A row of selects, then a reason,
   then the button — the shape of the command it becomes. */
.prow { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.prow .fld { width: auto; flex: 0 1 auto; min-width: 120px; }
.pcheck { display: inline-flex; align-items: center; gap: 6px; font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.fld-inline { width: auto; flex: 1; min-width: 140px; }
#punishblock { border-left: 3px solid rgba(255, 85, 85, 0.4); padding-left: 12px; }
#rankblock { border-left: 3px solid rgba(85, 255, 85, 0.35); padding-left: 12px; }
/* Active restrictions sit above the punish form, marked in the amber the game
   uses for "this is in force", so unmute/unban reads as the first thing here. */
#restrictions { border-left: 3px solid rgba(255, 170, 0, 0.45); padding-left: 12px; }
.revrow { margin-top: 10px; }
.revwhat { flex: 1 1 220px; min-width: 0; }

/* ------------------------------------------------------ the rules editor */
.ed-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 10px 0; margin-bottom: 12px;
  border-bottom: 2px solid rgba(255, 170, 0, 0.28); position: sticky; top: 56px;
  background: #070707; z-index: 20;
}
.ed-sec { margin-bottom: 14px; }
.ed-sechead { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }
.ed-title { font-weight: 700; }
.ed-intro { margin-bottom: 10px; }
.ed-moves { display: flex; gap: 4px; flex-shrink: 0; }
.ed-mini { padding: 2px 8px; font-size: 16px; min-height: 0; }
.ed-del { color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515; }
.ed-rules { display: flex; flex-direction: column; gap: 10px; }
.ed-rule { padding: 10px; border: 2px solid var(--slot-lo); background: rgba(0, 0, 0, 0.35); display: flex; flex-direction: column; gap: 6px; }
.ed-rulehead { display: flex; gap: 8px; align-items: center; }
.ed-rtitle { flex: 1; }
.ed-detail { color: var(--mc-gray); }
.ed-ladder { display: flex; align-items: center; gap: 8px; font-size: 16px; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }
.ed-ladder .fld { width: auto; flex: 0 1 260px; }
.ed-addrule { margin-top: 10px; }

/* --------------------------------------------------------- the access grid */
.acc-person { margin-bottom: 12px; }
.acc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 6px; margin-bottom: 8px; }
.acc-item {
  display: grid; grid-template-columns: auto 1fr; column-gap: 8px; align-items: baseline;
  padding: 6px 8px; border: 2px solid transparent; cursor: pointer;
}
.acc-item:hover { border-color: var(--slot-lo); }
.acc-label { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.acc-note { grid-column: 2; font-size: 16px; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }
/* A box that no longer matches the rank default — an exception you have made. */
.acc-over { border-color: rgba(255, 170, 0, 0.5); }
.acc-item input[disabled] { opacity: 0.4; }
/* An ability the editor does not hold themselves, so it is not theirs to grant. */
.acc-nope { cursor: not-allowed; }
.acc-nope .acc-label { opacity: 0.45; }

/* --------------------------------------------------- the network editor */
/* Ranks and ladders. The danger button — delete a rank, drop a rung — wears the
   red the game keeps for the choices it wants you to hesitate over. */
.btn-danger { background-color: #7a1f1f; border-color: var(--outline); }
.btn-danger:hover { background-color: #9c2a2a; border-color: var(--mc-white); color: var(--mc-white); }
.ls-del { min-height: 0; padding: 4px 10px; }

/* Each rank is a drawer: shut, it is one legible row; open, it is its editor.
   Native <details>, so it collapses without a line of script. */
.rank-ed { border: 2px solid var(--slot-lo); background: #0d0d0d; margin-bottom: 8px; }
.rank-ed[open] { border-color: var(--outline); }
.rank-ed > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; user-select: none;
}
.rank-ed > summary::-webkit-details-marker { display: none; }
.rank-ed > summary:hover { background: rgba(255, 255, 255, 0.04); }
.rank-new { border-style: dashed; }
.re-swatch { min-width: 90px; font-size: 16px; }
.re-name { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.re-prio { font-size: 16px; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; margin-left: auto; }
.re-tag {
  font-size: 16px; color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00;
  border: 2px solid rgba(255, 170, 0, 0.35); padding: 0 6px; line-height: 1.35;
}
.re-tag.muted { color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; border-color: var(--slot-lo); }
.re-body { padding: 12px; border-top: 2px solid var(--slot-lo); display: flex; flex-direction: column; gap: 14px; }
.re-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; }
.re-field { display: flex; flex-direction: column; gap: 4px; }
.re-field.re-inline { flex-direction: row; align-items: center; gap: 8px; }
.re-lab { font-size: 16px; color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; }
.re-flags { display: flex; flex-wrap: wrap; gap: 6px 16px; }
.re-flag { display: inline-flex; align-items: center; gap: 6px; font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.re-hint { margin: 0; }
.re-count { color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }

.re-perms { display: flex; flex-direction: column; gap: 8px; }
.permchips { display: flex; flex-wrap: wrap; gap: 6px; }
.permchip {
  display: inline-flex; align-items: center; gap: 6px; font-size: 16px;
  color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f;
  background: rgba(0, 0, 0, 0.4); border: 2px solid var(--slot-lo); padding: 2px 4px 2px 8px;
}
.permx { border: 0; background: none; color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515; cursor: pointer; font-size: 18px; line-height: 1; padding: 0 4px; }
.permx:hover { color: var(--mc-white); }
.re-permadd { display: flex; gap: 8px; }
.re-permadd .fld { flex: 1; min-width: 160px; }

.re-inherit { display: flex; flex-direction: column; gap: 8px; }
.inhrow { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.inhtog { display: inline-flex; align-items: center; gap: 6px; font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }

/* A ladder rung as one editable line: which offence number, what it becomes, for
   how long, how long it counts, and the two flags. */
.lad-card { margin-bottom: 14px; }
.lad-steps { display: flex; flex-direction: column; gap: 8px; margin-bottom: 6px; }
.lad-step {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 8px; border: 2px solid var(--slot-lo); background: rgba(0, 0, 0, 0.35);
}
.ls-n { min-width: 24px; text-align: center; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }
.lad-step .fld { width: auto; }
.ls-num { width: 74px; }
.ls-dur, .ls-decay { display: inline-flex; align-items: center; gap: 6px; }
.ls-declab { font-size: 16px; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }
.ls-del { margin-left: auto; }
.lad-head { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 12px; }

/* --------------------------------------------------- roll-out cards */
/* Shut, a card is one legible line — a name and enough to tell it apart; open,
   its whole body. Native <details>, the same gesture as the ranks editor, worn
   by reports, appeals, applications, ladders, access, the report menu and
   backups so a long list is a list, not a wall. */
.card-roll > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 10px;
  padding: 11px 14px; user-select: none;
}
.card-roll > summary::-webkit-details-marker { display: none; }
.card-roll > summary:hover { background: rgba(255, 255, 255, 0.03); }
.card-roll[open] > summary { border-bottom: 2px solid var(--outline); background: rgba(0, 0, 0, 0.4); }
.cs-name { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; }
.cs-meta { margin-left: auto; font-size: 16px; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a; }
.panel-body .ph-sub { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; margin-bottom: 8px; }
.badge.dead { color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515; }

/* --------------------------------------------------- users + broadcast */
.uav { width: 40px; height: 40px; flex-shrink: 0; }
.uav-blank { background: var(--slot-lo); display: inline-block; }
.utag {
  font-size: 16px; color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15;
  border: 2px solid rgba(255, 170, 0, 0.3); padding: 0 5px; margin-left: 4px;
}
.bcprev {
  min-height: 30px; padding: 8px 10px; margin: 8px 0;
  border: 2px solid var(--slot-lo); background: #000; font-size: 16px;
}

/* ------------------------------------------------ the network hub's pages */
/* A second order of navigation, quieter than the strip above it: by the time you
   are reading this row you have already chosen where you are, and it only has to
   say which part of it you are changing. */
.subtabs {
  display: flex; flex-wrap: wrap; gap: 2px; margin-bottom: 18px;
  padding-bottom: 8px; border-bottom: 2px solid var(--slot-lo);
}
.subtab {
  font-size: 16px; padding: 6px 10px; color: var(--mc-gray);
  text-shadow: 2px 2px 0 #2a2a2a; border: 2px solid transparent;
}
.subtab:hover { color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; }
.subtab.active {
  color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f;
  border-color: var(--slot-lo); background: #0d0d0d;
}

/* A regex or a command is read character by character, so it gets a real mono
   face — the pixel font is for prose, not for `\b(a|b)\1{3,}`. */
.mono { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 15px; }
textarea.fld.mono { line-height: 1.45; }

.logbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.logbar .lb-tabs { flex-shrink: 0; }

/* ------------------------------------------------------- the console's list */
/* Twenty destinations will not go along the top of a page at any size, in any
   arrangement — so they go down the side, grouped by what you came to do, and
   the header carries nothing but the mark and the account. Every page is one
   click from every other, and always in the same place. */
.console-layout { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 24px; align-items: start; }
.conside { position: sticky; top: 76px; display: flex; flex-direction: column; gap: 14px; }
.csearch { position: relative; }
.csearch .fld { width: 100%; }
.csearch .search-results { left: 0; right: auto; width: 100%; top: 100%; margin-top: 2px; }
.csearch .search-row { justify-content: space-between; }
.cg { display: flex; flex-direction: column; gap: 1px; }
.cg-label {
  font-size: 16px; color: var(--mc-dark-gray); text-shadow: 2px 2px 0 #1a1a1a;
  padding: 0 8px 3px;
}
.cg-item {
  font-size: 16px; padding: 5px 8px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a;
  border-left: 3px solid transparent;
}
.cg-item:hover { color: var(--mc-yellow); text-shadow: 2px 2px 0 #3f3f15; background: rgba(255, 255, 255, 0.03); }
.cg-item.active {
  color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f;
  border-left-color: var(--mc-gold); background: rgba(255, 170, 0, 0.08);
}

/* Narrow: the list goes back on top as one scrolling row, because a 190px column
   next to a phone leaves nothing for the page itself. */
@media (max-width: 860px) {
  .console-layout { grid-template-columns: 1fr; gap: 14px; }
  .conside {
    position: static; flex-direction: row; gap: 10px;
    overflow-x: auto; scrollbar-width: none; padding-bottom: 6px;
    border-bottom: 2px solid var(--slot-lo);
  }
  .conside::-webkit-scrollbar { display: none; }
  .cg { flex-direction: row; gap: 2px; flex-shrink: 0; }
  .cg-label { display: none; }
  .cg-item { white-space: nowrap; border-left: 0; border-bottom: 3px solid transparent; }
  .cg-item.active { border-left: 0; border-bottom-color: var(--mc-gold); }
}

/* A suggestion list that belongs to its field rather than to the header: full
   width of the input, directly beneath it. */
.suggest-wrap { position: relative; }
.suggest-wrap .search-results { left: 0; right: auto; width: 100%; top: 100%; margin-top: 2px; }

/* A server, at a glance: is it answering, and how full is it. */
.srv-dot { width: 10px; height: 10px; flex-shrink: 0; display: inline-block; background: var(--mc-dark-gray); }
.srv-dot.on { background: var(--mc-green); }
.srv-bar { width: 90px; height: 10px; flex-shrink: 0; background: #000; border: 2px solid var(--slot-lo); }
.srv-bar span { display: block; height: 100%; background: var(--mc-green); }
.srv-row { display: flex; align-items: center; gap: 10px; }
.srv-card { margin-bottom: 8px; }
.srv-card .srv-dot { margin-right: 2px; }

/* The unstick tools: a row of plain buttons, deliberately not styled like the
   punish controls above them — none of these go on anybody's record. */
.toolrow { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.urow-right { display: inline-flex; align-items: center; gap: 8px; }

/* A frozen transcript, read the way the game printed it — the colours are part
   of who was speaking, so stripping them makes it harder to follow, not easier. */
.snapbox {
  max-height: 340px; overflow-y: auto; padding: 10px;
  background: #000; border: 2px solid var(--slot-lo);
}
.snapline { font-size: 16px; line-height: 1.5; padding: 1px 0; }
.snapline .dr { margin-right: 6px; }

/* ------------------------------------------------------------- the dialog */
/* The game darkens the world behind a menu rather than replacing it; so does
   this. The panel is the same slot-and-bevel as everything else, because the
   moment a confirmation looks like it came from somewhere else is the moment it
   stops looking like part of the network. */
.modal-back {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0, 0, 0, 0.72);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.modal {
  width: 100%; max-width: 440px;
  background: #0d0d0d; border: 2px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(255, 255, 255, 0.14), inset -2px -2px 0 rgba(0, 0, 0, 0.5);
}
.modal-head {
  padding: 12px 16px; font-size: 16px;
  color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f;
  border-bottom: 2px solid var(--slot-lo); background: rgba(0, 0, 0, 0.4);
}
.modal-body {
  padding: 14px 16px; font-size: 16px; line-height: 1.55;
  color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a;
}
.modal-acts { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px; }
@media (max-width: 520px) {
  .modal-acts { flex-direction: column-reverse; }
  .modal-acts .btn { width: 100%; }
}

/* The host page: a starting server is neither up nor down, and saying so with a
   third colour beats leaving it looking offline while it boots. */
.srv-dot.warm { background: var(--mc-gold); }
.host-card .block > summary { padding-left: 0; }

/* --------------------------------------------------------------- meters */
/* A number you have to do arithmetic on is not a reading. The bar is the
   reading, and its colour is part of it: green while there is room, gold when it
   is tight, red when the next thing to start probably will not. */
.meter { margin-bottom: 12px; }
.meter-top { display: flex; align-items: baseline; gap: 10px; margin-bottom: 4px; }
.meter-l { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; min-width: 72px; }
.meter-v { font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.meter-p { margin-left: auto; font-size: 16px; color: var(--mc-green); text-shadow: 2px 2px 0 #153f15; }
.meter-p.warm { color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00; }
.meter-p.hot { color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515; }
.meter-bar { height: 14px; background: #000; border: 2px solid var(--slot-lo); }
.meter-bar span { display: block; height: 100%; background: var(--mc-green); }
.meter-bar span.warm { background: var(--mc-gold); }
.meter-bar span.hot { background: var(--mc-red); }
.meter-note { margin-top: 10px; }

/* A console log is read as a block of monospace, newest last, scrolled inside
   its own box rather than making the whole page a mile long. */
.logbox {
  max-height: 380px; overflow: auto; padding: 10px; margin-bottom: 10px;
  background: #000; border: 2px solid var(--slot-lo);
}
.logline {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 14px; line-height: 1.45; white-space: pre-wrap; word-break: break-word;
  color: var(--mc-gray);
}

/* Tick rate, where twenty is perfect and the colour is the whole message. */
.tps { font-size: 16px; color: var(--mc-green); text-shadow: 2px 2px 0 #153f15; }
.tps.warm { color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00; }
.tps.hot { color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515; }

/* The file browser: a path line, then the listing, then the editor under it. */
.filebar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.editorbox {
  width: 100%; min-height: 320px; resize: vertical;
  font-size: 14px; line-height: 1.45; white-space: pre; overflow-wrap: normal; overflow-x: auto;
}

/* --------------------------------------------------------- command palette */
/* The keyboard shortcut over the whole console. It is the dialog's darker
   cousin — same darkened world, same bevelled panel — but anchored near the top
   where a launcher belongs and where the eyes already are when you hit ⌘K. */
.pal-back {
  position: fixed; inset: 0; z-index: 210;
  background: rgba(0, 0, 0, 0.66);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 84px 20px 20px;
}
.pal {
  width: 100%; max-width: 560px;
  background: #0d0d0d; border: 2px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(255, 255, 255, 0.14), inset -2px -2px 0 rgba(0, 0, 0, 0.5);
  display: flex; flex-direction: column; max-height: 70vh;
}
.pal-top {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  border-bottom: 2px solid var(--slot-lo); background: rgba(0, 0, 0, 0.4);
}
.pal-search { width: 20px; height: 20px; color: var(--mc-gray); flex: none; }
.pal-search svg { width: 20px; height: 20px; }
.pal-in {
  flex: 1; min-width: 0; background: none; border: 0; outline: none;
  color: var(--mc-white); font-family: var(--font); font-size: 16px; text-shadow: 2px 2px 0 #3f3f3f;
}
.pal-in::placeholder { color: var(--mc-dark-gray); }
.pal-esc, .pal-foot kbd {
  font-family: var(--font); font-size: 12px; color: var(--mc-gray);
  background: var(--btn-off); border: 2px solid var(--outline);
  padding: 1px 6px; box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.12);
}
.pal-list { overflow-y: auto; padding: 6px; }
.pal-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; background: none; border: 0; text-align: left; cursor: pointer;
}
.pal-row.sel { background: var(--mc-dark-blue); }
.pal-ic { width: 18px; height: 18px; color: var(--mc-gray); flex: none; }
.pal-row.sel .pal-ic { color: var(--mc-white); }
.pal-ic svg { width: 18px; height: 18px; }
.pal-lab { font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pal-hint { margin-left: auto; padding-left: 10px; font-size: 14px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; white-space: nowrap; flex: none; }
.pal-empty { padding: 20px 12px; font-size: 16px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; }
.pal-foot {
  display: flex; gap: 16px; padding: 8px 12px;
  border-top: 2px solid var(--slot-lo); background: rgba(0, 0, 0, 0.3);
  font-size: 13px; color: var(--mc-gray);
}
.pal-foot span { display: inline-flex; align-items: center; gap: 5px; }

/* ------------------------------------------------------------------ live */
/* The watch screen: two panes, chat filling from the bottom on the left and a
   pulse of everything else on the right. Both are monospace blocks over black —
   they are logs that move, and a log is read in a fixed column. */
.live-ctl { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.live-tog {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font-size: 14px; color: var(--mc-gray); text-shadow: 2px 2px 0 #2a2a2a; white-space: nowrap;
}
.live-tog input { accent-color: var(--mc-green); }
.live-beat {
  display: inline-block; width: 9px; height: 9px; margin-right: 9px; vertical-align: middle;
  background: var(--mc-green); border: 1px solid #0a3f0a;
  animation: livebeat 1.4s ease-in-out infinite;
}
.live-beat.off { background: var(--mc-dark-gray); border-color: #000; animation: none; }
@keyframes livebeat { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }

.live-grid { display: grid; grid-template-columns: 1.35fr 1fr; gap: 12px; align-items: stretch; }
@media (max-width: 900px) { .live-grid { grid-template-columns: 1fr; } }
.live-pane { display: flex; flex-direction: column; min-height: 0; }
.live-head {
  padding: 8px 12px; font-size: 16px; color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f;
  border-bottom: 2px solid var(--slot-lo); background: rgba(0, 0, 0, 0.35);
  display: flex; align-items: baseline; gap: 10px;
}
.live-sub { font-size: 14px; color: var(--mc-gold); text-shadow: 2px 2px 0 #3f2a00; }
.livechat, .livepulse {
  height: 62vh; min-height: 320px; overflow-y: auto; padding: 8px 10px; background: #000;
}
.live-empty { padding: 22px 10px; font-size: 15px; color: var(--mc-dark-gray); }
/* Flagged-only keeps the flagged lines and hides the calm ones — the backlog is
   untouched, just not drawn. */
.livechat.flagged-only .lc-line:not(.flagged) { display: none; }
/* The flash when a filter trips: a red wash across the chat pane that fades on
   its own, so a moderator whose eyes were elsewhere sees that something landed. */
.livechat.flagflash { animation: flagflash 0.7s ease-out; }
@keyframes flagflash { 0% { box-shadow: inset 0 0 0 2px var(--mc-red); background: rgba(170, 0, 0, 0.22); } 100% { box-shadow: none; background: #000; } }

/* Chat: time, name, line — a fixed grid so names align down the left of the
   messages the way they do in game. A flagged line wears a red edge and the
   name of the filter it trips, because that is the one a moderator is scanning
   for. */
.lc-line {
  display: grid; grid-template-columns: auto auto 1fr auto; gap: 8px; align-items: baseline;
  padding: 2px 4px; font-size: 15px; line-height: 1.5; border-left: 2px solid transparent;
}
.lc-line:hover { background: rgba(255, 255, 255, 0.04); }
.lc-line.flagged { border-left-color: var(--mc-red); background: rgba(170, 0, 0, 0.14); }
.lc-t { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 13px; color: var(--mc-dark-gray); }
.lc-name { font-weight: 400; text-shadow: 1px 1px 0 #000; white-space: nowrap; }
a.lc-name:hover { text-decoration: underline; }
.lc-msg { color: var(--mc-white); text-shadow: 1px 1px 0 #000; word-break: break-word; min-width: 0; }
.lc-hit { background: var(--mc-yellow); color: #000; text-shadow: none; }
.lc-flag {
  font-size: 12px; color: var(--mc-red); text-shadow: 1px 1px 0 #000; white-space: nowrap;
  border: 1px solid var(--mc-dark-red); padding: 0 4px; align-self: center;
}

/* Pulse: an icon coloured by kind, then the sentence and how long ago. The left
   edge carries the colour so a column of events reads as a column of kinds. */
.pulse-row {
  display: flex; gap: 10px; padding: 7px 6px; border-left: 3px solid var(--slot-lo);
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.pulse-ic { width: 18px; height: 18px; flex: none; margin-top: 2px; color: var(--mc-gray); }
.pulse-ic svg { width: 18px; height: 18px; }
.pulse-body { min-width: 0; }
.pulse-txt { font-size: 15px; line-height: 1.45; color: var(--mc-white); text-shadow: 1px 1px 0 #000; word-break: break-word; }
.pulse-txt b { font-weight: 400; }
.pulse-when { font-size: 13px; color: var(--mc-dark-gray); margin-top: 1px; }
.ev-note { color: var(--mc-gray); }
.ev-perm { color: var(--mc-red); }
.pulse-row.ev-punish { border-left-color: var(--mc-red); } .ev-punish .pulse-ic, .ev-punish .ev-verb { color: var(--mc-red); }
.pulse-row.ev-warn   { border-left-color: var(--mc-gold); } .ev-warn .pulse-ic, .ev-warn .ev-verb { color: var(--mc-gold); }
.pulse-row.ev-lift   { border-left-color: var(--mc-green); } .ev-lift .pulse-ic, .ev-lift .ev-verb { color: var(--mc-green); }
.pulse-row.ev-join   { border-left-color: var(--mc-green); } .ev-join .pulse-ic, .ev-join .ev-verb { color: var(--mc-green); }
.pulse-row.ev-leave  { border-left-color: var(--mc-dark-gray); } .ev-leave .pulse-ic, .ev-leave .ev-verb { color: var(--mc-gray); }
.pulse-row.ev-cmd    { border-left-color: var(--mc-blue); } .ev-cmd .pulse-ic { color: var(--mc-blue); }

/* The dossier's ban-evasion tell: a connected account under a live restriction
   is edged red and counted in the section label, because it is the one thing on
   the page worth opening it for. */
.conn-alarm {
  font-size: 13px; color: var(--mc-red); text-shadow: 2px 2px 0 #3f1515;
  border: 1px solid var(--mc-dark-red); padding: 0 5px; margin-left: 4px;
}
.board-row.conn-banned { border-color: var(--mc-dark-red); background: rgba(170, 0, 0, 0.12); }

/* ------------------------------------------------------- team activity */
/* Each member is a head, a name, their punishment chips and a bar that compares
   to the busiest person on the team rather than to nothing. The bar is a sense
   of load, not a score — the numbers beside it are the real reading. */
.act-row { align-items: center; }
.act-head { border: 2px solid var(--outline); image-rendering: pixelated; flex: none; }
.act-top { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.act-top .dn { text-shadow: 1px 1px 0 #000; }
a.dn:hover { text-decoration: underline; }
.act-chips { display: flex; flex-wrap: wrap; gap: 5px; margin: 5px 0; }
.act-chip { font-size: 13px; }
.act-blacklist, .act-ban { color: var(--mc-red); border-color: var(--mc-dark-red); }
.act-mute { color: var(--mc-gold); border-color: #3f2a00; }
.act-kick, .act-warn { color: var(--mc-yellow); border-color: #3f3f15; }
.act-cmd { color: var(--mc-blue); border-color: #15153f; }
.act-bar { height: 8px; background: #000; border: 1px solid var(--slot-lo); max-width: 320px; }
.act-bar span { display: block; height: 100%; background: var(--mc-green); }
.act-when { white-space: nowrap; align-self: center; }
.act-dark { font-size: 12px; color: var(--mc-gold); border: 1px solid #3f2a00; padding: 0 5px; text-shadow: 1px 1px 0 #000; }

/* A report's inline chat evidence: the accused's own recent lines, in a small
   scrolling block so a chatty player does not push the decision buttons off the
   bottom of the card. */
.rep-chatlog { max-height: 220px; overflow-y: auto; background: #000; border: 2px solid var(--slot-lo); padding: 6px 8px; }
.rep-line { font-size: 15px; line-height: 1.5; padding: 1px 0; }
.rep-line .dr { margin-right: 6px; }
.rep-msg { color: var(--mc-white); text-shadow: 1px 1px 0 #000; }

/* Dossier identity chips. Disguise is the alarm — a nicked player is somebody
   else in game — so it wears the warning colour; the rest are quiet facts. */
.id-chip { font-size: 13px; }
.id-disguise { color: var(--mc-gold); border-color: #3f2a00; }
.id-auth { color: var(--mc-aqua); border-color: #153f3f; }
.id-2fa { color: var(--mc-green); border-color: #153f15; }

/* When-they-play: twenty-four bars, one per hour, scaled to their own busiest
   hour. Empty hours keep a faint baseline tick so the gaps in a schedule are as
   readable as the peaks. */
.playgrid { display: flex; align-items: flex-end; gap: 2px; height: 64px; background: #000; border: 2px solid var(--slot-lo); padding: 5px 6px; }
.playbar { flex: 1; min-height: 2px; background: var(--mc-green); }
.playbar.empty { background: var(--slot-lo); opacity: 0.5; }
.playaxis { display: flex; justify-content: space-between; font-size: 12px; color: var(--mc-dark-gray); margin-top: 3px; }

/* ---------------------------------------------------- the punishment record */
/* The network's whole ledger of bans, mutes, kicks and blacklists. A row per
   punishment, five columns on a wide console — who it hit, what it was, why,
   who gave it, and whether it still bites — stacking when the console narrows.
   The type chips carry live counts so a search tells you what pressing each one
   would show before you press it. */
.prbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; justify-content: space-between; }
.pr-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.pr-tools { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.pr-chip { font-size: 15px; padding: 7px 12px; min-height: 36px; display: inline-flex; align-items: center; gap: 7px; }
.pr-chip.active { color: var(--mc-white); text-shadow: 2px 2px 0 #3f3f3f; border-color: var(--slot-hi); background: #0d0d0d; }
.pr-cn { font-size: 12px; color: var(--mc-dark-gray); }
.pr-chip.active .pr-cn { color: var(--mc-yellow); }

.pr-count { font-size: 15px; color: var(--mc-gray); text-shadow: 1px 1px 0 #000; margin: 12px 2px 8px; }
.pr-count b { color: var(--mc-white); }
.pr-live { color: var(--mc-red) !important; }

.pnrow {
  display: grid;
  grid-template-columns: minmax(150px, 1.2fr) 80px minmax(130px, 1.7fr) minmax(120px, 0.9fr) 94px;
  gap: 10px; align-items: center;
  background: var(--entry); border: 2px solid var(--slot-lo);
  padding: 8px 10px; margin-bottom: 6px;
}
.pnrow:hover { border-color: var(--slot); }
.pr-who { display: flex; align-items: center; gap: 8px; min-width: 0; }
.pr-who img { image-rendering: pixelated; border: 1px solid #000; flex-shrink: 0; }
.pr-wg { min-width: 0; display: flex; flex-direction: column; }
.pr-name { font-size: 16px; text-shadow: 2px 2px 0 #000; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a.pr-name:hover { text-decoration: underline; }
.pr-sub { font-size: 12px; color: var(--mc-dark-gray); text-shadow: 1px 1px 0 #000; overflow: hidden; text-overflow: ellipsis; }
.pr-reason { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.pr-rtext { font-size: 15px; color: var(--mc-white); text-shadow: 1px 1px 0 #000; word-break: break-word; }
.pr-none { color: var(--mc-dark-gray); }
.pr-by { min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px; }
.pr-by .pr-name { font-size: 14px; }
.pr-state { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; }

/* The type pill: each punishment its own colour so a page of them reads at a
   glance. Red for a ban, purple for the blacklist above it, calmer shades for
   the rest. */
.pr-type { font-size: 13px; text-align: center; padding: 2px 4px; border: 1px solid; text-shadow: 1px 1px 0 #000; white-space: nowrap; }
.pt-ban { color: var(--mc-red); border-color: #3f1515; }
.pt-blacklist { color: var(--mc-light-purple); border-color: #3f153f; }
.pt-mute { color: var(--mc-gold); border-color: #3f2a00; }
.pt-kick { color: var(--mc-aqua); border-color: #153f3f; }
.pt-warn { color: var(--mc-yellow); border-color: #3f3f15; }

.pr-pager { display: flex; align-items: center; justify-content: center; gap: 14px; margin: 14px 0 4px; }
.pr-pn { font-size: 15px; color: var(--mc-gray); text-shadow: 1px 1px 0 #000; }

@media (max-width: 720px) {
  .pnrow { display: flex; flex-direction: column; align-items: stretch; gap: 5px; }
  .pr-type { align-self: flex-start; }
  .pr-state { flex-direction: row; align-items: center; gap: 8px; }
}

/* -------------------------------------------------------- the feedback board */
/* Suggestions other players can upvote. Each card is a vote pill on the left —
   caret over count, the way a board of ideas is read everywhere — and the idea
   on the right. Pressing the pill lights it; pressing again takes the vote back.
   Bugs never appear here; they stay between their author and the team. */
.sbg { display: flex; flex-direction: column; gap: 8px; }
.scard { display: flex; gap: 12px; align-items: flex-start; background: var(--entry); border: 2px solid var(--slot-lo); padding: 10px 12px; }
.scard:hover { border-color: var(--slot); }
.votebtn {
  flex-shrink: 0; display: flex; flex-direction: column; align-items: center; gap: 1px;
  min-width: 46px; padding: 6px 4px; background: #0d0d0d; border: 2px solid var(--slot-lo);
  color: var(--mc-gray); cursor: pointer; text-shadow: 1px 1px 0 #000;
}
.votebtn:hover:not(:disabled) { border-color: var(--mc-yellow); color: var(--mc-yellow); }
.votebtn.on { border-color: var(--mc-green); color: var(--mc-green); text-shadow: 1px 1px 0 #153f15; }
.votebtn:disabled { cursor: default; opacity: 0.6; }
.vote-caret { font-size: 13px; line-height: 1; }
.vote-n { font-size: 17px; line-height: 1; }
.sc-body { min-width: 0; flex: 1; }
.sc-top { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.sc-title { font-size: 17px; color: var(--mc-white); text-shadow: 2px 2px 0 #000; }
.sc-meta { font-size: 13px; color: var(--mc-dark-gray); text-shadow: 1px 1px 0 #000; margin-top: 2px; }
.sc-detail { font-size: 15px; color: var(--mc-gray); text-shadow: 1px 1px 0 #000; margin-top: 6px; line-height: 1.5; }

/* Staff triage: the kind switch, the status filters with counts, and the row of
   controls under each card — set a status, leave a note back, save. */
.fbbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; }
.fb-cn { font-size: 12px; color: var(--mc-dark-gray); }
.lb-tab.active .fb-cn { color: var(--mc-yellow); }
.fb-ctl { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 12px; }
.fb-ctl .fld-inline { min-width: 150px; }
.fb-ctl input.fld-inline { flex: 1; }
.fb-votes { color: var(--mc-green); }
