:root {
  --bg: #0d1020;
  --bg-lift: #161b31;
  --bg-lift-2: #1e2440;
  --line: #2a3157;
  --text: #eef1ff;
  --muted: #939bc4;
  --accent: #7c5cff;
  --danger: #ff6b81;
  --radius: 18px;
  --shadow: 0 18px 40px rgb(0 0 0 / 0.45);
  --font: "Segoe UI", system-ui, -apple-system, "Noto Sans", sans-serif;
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  background:
    radial-gradient(1100px 600px at 15% -10%, #2b1e5e 0%, transparent 60%),
    radial-gradient(900px 500px at 100% 0%, #0f3a52 0%, transparent 55%),
    var(--bg);
  color: var(--text);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
}

#app {
  height: 100dvh;
  display: flex;
  flex-direction: column;
}

.muted { color: var(--muted); }
.error { color: var(--danger); min-height: 1.2em; margin: .4rem 0 0; }
.spacer { flex: 1; }
.empty { text-align: center; padding: 3rem; }

.centered {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 1.5rem;
}

/* -- brand ---------------------------------------------------------------- */

.logo {
  font-size: clamp(2rem, 6vw, 3rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  margin: 0;
}
.logo span { color: var(--accent); }
.logo.small { font-size: 1.25rem; }

/* -- cards and forms ------------------------------------------------------ */

.card {
  background: var(--bg-lift);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: clamp(1.25rem, 4vw, 2rem);
  width: min(440px, 100%);
}
.card.plain { box-shadow: none; border: none; background: none; padding: 0; width: auto; }
.card h1, .card h2 { margin: 0 0 .25rem; }

label {
  display: block;
  margin: 1rem 0 0;
  font-size: .9rem;
  color: var(--muted);
}

input, select {
  display: block;
  width: 100%;
  margin-top: .35rem;
  padding: .8rem .9rem;
  font: inherit;
  color: var(--text);
  background: var(--bg-lift-2);
  border: 1px solid var(--line);
  border-radius: 12px;
}
input:focus-visible, select:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

button {
  font: inherit;
  cursor: pointer;
  border-radius: 12px;
  border: 1px solid transparent;
  padding: .75rem 1.1rem;
  color: var(--text);
  background: var(--bg-lift-2);
  transition: transform .08s ease, filter .15s ease;
}
button:active { transform: translateY(1px); }
button.primary {
  background: linear-gradient(180deg, var(--accent), #5a3fd6);
  font-weight: 650;
  width: 100%;
  margin-top: 1.25rem;
  padding: .9rem;
}
button.primary:hover { filter: brightness(1.08); }
button.ghost { background: transparent; border-color: var(--line); }
button.ghost:hover { background: var(--bg-lift-2); }
button.back {
  background: none;
  border: none;
  color: var(--muted);
  padding: 0 0 .75rem;
}

.field { margin-top: 1.15rem; }
.field-label { font-size: .9rem; color: var(--muted); }

.avatar-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
  gap: .4rem;
  margin-top: .45rem;
}
.avatar-option {
  aspect-ratio: 1;
  padding: 0;
  font-size: 1.5rem;
  background: var(--bg-lift-2);
  border: 2px solid transparent;
}
.avatar-option.selected { border-color: var(--accent); background: #2b2360; }

.avatar-group { margin-top: .7rem; }
.avatar-group-label {
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--muted);
}

.color-picker { display: flex; gap: .5rem; margin-top: .45rem; flex-wrap: wrap; }
.color-option {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  padding: 0;
  border: 3px solid transparent;
}
.color-option.selected { border-color: var(--text); }

/* -- signing in ----------------------------------------------------------- */

.auth { width: min(440px, 100%); }
.auth-lead { margin: .35rem 0 1.25rem; }
.card.signin h1.logo,
.card.register h1 { margin-bottom: .1rem; }

.auth-alt {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
  font-size: .92rem;
  flex-wrap: wrap;
}
.auth-alt button { padding: .5rem .9rem; }
.create-account { font-weight: 600; }

.card.newpassword { width: min(500px, 100%); }
button.wide { width: 100%; margin-top: .6rem; }

.shake { animation: shake .35s; }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-9px); }
  40% { transform: translateX(9px); }
  60% { transform: translateX(-5px); }
  80% { transform: translateX(5px); }
}

/* -- shell ---------------------------------------------------------------- */

.shell {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto minmax(0, 1fr);
  grid-template-areas:
    "header"
    "sort"
    "main";
  flex: 1;
  min-height: 0;
}

.shell > header {
  grid-area: header;
  flex-shrink: 0;
}

/* main is the games grid itself (main.games); leave its display:grid and
   overflow:auto to the .games rule -- do NOT set display here or it clobbers
   the gallery into a squished flex row. */
.shell > main {
  grid-area: main;
  min-height: 0;
}

/* The shelf's own toolbar: how the gallery is sorted on one side, and the way
 * to ask for a game it does not have on the other. A row of its own between
 * the header and the scrolling grid.
 *
 * It needs the named area or it does not work at all: `.shell` is a grid with
 * exactly three areas, so a child with no `grid-area` is auto-placed into an
 * implicit row *after* main -- which put the sort buttons at the bottom of the
 * window, underneath the chat button, where they could not be read.
 *
 * Outside `.games` rather than inside it, because `.games` is the scroll
 * container. A hundred games is a lot of scrolling, and a control you have to
 * scroll back up to reach is one you stop using. Here it simply never moves.
 */
.gallery-bar {
  grid-area: sort;
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  padding: clamp(.75rem, 2vw, 1rem) clamp(1rem, 4vw, 2rem) 0;
}
.gallery-bar .game-sort { margin: 0; }
/* The two shelf buttons travel together, so a narrow window drops the pair
   onto a line of their own rather than stranding one of them beside the sort
   tabs. They may still split from each other on a phone -- wrapping is what
   the row does; overflowing off the side is not. */
.gallery-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: .6rem;
}
.suggest-game, .whats-new { font-size: .9rem; padding: .5rem .9rem; }
.whats-new { position: relative; display: flex; align-items: center; gap: .35rem; }

/* The grid's own top padding would double up under the toolbar. */
.gallery-bar + .games { padding-top: clamp(.75rem, 2vw, 1rem); }

.topbar {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: 1rem clamp(1rem, 4vw, 2rem) 1rem;
  border-bottom: 1px solid var(--line);
}

.coins {
  background: var(--bg-lift);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .45rem .9rem;
  font-size: .95rem;
}

.who {
  display: flex;
  align-items: center;
  gap: .55rem;
  background: var(--bg-lift);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .3rem .9rem .3rem .3rem;
}

.games {
  flex: 1;
  overflow: auto;
  padding: 2rem clamp(1rem, 4vw, 2rem) 2rem clamp(1rem, 4vw, 2rem);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  /* Size rows to the card, not to its collapsed minimum. game-card has
     overflow:hidden, whose automatic min-size is 0, so plain `auto` rows let a
     card collapse to a ~28px strip (its clipped min-content) inside a
     definite-height grid area -- which is what crushed the whole gallery on
     mobile. max-content keeps every row as tall as its card. */
  grid-auto-rows: max-content;
  gap: 1.25rem;
  align-content: start;
}

.game-card {
  display: flex;
  flex-direction: column;
  background: var(--bg-lift);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
}
.game-art {
  display: grid;
  place-items: center;
  height: 140px;
  font-size: 4rem;
  background:
    radial-gradient(circle at 50% 120%, color-mix(in srgb, var(--accent) 55%, transparent), transparent 70%),
    var(--bg-lift-2);
}
.game-body { padding: 1.1rem 1.25rem 1.25rem; display: flex; flex-direction: column; gap: .4rem; flex: 1; }
.game-body h2 { margin: 0; font-size: 1.3rem; }
.game-body .muted { margin: 0; font-size: .92rem; }
.game-stats {
  font-size: .88rem;
  color: var(--muted);
  margin-top: .25rem;
  display: grid;
  gap: .1rem;
}
/* The holder is the point of the leaderboard, so it gets the accent the card
   is already themed with rather than the muted grey of your own best. */
.game-leader { color: var(--accent); font-weight: 600; }
/* Which contest the numbers above belong to. Quieter than the leader line and
   above it in reading order only when it is shown at all -- see seasonLine(). */
.game-season { font-variant-numeric: tabular-nums; }

/* Play takes the whole width; Leaderboard and Hall of Fame share the row
   underneath. A grid rather than a flex row with a wrapper div: the card has
   one row of actions, and the only thing that varies is that one of them is
   twice as wide as the others. */
.game-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: .6rem;
  margin-top: auto;
  padding-top: .9rem;
}
.game-actions .primary { margin: 0; grid-column: 1 / -1; }
.game-actions .ghost { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* -- ratings -------------------------------------------------------------- */

/* A star is never `.empty`. That is a global utility a few lines up
   (`text-align: center; padding: 3rem`), and a star wearing it grew three rems
   of padding on every side -- which spread five of them across the whole width
   of a card and pushed the number off the end. `off` says the same thing and
   collides with nothing. */
.stars {
  display: inline-flex;
  align-items: center;
  gap: .04em;
  line-height: 1;
  white-space: nowrap;
}
.star { color: var(--star-off, #39406b); }
/* A rating is the card's own verdict on itself, so it wears the card's accent
   rather than a global gold -- the same reasoning as the leader line above. */
.star.on { color: var(--accent); }
.star.part {
  background: linear-gradient(90deg, var(--accent) 50%, var(--star-off, #39406b) 50%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.stars.big { font-size: 1.5rem; }

/* -- the badge on a card -------------------------------------------------- */

.game-art { position: relative; }

.rating-badge {
  position: absolute;
  top: .6rem;
  left: .6rem;
  display: flex;
  align-items: center;
  gap: .35rem;
  margin: 0;
  padding: .3rem .6rem;
  font: inherit;
  font-size: .95rem;
  line-height: 1;
  /* The artwork behind it is a different colour on every card, so the badge
     brings its own ground rather than trusting whatever it lands on. */
  background: rgb(10 13 28 / 0.72);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--text);
  backdrop-filter: blur(3px);
  cursor: pointer;
}
button.rating-badge:hover { border-color: var(--accent); }
.rating-average { font-weight: 700; font-variant-numeric: tabular-nums; }
.rating-count { font-size: .78rem; color: var(--muted); }
.rating-count::before { content: '('; }
.rating-count::after { content: ')'; }
.rating-badge .stars { font-size: .95rem; }
.rating-none { font-size: .78rem; color: var(--muted); }
.rating-badge.unrated .stars { opacity: .75; }

/* Locked is a state, not a button: nothing happens if you press it, so it does
   not look like anything you could press. */
.rating-badge.locked {
  cursor: default;
  font-size: .78rem;
  color: var(--muted);
  backdrop-filter: blur(3px);
}

/* -- the stars in the play bar -------------------------------------------- */

/* The one place a rating is given: in the game, next to its name, while you
   have an opinion about it. */
/* Never the thing that gives way. The stars are fixed-size and the title
   beside them is what shrinks, so a narrow bar cannot squeeze five stars
   into the width of one. */
.bar-rating { display: flex; align-items: center; gap: .5rem; flex: none; }
.bar-stars { display: flex; align-items: center; gap: .1rem; }
.bar-star {
  background: none;
  border: none;
  padding: .1rem .1rem;
  font-size: 1.35rem;
  line-height: 1;
  color: var(--star-off, #39406b);
  cursor: pointer;
  transition: transform .08s ease, color .1s ease;
}
.bar-star.on { color: #ffc53d; }
/* The star under the pointer and everything left of it: a rating is a level,
   not a choice between five things, so it fills rather than highlights. */
.bar-star.hot { color: #ffdc7a; transform: scale(1.15); }
.bar-stars.unearned .bar-star { color: var(--line); cursor: help; }
.bar-stars.unearned .bar-star.hot { color: var(--muted); transform: none; }
.bar-stars-note {
  font-size: .78rem;
  color: var(--muted);
  white-space: nowrap;
}

/* -- the breakdown sheet -------------------------------------------------- */

.rating-headline { display: flex; align-items: center; gap: .9rem; margin: .5rem 0 1.2rem; }
.rating-headline p { margin: .2rem 0 0; font-size: .9rem; }
.rating-big { font-size: 2.6rem; line-height: 1; font-variant-numeric: tabular-nums; }

.rating-spread { list-style: none; margin: 0 0 1.2rem; padding: 0; display: grid; gap: .3rem; }
.rating-spread li { display: grid; grid-template-columns: 2.2rem 1fr 1.6rem; align-items: center; gap: .6rem; }
.rating-spread .mine .spread-label { color: var(--accent); font-weight: 700; }
.spread-label { font-size: .82rem; color: var(--muted); }
.spread-track { height: 8px; border-radius: 999px; background: var(--bg-lift-2); overflow: hidden; }
.spread-fill { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.spread-count { font-size: .82rem; color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }

.rating-who h4, .rating-sheet-body h4 { margin: 1.2rem 0 .4rem; }
.rating-hint { font-size: .85rem; margin-top: 1rem; }

button.small { font-size: .82rem; padding: .4rem .7rem; }
button.danger { color: #ff8095; }

/* -- suggestions and bug reports ------------------------------------------ */

.feedback-form textarea,
.feedback-form input {
  width: 100%;
  margin-top: .35rem;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--bg-lift-2);
  color: var(--text);
  padding: .7rem .85rem;
  font: inherit;
  resize: vertical;
}
.feedback-form textarea:focus,
.feedback-form input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.feedback-form .field { display: block; }

.notes { list-style: none; margin: .5rem 0 0; padding: 0; display: grid; gap: .7rem; }
.note {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-lift-2);
  padding: .8rem .95rem;
}
/* Dealt with, but still readable: a done note is history, not litter. */
.note.done { opacity: .58; }
.note-head { display: flex; align-items: center; gap: .5rem; font-size: .8rem; color: var(--muted); }
.note-kind { font-weight: 600; }
.note-game {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .05rem .5rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .74rem;
}
.note-game.house { font-family: inherit; font-style: italic; }
.note-title { margin: .4rem 0 .2rem; font-size: 1rem; }
.note-body { margin: 0; font-size: .9rem; color: var(--muted); white-space: pre-wrap; }
.note-foot { display: flex; align-items: center; gap: .5rem; margin-top: .7rem; font-size: .82rem; }
.note-status { color: var(--accent); font-weight: 600; }
.note-actions { display: flex; gap: .4rem; }

/* -- what's new ------------------------------------------------------------ */

/* The log is read top to bottom in one scroll, so the day is a heading that
   sticks to the top of the sheet as you pass through it -- otherwise "which
   day am I in" is a question you answer by scrolling back up. */
.update-day { margin-top: 1.5rem; }
.update-day:first-child { margin-top: 1rem; }
.update-date {
  position: sticky;
  top: -1.5rem;                 /* cancels the sheet's own padding */
  z-index: 1;
  margin: 0 0 .6rem;
  padding: .35rem 0;
  background: var(--bg-lift);
  color: var(--muted);
  font-size: .78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .09em;
}

.update-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .7rem; }
.update {
  background: var(--bg-lift-2);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: .8rem .9rem;
}
/* An entry the reader has not seen keeps the accent down its edge rather than
   colouring the whole card: forty of them all lit up is a wall, and the point
   is to find the few that are new. */
.update.fresh { border-left: 3px solid var(--accent); }

.update-head { display: flex; align-items: center; gap: .5rem; font-size: .78rem; color: var(--muted); }
.update-kind {
  border-radius: 999px;
  padding: .12rem .5rem;
  font-weight: 700;
  letter-spacing: .04em;
  background: var(--bg-lift);
  border: 1px solid var(--line);
}
.update-kind[data-kind="new"] { color: #a78bfa; border-color: #4c3a94; }
.update-kind[data-kind="better"] { color: #6fe3c0; border-color: #2c6b5b; }
.update-kind[data-kind="fixed"] { color: #ffc46b; border-color: #7a5a24; }
.update-area { font-weight: 600; color: var(--text); }
.update-new { color: var(--accent); font-weight: 700; white-space: nowrap; }

.update-title { margin: .45rem 0 .25rem; font-size: 1rem; }
.update-body { margin: 0; font-size: .9rem; line-height: 1.5; color: var(--muted); }

.report-button { font-size: .9rem; }
@media (max-width: 640px) {
  /* The play bar belongs to the game, not to the shell. At phone width the
     words go and the icons carry it, rather than the bar wrapping onto a
     second line and stealing a strip of the game. */
  .report-button .label, .play-bar .friends-button .label { display: none; }
  .report-button, .play-bar .friends-button { font-size: 1.05rem; padding: .5rem .65rem; }
}

/* -- sheets --------------------------------------------------------------- */

.overlay {
  position: fixed;
  inset: 0;
  background: rgb(4 6 16 / 0.72);
  display: grid;
  place-items: center;
  padding: 1.5rem;
  z-index: 20;
  backdrop-filter: blur(4px);
}
.sheet {
  position: relative;
  width: min(520px, 100%);
  max-height: 85dvh;
  overflow-y: auto;
  background: var(--bg-lift);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.5rem;
}
.sheet h2 { margin-top: 0; }
.sheet-close {
  position: absolute;
  top: .75rem;
  right: .75rem;
  background: none;
  color: var(--muted);
  padding: .35rem .6rem;
}
.sheet-who { display: flex; align-items: center; gap: 1rem; }
.sheet-who h2 { margin: 0; }
.sheet-who p { margin: .15rem 0 0; }
.coin-line { font-size: 1.15rem; margin: 1.25rem 0; }
.actions { display: flex; gap: .6rem; flex-wrap: wrap; }

.tabs { display: flex; gap: .4rem; margin: 1rem 0; }
.tab { background: transparent; border: 1px solid var(--line); font-size: .9rem; padding: .5rem .9rem; }
.tab.active { background: var(--accent); border-color: var(--accent); }

.board { list-style: none; margin: 0; padding: 0; display: grid; gap: .4rem; }
.board li {
  display: grid;
  grid-template-columns: 2.2rem 32px 1fr auto auto;
  align-items: center;
  gap: .7rem;
  padding: .6rem .7rem;
  border-radius: 12px;
  background: var(--bg-lift-2);
}
.board li.me { outline: 1px solid var(--accent); }
.rank { font-size: 1.1rem; text-align: center; color: var(--muted); }
.board-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board-score { font-variant-numeric: tabular-nums; font-weight: 700; }
.board-score small { color: var(--muted); font-weight: 400; }
.board-plays { font-size: .8rem; color: var(--muted); }

.recent { list-style: none; padding: 0; margin: .5rem 0 0; display: grid; gap: .35rem; font-size: .88rem; }
.recent li { display: flex; align-items: center; gap: .6rem; }
.recent .when { margin-left: auto; }

/* -- hall of fame ---------------------------------------------------------- */

/* A button that has to sit inside a sentence. `.quiet` is the closest thing
   already here and it is still a button-shaped object with padding; in the
   middle of a line of prose that reads as a stray control. */
.linkish {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--accent);
  text-decoration: underline;
}
.linkish:hover { filter: brightness(1.15); }
.linkish:active { transform: none; }

.board-season { margin: -.4rem 0 0; font-size: .9rem; }
.hof-current { margin: 0 0 1.1rem; font-size: .95rem; }
.hof-empty { margin-bottom: 1.4rem; }
.hof-empty p { font-size: .92rem; }
.hof-standings, .hof-season { margin-bottom: 1.4rem; }
.hof h4 { margin: 0 0 .5rem; color: var(--muted); font-size: .82rem;
          text-transform: uppercase; letter-spacing: .08em; }
.hof h4 small { text-transform: none; letter-spacing: 0; }

/* The laurel count is the column the wall is sorted by, so it is the one
   thing on the row allowed to be gold. */
.hof-laurels {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: #ffc53d;
  white-space: nowrap;
}
.hof-laurels.big { font-size: 1.05rem; }
/* Medals ride under the name rather than in a column of their own: three more
   columns for numbers that are usually 1 would squeeze the names to nothing. */
.hof-medals { display: block; font-weight: 400; color: var(--muted); font-size: .78rem; }
.hof-rest { margin: .5rem 0 0; font-size: .85rem; }

/* -- playing -------------------------------------------------------------- */

.play {
  display: flex;
  flex-direction: column;
  height: 100dvh;
}
.play-bar {
  display: flex;
  align-items: center;
  gap: .9rem;
  padding: .6rem clamp(.75rem, 3vw, 1.5rem);
  border-bottom: 1px solid var(--line);
  background: rgb(13 16 32 / 0.9);
}
/* Nothing in the play bar may wrap: it is one row above the game, and a
   button that folds onto a second line steals a strip of the game to do it.
   The title is the only thing here that can be any length, so it is the thing
   that gives way. */
.play-bar > button, .play-bar .coins { white-space: nowrap; }
.play-title { font-weight: 650; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 720px) {
  /* The stars are the reason this bar is worth its height, and the game says
     its own name on the screen below -- so the title is what goes, and every
     gap tightens, rather than the bar overflowing and squashing the one item
     in it that can shrink. */
  .play-title, .bar-stars-note { display: none; }
  .play-bar { gap: .5rem; padding-left: .6rem; padding-right: .6rem; }
  .play-bar > button { padding: .5rem .7rem; }
  .bar-star { font-size: 1.15rem; padding: 0 .05rem; }
}

/* Below this the bar is genuinely out of room, and it gives things up in the
   order they matter least in a game: the coin count (the gallery header has
   it, and earning one raises a toast anyway), then the word "Games" beside the
   arrow. The stars never go -- they are why the bar is worth its height. */
@media (max-width: 480px) {
  .play-bar .coins { display: none; }
}
@media (max-width: 400px) {
  .play-bar .back-label { display: none; }
}
.play-stage { flex: 1; min-height: 0; display: grid; }
.game-frame { border: 0; width: 100%; height: 100%; display: block; }

.toast {
  position: fixed;
  left: 50%;
  bottom: 2rem;
  transform: translateX(-50%);
  background: var(--bg-lift-2);
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: .75rem 1.4rem;
  font-weight: 600;
  box-shadow: var(--shadow);
  z-index: 40;
  animation: rise .25s ease;
}
.toast.out { opacity: 0; transition: opacity .5s ease; }
@keyframes rise {
  from { opacity: 0; transform: translate(-50%, 14px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}

.fatal { text-align: center; }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}


/* -- friends and parties --------------------------------------------------- */

.friends-button { position: relative; display: flex; align-items: center; gap: .35rem; }
.friends-button .badge,
.whats-new .badge {
  display: inline-grid;
  place-items: center;
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--danger);
  color: #2a0a10;
  font-size: .72rem;
  font-weight: 800;
}
/* Unread updates are news, not a summons -- a friend waiting on you is the
   red one. Same pill, the house colour. */
.whats-new .badge { background: var(--accent); color: #f4f1ff; }
.badge.hidden { display: none; }

button.small { padding: .35rem .65rem; font-size: .82rem; }
button.quiet { color: var(--muted); border-color: transparent; }
.small { font-size: .82rem; }

.friend-group { margin-bottom: 1.25rem; }
.friend-group h4 { margin: 0 0 .5rem; color: var(--muted); font-size: .82rem; text-transform: uppercase; letter-spacing: .08em; }

.friend-row {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .5rem .6rem;
  border-radius: 12px;
  background: var(--bg-lift-2);
  margin-bottom: .35rem;
}
.friend-row.away { opacity: .62; }
.friend-name { font-weight: 600; }
/* flex:1 here would split growth with the sibling .spacer 50/50 instead of
   letting .spacer alone push the trailing buttons to the edge, the way the
   old bare name span behaved. */
.friend-row .friend-text {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: .1rem;
}
.friend-playing {
  font-size: .76rem;
  color: var(--good, #2fd4a7);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dot { width: 9px; height: 9px; border-radius: 50%; background: #4b5468; }
.dot.on { background: var(--good, #2fd4a7); box-shadow: 0 0 8px #2fd4a7; }

.find-results { margin-top: .5rem; }

/* -- the party bar --------------------------------------------------------- */

.partybar {
  position: fixed;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  display: none;
  align-items: center;
  gap: .6rem;
  width: min(760px, calc(100% - 1.5rem));
  padding: .5rem .75rem;
  background: var(--bg-lift);
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -8px 30px rgb(0 0 0 / .45);
  z-index: 30;
}
.partybar.shown { display: flex; }
.party-label { color: var(--muted); font-size: .82rem; white-space: nowrap; }
.party-member {
  display: flex;
  align-items: center;
  gap: .35rem;
  padding: .2rem .5rem .2rem .2rem;
  background: var(--bg-lift-2);
  border-radius: 999px;
  font-size: .88rem;
}
.party-member.pending { opacity: .6; }
.party-member em { font-style: normal; color: var(--muted); font-size: .75rem; }
.party-member .crown { font-size: .8rem; }
.kick {
  background: none;
  border: 0;
  color: var(--muted);
  padding: 0 .1rem;
  font-size: .8rem;
  cursor: pointer;
}
.kick:hover { color: var(--danger); }

/* -- invitations ----------------------------------------------------------- */

.invite {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .85rem 1rem;
  background: var(--bg-lift);
  border: 1px solid var(--accent);
  border-radius: 14px;
  box-shadow: var(--shadow);
  z-index: 40;
  animation: rise .25s ease;
}
.invite-actions { display: flex; gap: .4rem; }
.invite .primary { margin: 0; width: auto; }

/* -- narrow screens ------------------------------------------------------- */

@media (max-width: 768px) {
  .shell {
    grid-template-columns: 1fr;
    /* The sort row is repeated here deliberately. This block re-declares the
       whole template, so leaving it out puts the sort buttons back into an
       implicit row below the gallery -- the bug, returning on exactly the
       phones and tablets least likely to get looked at. */
    grid-template-rows: auto auto minmax(0, 1fr);
    grid-template-areas:
      "header"
      "sort"
      "main";
  }
}

/* -- settings -------------------------------------------------------------- */

.shell.settings { grid-template-columns: minmax(0, 1fr); grid-template-areas: "header" "main"; }
.settings-title { font-size: 1.1rem; margin: 0 0 0 .5rem; font-weight: 650; }
.who.static { cursor: default; }

.settings-layout {
  grid-area: main;
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: clamp(1rem, 3vw, 2.5rem);
  padding: clamp(1rem, 3vw, 2rem) clamp(1rem, 4vw, 2rem) 3rem;
  overflow: auto;
  min-height: 0;
}

.settings-nav {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  align-self: start;
  position: sticky;
  top: 0;
}
.settings-tab {
  display: flex;
  align-items: center;
  gap: .6rem;
  text-align: left;
  background: transparent;
  border-color: transparent;
  color: var(--muted);
  padding: .65rem .8rem;
}
.settings-tab:hover { background: var(--bg-lift); color: var(--text); }
.settings-tab.active {
  background: var(--bg-lift);
  border-color: var(--line);
  color: var(--text);
  font-weight: 600;
}
.settings-icon { width: 1.3em; text-align: center; }

.settings-body { min-width: 0; }
.settings-section { max-width: 620px; }
.settings-section > h2 { margin: 0 0 .3rem; }
.settings-section > h2 + .muted { margin: 0 0 1.5rem; }
.settings-form h3 { margin: 2rem 0 0; }

.settings-form {
  margin-top: 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}
.settings-section > .settings-form:first-of-type { border-top: none; }

.settings-actions {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin-top: 1.5rem;
  flex-wrap: wrap;
}
button.primary.inline { width: auto; margin: 0; padding: .75rem 1.4rem; }
button.danger {
  background: linear-gradient(180deg, var(--danger), #c53a51);
  font-weight: 650;
  border: none;
  padding: .8rem 1.4rem;
}
button.danger:hover { filter: brightness(1.08); }

.saved { color: #4ade80; font-size: .9rem; margin: 0; }
.saved.hidden, .hidden { display: none; }

.settings-facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: .55rem 1.25rem;
  margin: 0;
  padding: 1rem 1.1rem;
  background: var(--bg-lift);
  border: 1px solid var(--line);
  border-radius: 14px;
}
.settings-facts dt { color: var(--muted); font-size: .9rem; }
.settings-facts dd { margin: 0; }
code {
  background: var(--bg-lift-2);
  border-radius: 6px;
  padding: .1rem .4rem;
  font-size: .92em;
}

/* -- profile picture ------------------------------------------------------- */

.picture-row {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
}
.picture-actions { display: grid; gap: .5rem; justify-items: start; }
.picture-button {
  display: inline-block;
  margin: 0;
  padding: .7rem 1.1rem;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--bg-lift-2);
  color: var(--text);
  font-size: .95rem;
  cursor: pointer;
}
.picture-button:hover { filter: brightness(1.1); }
/* The real input stays inside the label, so a click anywhere on it opens the
   file picker without the label needing to know the input's id. */
.picture-file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

/* -- toggles --------------------------------------------------------------- */

.toggle-row {
  display: flex;
  align-items: flex-start;
  gap: .85rem;
  margin: 0 0 1rem;
  padding: .9rem 1rem;
  background: var(--bg-lift);
  border: 1px solid var(--line);
  border-radius: 14px;
  cursor: pointer;
}
.toggle-row input {
  width: 20px;
  height: 20px;
  margin: .15rem 0 0;
  flex: 0 0 auto;
  accent-color: var(--accent);
}
.toggle-text { display: grid; gap: .15rem; }
.toggle-label { font-weight: 600; }

.danger-box {
  border: 1px solid var(--danger);
  border-radius: 16px;
  padding: 1.25rem;
  background: color-mix(in srgb, var(--danger) 8%, transparent);
}
.danger-box p { margin-top: 0; }

.admin-list { display: grid; gap: .45rem; margin-bottom: 1rem; }

/* -- whisper notifications ------------------------------------------------- */

.whisper-toast {
  position: fixed;
  right: 1.25rem;
  top: 1.25rem;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: .75rem;
  width: min(340px, calc(100vw - 2.5rem));
  padding: .8rem .9rem;
  text-align: left;
  background: var(--bg-lift);
  border: 1px solid var(--tile, var(--accent));
  border-radius: 16px;
  box-shadow: var(--shadow);
  animation: drop .25s ease;
  transition: opacity .55s ease, transform .55s ease;
}
@keyframes drop {
  from { opacity: 0; transform: translateY(-12px); }
  to { opacity: 1; transform: none; }
}
.whisper-toast:hover { filter: brightness(1.1); }
.whisper-toast.out { opacity: 0; transform: translateY(-10px); }
.whisper-body { display: grid; gap: .1rem; min-width: 0; }
.whisper-from { font-weight: 700; font-size: .92rem; }
.whisper-text {
  color: var(--muted);
  font-size: .88rem;
  /* Two lines at most: a card that grows with the message is a card that
     covers whatever is being played. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.whisper-mark { margin-left: auto; font-size: 1.1rem; }

@media (max-width: 768px) {
  .settings-layout { grid-template-columns: minmax(0, 1fr); }
  .settings-nav {
    position: static;
    flex-direction: row;
    overflow-x: auto;
    padding-bottom: .35rem;
  }
  .settings-tab { white-space: nowrap; }
}
/* -- the chat window ------------------------------------------------------- */

/* Bottom left, above everything, and out of the way of the party bar (bottom
   centre) and party invites (bottom right). */
.chat-widget {
  position: fixed;
  left: clamp(.5rem, 1.5vw, 1rem);
  bottom: clamp(.5rem, 1.5vw, 1rem);
  z-index: 45;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .5rem;
  pointer-events: none;
}
.chat-widget > * { pointer-events: auto; }

.chat-fab {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .5rem .9rem .5rem .75rem;
  border-radius: 999px;
  background: var(--bg-lift);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  font-weight: 600;
  font-size: .9rem;
}
.chat-fab:hover { border-color: var(--accent); }
.chat-fab.open { border-color: var(--accent); background: var(--bg-lift-2); }
.chat-fab-icon { font-size: 1.05rem; line-height: 1; }
.chat-fab-badge {
  display: inline-grid;
  place-items: center;
  min-width: 19px;
  height: 19px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--danger);
  color: #2a0a10;
  font-size: .72rem;
  font-weight: 800;
}
/* An unread whisper has to carry from the middle of a game, where nobody is
   looking at the corner of the screen. */
.chat-fab.has-unread {
  border-color: var(--danger);
  animation: nudge 1.8s ease-in-out infinite;
}
@keyframes nudge {
  0%, 70%, 100% { transform: translateY(0); }
  80% { transform: translateY(-4px); }
  90% { transform: translateY(-1px); }
}

/* Landscape, not a column: chat is read a line at a time, and a wide box fits
   a whole sentence on one of them. */
.chat-window {
  display: none;
  flex-direction: column;
  order: -1;                       /* above the launcher, not below it */
  width: min(660px, calc(100vw - 1.5rem));
  height: min(300px, 45vh);
  background: color-mix(in srgb, var(--bg-lift) 96%, transparent);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--shadow);
  overflow: hidden;
  animation: pop .16s ease;
}
.chat-widget.open .chat-window { display: flex; }
@keyframes pop {
  from { opacity: 0; transform: translateY(8px) scale(.99); }
  to { opacity: 1; transform: none; }
}

/* -- tab strip ------------------------------------------------------------- */

.chat-bar {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .3rem .35rem .3rem .4rem;
  border-bottom: 1px solid var(--line);
  background: var(--bg-lift-2);
  flex-shrink: 0;
}

.chat-tabs {
  display: flex;
  align-items: center;
  gap: .2rem;
  flex: 1;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.chat-tabs::-webkit-scrollbar { display: none; }

.chat-tab {
  display: flex;
  align-items: center;
  padding: 0;
  border-radius: 8px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--muted);
  font-size: .78rem;
  font-weight: 600;
  white-space: nowrap;
}
button.chat-tab { padding: .28rem .6rem; }
.chat-tab:hover { color: var(--text); background: var(--bg-lift); }
.chat-tab.active {
  background: var(--bg-lift);
  border-color: var(--line);
  color: var(--text);
}
.chat-tab-name, .chat-tab-close {
  background: transparent;
  border: none;
  color: inherit;
  font: inherit;
  padding: .28rem .1rem .28rem .55rem;
  border-radius: 8px;
}
.chat-tab-close { padding: .28rem .5rem .28rem .35rem; opacity: .55; }
.chat-tab-close:hover { opacity: 1; color: var(--danger); }
/* A tab with something waiting in it has to be findable at a glance, which is
   the whole reason for putting whispers in tabs in the first place. */
.chat-tab.has-unread {
  color: var(--text);
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 16%, transparent);
}
.chat-tab-count {
  display: inline-grid;
  place-items: center;
  min-width: 16px;
  height: 16px;
  margin-left: .35rem;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--danger);
  color: #2a0a10;
  font-size: .66rem;
  font-weight: 800;
}

.chat-tabs-toggle {
  display: flex;
  align-items: center;
  gap: .35rem;
  margin: 0;
  font-size: .72rem;
  color: var(--muted);
  white-space: nowrap;
  cursor: pointer;
}
.chat-tabs-toggle input {
  width: 14px;
  height: 14px;
  margin: 0;
  accent-color: var(--accent);
}
.chat-tabs-toggle:hover { color: var(--text); }

.chat-minimise {
  background: transparent;
  border-color: transparent;
  color: var(--muted);
  padding: .1rem .5rem;
  font-size: 1rem;
  line-height: 1;
}
.chat-minimise:hover { background: var(--bg-lift); color: var(--text); }

/* -- the log --------------------------------------------------------------- */

/* Dense on purpose. This is a transcript, not a message thread: the useful
   number here is how many lines fit, not how comfortable one of them looks. */
.chat-log {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: .35rem .5rem;
  font-size: .78rem;
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
}
.chat-log-empty { color: var(--muted); font-size: .78rem; margin: .5rem .25rem; }

.chat-line {
  padding: 0;
  margin: 0;
  overflow-wrap: anywhere;
}
.chat-time { color: #6b7398; margin-right: .3rem; }
.chat-tag { margin-right: .3rem; font-weight: 600; }

/* One colour per channel, so which channel a line came from is readable
   without stopping to look at the tag. */
.chat-line.global .chat-tag, .chat-line.global .chat-text { color: #ffd479; }
.chat-line.party .chat-tag, .chat-line.party .chat-text { color: #7fb6ff; }
.chat-line.whisper .chat-tag, .chat-line.whisper .chat-text { color: #ff9ff0; }
.chat-line.system .chat-text { color: var(--muted); font-style: italic; }

.chat-name {
  padding: 0;
  margin-right: .25rem;
  border: none;
  background: none;
  font: inherit;
  font-weight: 600;
  color: var(--tile, var(--accent));
  border-radius: 4px;
}
.chat-name:hover { text-decoration: underline; }

/* -- the entry line -------------------------------------------------------- */

.chat-entry {
  display: flex;
  align-items: stretch;
  gap: .35rem;
  padding: .35rem;
  border-top: 1px solid var(--line);
  flex-shrink: 0;
}

/* The channel your next line goes to, said in front of the box you type it in
   -- the same place the words will appear, so it is read on the way past. */
.chat-chip {
  display: flex;
  align-items: center;
  padding: 0 .6rem;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--bg-lift-2);
  font-size: .74rem;
  font-weight: 700;
  white-space: nowrap;
  max-width: 40%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chat-chip.global { color: #ffd479; border-color: color-mix(in srgb, #ffd479 45%, transparent); }
.chat-chip.party { color: #7fb6ff; border-color: color-mix(in srgb, #7fb6ff 45%, transparent); }
.chat-chip.whisper { color: #ff9ff0; border-color: color-mix(in srgb, #ff9ff0 45%, transparent); }

.chat-input {
  flex: 1;
  min-width: 0;
  margin: 0;
  padding: .45rem .6rem;
  background: var(--bg-lift-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text);
  font: inherit;
  font-size: .85rem;
}
.chat-input:focus { border-color: var(--accent); outline: none; }
.chat-send {
  padding: .45rem .9rem;
  background: var(--accent);
  border: none;
  border-radius: 8px;
  font-weight: 600;
  font-size: .82rem;
  white-space: nowrap;
}
.chat-send:hover { filter: brightness(1.08); }

@media (max-width: 720px) {
  .chat-widget { right: clamp(.5rem, 1.5vw, 1rem); }
  .chat-window { width: 100%; height: min(50vh, 320px); }
  .chat-tabs-toggle span { display: none; }
  .chat-tabs-toggle::after { content: 'Tabs'; }
}

/* -- between seasons ---------------------------------------------------------
 * What stands in for a game while its next season is being installed. See
 * public/shell/between-seasons.mjs. */
.between-seasons {
  flex: 1;
  display: grid;
  place-items: center;
  padding: 24px;
}
.between-seasons-card {
  max-width: 460px;
  text-align: center;
  padding: 28px 32px;
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
}
.between-seasons-icon { font-size: 44px; line-height: 1; margin-bottom: 8px; }
.between-seasons-card h2 { margin: 0 0 12px; }
.between-seasons-card p { line-height: 1.5; margin: 0 0 10px; }
