/* ---------------------------------------------------------------------------
   Tokens.

   Every measurement in this file comes from one of these scales. The point is
   not tidiness for its own sake: an interface built from arbitrary pixel values
   drifts a little with every edit, and the drift is what reads as "unpolished"
   long before anyone can name a specific wrong number.
   --------------------------------------------------------------------------- */

:root {
  --bg: #0a0e18;
  --panel: rgba(10, 15, 27, 0.88);
  --line: rgba(255, 255, 255, 0.10);
  --line-strong: rgba(255, 255, 255, 0.18);
  --text: #e8eef8;
  --dim: rgba(232, 238, 248, 0.55);
  --accent: #3ba7ff;
  --good: #5ee27a;
  --warn: #ffd23f;
  --bad: #ff5f6d;

  /* Spacing. One scale, used for every gap, margin and padding. */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 22px;
  --s6: 30px;

  /* Corner radii, by element size rather than by taste. */
  --r-sm: 8px;   /* things inside a panel */
  --r-md: 11px;  /* panels, inputs, buttons */
  --r-lg: 16px;  /* cards */
  --r-pill: 999px;

  /* Type. */
  --fz-micro: 10px; /* uppercase labels */
  --fz-sm: 12px;
  --fz-base: 13.5px;
  --fz-md: 15px;
  --fz-lg: 19px;
  --fz-xl: 26px;

  /* How far any fixed overlay sits from the edge of the viewport. Shared by
     every HUD panel so they line up with each other on all four corners. */
  --edge: 12px;

  --ease: 0.14s ease;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font: var(--fz-base)/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-user-select: none;
  user-select: none;
  overscroll-behavior: none;
  touch-action: none;
}

#game { display: block; position: fixed; inset: 0; }

/* ---------------------------------------------------------------------------
   Shared element rules
   --------------------------------------------------------------------------- */

/* Anything numeric that changes while you watch it. Tabular figures keep the
   glyphs the same width; the min-width keeps the box the same width when the
   digit count changes. Together they are what stops the HUD twitching. */
.tnum {
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* A floating panel. Same skin for the stats, the score, the watch bar and the
   reconnect notice, so they read as one system rather than four decisions. */
.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}

/* Small uppercase label. */
.micro {
  font-size: var(--fz-micro);
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--dim);
}

button {
  padding: var(--s3) var(--s4);
  /* Transparent rather than none, so a button that draws a border and one that
     does not are the same height. The border box is the same either way; only
     the colour changes. */
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, 0.07);
  color: var(--text);
  font: inherit;
  font-size: var(--fz-base);
  font-weight: 700;
  /* One line height for every control, so a button, an input and a chip with
     the same padding come out the same height whatever type size they carry.
     Inherited, the number scales with the font and the larger labels stood
     half a pixel proud of everything beside them. */
  line-height: 1.305;
  cursor: pointer;
  transition: background var(--ease), filter var(--ease), transform 0.06s ease;
}

button:hover { background: rgba(255, 255, 255, 0.12); }
button:active { transform: translateY(1px); }
button:disabled { opacity: 0.4; cursor: default; transform: none; }
button:disabled:hover { background: rgba(255, 255, 255, 0.07); }

button.primary {
  background: linear-gradient(135deg, #3ba7ff, #2f7fe0);
  color: #fff;
  font-size: var(--fz-md);
}

button.primary:hover { background: linear-gradient(135deg, #3ba7ff, #2f7fe0); filter: brightness(1.1); }

button.ghost {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid var(--line);
  color: var(--dim);
}

/* Buttons that sit in a card are full width and stack with a fixed gap.
   Direct children only: a descendant selector here also caught the buttons
   inside the lives and skin grids, where a top margin eats into the stretched
   cell and leaves the first item taller than the rest. */
.card > button { width: 100%; }
.card > button + button { margin-top: var(--s2); }

input {
  padding: var(--s3) var(--s4);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--text);
  font: inherit;
  font-size: var(--fz-md);
  /* After the font shorthand, which would otherwise reset it. Pinned so an
     input comes out the same height as a button with the same padding: the two
     sit in the same dialogs, and the inherited line height left the field two
     pixels taller than everything beside it. */
  line-height: 1.305;
  outline: none;
  transition: border-color var(--ease), background var(--ease);
}

input:focus { border-color: var(--accent); background: rgba(59, 167, 255, 0.10); }
input::placeholder { color: rgba(232, 238, 248, 0.35); }

/* ---------------------------------------------------------------------------
   HUD
   --------------------------------------------------------------------------- */

#hud { display: none; }

/* Top left, top centre and top right all share one offset and one panel skin,
   so the three read as a row rather than as three separate things. */
#stats {
  position: fixed;
  left: var(--edge);
  top: var(--edge);
  display: flex;
  align-items: center;
  padding: var(--s2) var(--s3);
  font-size: var(--fz-sm);
  /* One line-height for every item, so boxes of different type sizes hold
     their digits at the same relative height and centring the boxes centres
     the numbers too. */
  line-height: 1;
  font-weight: 600;
  color: var(--dim);
}

/* Each figure sits in a slot wide enough that the panel does not resize when a
   digit is gained or lost -- and centred in it, so the spare width is split
   evenly instead of piling up on one side. Left-aligned, the reserved space
   collected after the number and doubled the gap before the next divider. */
#stats .score,
#stats .ping,
#stats .fps { text-align: center; }

#stats .score {
  /* Wide enough that a five-figure score -- which is the whole range the duel
     produces -- still has spare width to centre in, so the gap beside the first
     divider matches the other three instead of being the one tight one. */
  min-width: 5.5ch;
  font-size: var(--fz-lg);
  font-weight: 700;
  color: var(--text);
}

#stats .ping { min-width: 6ch; }
#stats .fps { min-width: 6ch; }
#stats .sep { margin: 0 var(--s2); opacity: 0.28; }
#stats .ping.good { color: var(--good); }
#stats .ping.ok { color: var(--warn); }
#stats .ping.bad { color: var(--bad); }

/* The score of the match, which is the number players actually watch. Centred
   at the top and set large: it was previously a small pill tucked under the
   frame rate, which is where you put a diagnostic, not a scoreline.

   Laid out as a grid rather than as flex rows so the numbers, the colon and the
   two names line up on shared baselines by construction, instead of by nudging
   each of them separately. */
#tally {
  position: fixed;
  left: 50%;
  top: var(--edge);
  transform: translateX(-50%);
  display: grid;
  grid-template-columns: 64px auto 64px;
  column-gap: var(--s3);
  justify-items: center;
  align-items: center;
  padding: var(--s2) var(--s4);
}

#tally .num {
  font-size: var(--fz-xl);
  font-weight: 800;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

#tally .num.me { color: var(--good); }
#tally .num.them { color: var(--bad); }

#tally .colon {
  font-size: var(--fz-lg);
  font-weight: 700;
  line-height: 1.1;
  color: var(--dim);
  opacity: 0.4;
}

#tally .who {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fz-micro);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--dim);
}

#minimap {
  position: fixed;
  right: var(--edge);
  top: var(--edge);
  width: 150px;
  height: 150px;
}

/* The watch bar sits at the bottom so it never fights the scoreline. */
#specBar {
  display: none;
  position: fixed;
  left: 50%;
  bottom: var(--edge);
  transform: translateX(-50%);
  z-index: 12;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s2) var(--s2) var(--s4);
  border-radius: var(--r-pill);
  font-size: var(--fz-base);
  font-weight: 600;
  white-space: nowrap;
}

#specBar button { padding: var(--s2) var(--s4); border-radius: var(--r-pill); }

#reconnect {
  display: none;
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 14;
  padding: var(--s3) var(--s5);
  border-color: rgba(255, 210, 63, 0.35);
  color: var(--warn);
  font-size: var(--fz-base);
  font-weight: 600;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Main menu

   One centred column, read top to bottom: who you are, what is on offer, and
   the one thing you can start yourself. It stays full screen rather than living
   in a card, but every piece in it is built from the same panel, button and
   type rules as the in-game cards, which is what makes the two feel related.
   --------------------------------------------------------------------------- */

/* Two ways in, side by side rather than one hidden behind a link: a new player
   and a returning one arrive at this screen equally often. */
.segmented {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s1);
  margin-bottom: var(--s3);
  padding: var(--s1);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, 0.04);
}

.seg {
  padding: var(--s2) var(--s3);
  background: transparent;
  border-radius: var(--r-sm);
  font-size: var(--fz-base);
  color: var(--dim);
}

.seg.sel {
  background: rgba(59, 167, 255, 0.18);
  color: var(--text);
}

.seg.sel:hover { background: rgba(59, 167, 255, 0.24); }

#authModal input { width: 100%; }
#authModal input + input { margin-top: var(--s2); }

.formHint { margin: var(--s2) 0 var(--s4); font-size: var(--fz-sm); color: var(--dim); }

/* Errors from the server appear here and nowhere else, so the card never grows
   a second place to look when something goes wrong. */
.formErr {
  margin: var(--s3) 0 0;
  min-height: 1.45em;
  font-size: var(--fz-sm);
  color: #ff8f9c;
  text-align: center;
}

#menu {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  padding: var(--s5);
  background:
    radial-gradient(ellipse at 50% 0%, rgba(59, 167, 255, 0.10), transparent 62%),
    radial-gradient(ellipse at center, rgba(10, 14, 24, 0.86), rgba(6, 9, 16, 0.97));
  overflow-y: auto;
}

.menuInner {
  width: 100%;
  max-width: 440px;
  display: flex;
  flex-direction: column;
}

.menuHead { text-align: center; margin-bottom: var(--s6); }

.brand {
  margin: 0;
  /* Sized for the whole wordmark including the domain tail, which is what
     has to fit inside the column -- not just the name. */
  font-size: clamp(24px, 4.8vw, 42px);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
  /* Plain white. The gradient was the one piece of the interface that did not
     follow its own type rules -- everything else is white for the thing you are
     reading, dimmed for what supports it, and blue only for what you can press.
     The mark is the brightest thing on the screen and nothing else is, which is
     the hierarchy doing the work instead of the colour. */
  color: #fff;
}

/* The domain, carried by the wordmark rather than printed under it. Same face
   and weight as the name -- a lighter weight made it look like a different
   font set next to it -- and set apart by colour alone, so it reads as an
   address attached to the name rather than as part of it. */
.tld {
  color: rgba(255, 255, 255, 0.38);
}

.tagline { margin: var(--s3) 0 0; font-size: var(--fz-base); color: var(--dim); }

/* Who you are, and what you have. Above the list, because it is what you
   settle before looking at what is on offer. */
.account {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  margin-bottom: var(--s5);
}

.identity {
  display: flex;
  align-items: stretch;
  gap: var(--s2);
}

/* The name is the account's, so it is displayed rather than edited. It is a
   div rather than a button, so everything the button rule would have given it
   -- the padding, the corner radius -- has to be said here, or it reads as the
   one square-cornered box in a row of rounded ones. */
.chip.who {
  flex: 1 1 120px;
  min-width: 0;
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  justify-content: flex-start;
  cursor: default;
  background: rgba(255, 255, 255, 0.04);
}

.chip.who:hover { background: rgba(255, 255, 255, 0.04); }
.chip.who span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The one thing on this screen that is worth signing in for, so it reads as an
   invitation rather than as another grey control. */
.chip.signIn {
  flex: 1 1 auto;
  justify-content: center;
  border-color: rgba(59, 167, 255, 0.45);
  background: rgba(59, 167, 255, 0.16);
  color: var(--text);
  font-weight: 700;
}

.chip.signIn:hover { background: rgba(59, 167, 255, 0.24); }

/* The wallet: a balance and the two things you can do with it, side by side.
   Naming the actions is the point -- a number on its own gives a player no
   reason to believe money can move at all. */
#wallet {
  display: none;
  align-items: stretch;
  gap: var(--s2);
}

.walletMain {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, 0.04);
}

/* The balance is the largest thing in the block, but not so large that its
   line box makes the wallet row taller than the row of chips above it: the
   line height is pinned so both rows come out the same height. */
#balanceText { font-size: var(--fz-md); font-weight: 700; line-height: 1.305; }
/* Money that is spoken for is not spendable, and the row says so rather than
   quietly showing a smaller number than the player expects. */
#balanceText .held { margin-left: 6px; font-size: var(--fz-sm); font-weight: 600; color: var(--warn); }

.walletNote {
  display: none;
  margin: 0;
  padding: 0 var(--s1);
  font-size: var(--fz-sm);
  color: var(--dim);
}

/* Signing out is a rare thing to want and a bad thing to hit by accident, so
   it lives at the bottom as text rather than in the row with the controls that
   get used every visit. */
.footNote {
  margin: var(--s3) 0 0;
  font-size: var(--fz-sm);
  color: var(--dim);
  text-align: center;
}

.footNote:not(.on) { display: none; }

.linkBtn {
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  color: var(--dim);
  font-size: var(--fz-sm);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.linkBtn:hover { background: none; color: var(--text); }

.chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  border: 1px solid var(--line);
  font-weight: 600;
}

.swatch {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
  background-origin: border-box;
}

/* Waiting happens in the lobby rather than instead of it, so the list behind
   keeps updating while you wait. */
#waitBanner {
  display: none;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s3);
  padding: var(--s3) var(--s4);
  border: 1px solid rgba(59, 167, 255, 0.35);
  border-radius: var(--r-md);
  background: rgba(59, 167, 255, 0.10);
}

.waitText { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.waitText b { font-size: var(--fz-base); }
.waitText span { font-size: var(--fz-sm); color: var(--dim); }

.listHead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 0 var(--s1) var(--s2);
}

.listHead h2 { margin: 0; }
.count { font-size: var(--fz-sm); color: var(--dim); font-variant-numeric: tabular-nums; }

/* Capped rather than free-growing: a long list would otherwise push the create
   button off a short window, and the composition would move every time somebody
   opened or closed a duel. */
#roomList {
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: 40vh;
  overflow-y: auto;
}

/* Every row is the same height whatever it holds, so a list that changes while
   you look at it does not shuffle under the cursor. */
#roomList li {
  display: flex;
  align-items: center;
  gap: var(--s3);
  height: 56px;
  padding: 0 var(--s3) 0 var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, 0.045);
}

#roomList li + li { margin-top: var(--s2); }
#roomList li.live { border-color: rgba(255, 226, 77, 0.28); }
#roomList li.mine { border-color: rgba(59, 167, 255, 0.45); background: rgba(59, 167, 255, 0.07); }

/* What a row costs. Sits next to the lives count and is the same size, because
   the price and the length are the two things that decide whether a duel is
   the one you want. */
.stakeTag {
  flex: none;
  padding: var(--s1) var(--s2);
  border-radius: var(--r-pill);
  background: rgba(94, 226, 122, 0.14);
  color: #9ff0b4;
  font-size: var(--fz-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

#roomList .who {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fz-md);
  font-weight: 600;
}

#roomList .lives {
  flex: none;
  min-width: 66px;
  padding: var(--s1) var(--s2);
  border-radius: var(--r-pill);
  background: rgba(59, 167, 255, 0.16);
  color: #7fd0ff;
  font-size: 11.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

#roomList .live .lives { background: rgba(255, 226, 77, 0.14); color: var(--warn); }

#roomList .seen {
  flex: none;
  font-size: 11.5px;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}

/* Join, Watch and Yours are words of different lengths; one width keeps the
   right edge of the list straight regardless. */
#roomList button {
  flex: none;
  width: 84px;
  padding: var(--s2) 0;
  font-size: var(--fz-base);
  border-radius: var(--r-sm);
}

#roomList button.join { background: linear-gradient(135deg, #3ba7ff, #2f7fe0); color: #fff; }
#roomList button.join:hover { filter: brightness(1.1); }

/* An empty lobby is a state, not an absence, so it gets a box of its own
   instead of a line of text floating where the rows would be. */
.empty {
  margin: 0;
  padding: var(--s5) var(--s4);
  border: 1px dashed var(--line);
  border-radius: var(--r-md);
  font-size: var(--fz-base);
  line-height: 1.5;
  color: var(--dim);
  text-align: center;
}

#createBtn { width: 100%; margin-top: var(--s5); }

#menu .hint { margin: var(--s5) 0 0; }
.hint { font-size: 11.5px; color: var(--dim); text-align: center; }
.hint b { color: var(--text); font-weight: 600; }

#status { margin: var(--s3) 0 0; font-size: var(--fz-sm); color: #ff8f9c; display: none; text-align: center; }

/* ---------------------------------------------------------------------------
   Cards and dialogs
   --------------------------------------------------------------------------- */

#dead, #result, .modal {
  display: none;
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  padding: var(--s5);
  overflow-y: auto;
}

#dead .card,
#result .card,
.modal .card { margin: auto; }

#dead, #result {
  background: radial-gradient(ellipse at center, rgba(10, 14, 24, 0.72), rgba(6, 9, 16, 0.94));
}

/* These screens are switched on by setting display, and a transition cannot
   animate out of display:none. An animation can: it runs the moment the element
   becomes displayed, which is exactly when the card should arrive. */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes cardIn {
  from { opacity: 0; transform: translateY(10px) scale(0.97); }
  to { opacity: 1; transform: none; }
}

#dead, #result, .modal { animation: fadeIn 260ms ease both; }

#dead .card,
#result .card,
.modal .card { animation: cardIn 320ms cubic-bezier(0.2, 0.7, 0.3, 1) both; }

/* Reduced motion means less movement, not less fading.
   Killing the whole animation here was wrong: it took the cross-fade with it,
   so anyone with the system setting on -- which is the default in a fair number
   of setups -- saw the card snap in. The rise and the scale are the motion, so
   only those are dropped; the opacity still eases. */
@media (prefers-reduced-motion: reduce) {
  #dead .card,
  #result .card,
  .modal .card { animation-name: fadeIn; }
}

.modal { z-index: 20; background: rgba(4, 7, 13, 0.72); }

.card {
  width: 100%;
  max-width: 340px;
  padding: var(--s6) var(--s5) var(--s5);
  text-align: center;
  background: rgba(14, 20, 34, 0.94);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.55);
}

.card h1 {
  margin: 0 0 var(--s2);
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.card h1.small { font-size: 21px; }
.card .sub { margin: 0 0 var(--s5); font-size: var(--fz-base); color: var(--dim); }
.card .sub:last-child { margin-bottom: 0; }

/* Both pickers in the create dialog are the same shape, because both ask the
   same kind of question: pick one of four. */
#targets, #stakes {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s2);
  margin-bottom: var(--s5);
}

#stakes { margin-bottom: var(--s2); }

/* The note under the stakes explains why a rung is unavailable, so it keeps
   its line whether or not it has anything to say -- otherwise the button below
   it moves as the balance changes. */
#stakeNote { min-height: 1.45em; margin: 0 0 var(--s4); text-align: center; }

.target {
  padding: var(--s3) 0;
  border: 1px solid var(--line);
  font-size: var(--fz-md);
  font-variant-numeric: tabular-nums;
}

.target.sel {
  background: rgba(59, 167, 255, 0.20);
  border-color: var(--accent);
  color: #9fdcff;
}

.target.sel:hover { background: rgba(59, 167, 255, 0.26); }

#skins {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--s2);
  margin-bottom: var(--s5);
}

.skin {
  width: auto;
  aspect-ratio: 1;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 50%;
  /* The gradient has to cover the border too.
     A background is positioned in the padding box by default but painted into
     the border box, so the two-pixel ring left over was filled by the repeat of
     the tile -- a sliver of the wrong colour along the top and right edges,
     inside an otherwise clean circle. */
  background-origin: border-box;
  transition: border-color 0.1s ease;
}

/* Hover tints the ring instead of growing the swatch.
   Scaling a 42px circle by 12% lands its rounded clip on fractional device
   pixels, and the hairline that leaves along the rim is exactly the flaw this
   picker kept showing. Nothing about the geometry changes now, so there is
   nothing left to land badly. */
.skin:hover { border-color: rgba(255, 255, 255, 0.45); }

/* Selection is a ring, not a size change. Scaling the chosen one pushed it out
   of its row by a couple of pixels and off the column grid, so the resting
   state of the picker was visibly crooked. */
.skin.sel {
  border-color: #fff;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.16);
}

/* The countdown inside the continue button. Its own slot, wide enough for the
   digit, so the label does not shuffle sideways once a second as it counts. */
#continueBtn .count {
  display: inline-block;
  min-width: 2.4ch;
  margin-left: var(--s1);
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
}

.spinner {
  width: 26px;
  height: 26px;
  border: 3px solid rgba(255, 255, 255, 0.12);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}

.spinner.small { width: 16px; height: 16px; border-width: 2px; }

@keyframes spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------------------
   Narrow screens
   --------------------------------------------------------------------------- */

@media (max-width: 560px) {
  :root { --edge: 8px; }

  /* The column already stacks; it just needs a little less air around it and
     a shorter list so the create button stays reachable on a short screen. */
  #menu { padding: var(--s4); }
  .menuHead { margin-bottom: var(--s5); }
  #roomList { max-height: 34vh; }

  /* The top row does not fit three panels on a narrow screen: the score, the
     tally and the map together are wider than the viewport, and they were
     landing on top of each other. The scoreline is the one that has to stay
     centred at the top, so the diagnostics move down to the opposite corner
     from the map. */
  #stats {
    top: auto;
    bottom: var(--edge);
    font-size: 11.5px;
    padding: var(--s1) var(--s2);
  }

  #stats .score { font-size: 16px; }
  #minimap { width: 92px; height: 92px; }
  #tally { grid-template-columns: 46px auto 46px; column-gap: var(--s2); padding: var(--s1) var(--s3); }
  #tally .num { font-size: 21px; }
  #tally .who { font-size: 9px; letter-spacing: 0.06em; }

  /* The watch bar shares the bottom edge with the stats, so it sits above it. */
  #specBar { bottom: calc(var(--edge) * 2 + 30px); }
}
