﻿/* Self-hosted from Google Fonts, latin, latin-ext and cyrillic. Every face is OFL 1.1; see wwwroot/fonts.
   Optional rather than swap: the files a page's language needs are preloaded and arrive before the first
   paint, and one that misses it is used from the next page on instead of re-laying out this one under the
   reader. Fredoka has no Cyrillic, so its Cyrillic range is Nunito, the nearest rounded face that does. */
@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 500 600;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/fredoka-500-600-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 500 600;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/fredoka-500-600-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Fredoka';
  font-style: normal;
  font-weight: 500 600;
  font-display: optional;
  src: url("/fonts/nunito-500-600-cyrillic.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/nunito-sans-400-700-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/nunito-sans-400-700-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/nunito-sans-400-700-cyrillic.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

:root {
  /* Each table's centre, named apart from the theme in force so a swatch can show one it is not on.
     No brighter: --cream on it is body text at 4.6:1, and every theme's centre has to carry that. */
  --theme-baize: #2A7F53;
  --theme-midnight: #2C5FA8;
  --theme-claret: #8C2F4A;

  --table-center: var(--theme-baize);
  --table-edge: #0B3623;
  --rail: #0A2E1D;
  --game-blackjack: #C8102E;
  --game-solitaire: #1D6FB8;
  --game-mahjong: #7A2F8F;
  --game-words: #0F6F6C;
  --game-sudoku: #9C4A17;
  --game-ludo: #B33A7A;
  --game-flags: #4F46A6;
  --game-tanks: #4A5D23;
  --game-hearts: #8A1C3A;
  --game-spades: #1F3A5F;
  --game-belote: #2D6A3E;
  --game-dominoes: #34302C;
  --game-gin-rummy: #7A5B0B;
  --game-bingo: #B04E00;
  --game-checkers: #9A2F1F;
  --game-backgammon: #0E6884;
  --game-chess: #5A4632;
  --game-reversi: #3D6B1A;
  --game-tic-tac-toe: #3E5C7A;
  /* The baize every game picture was captured on, whichever table the reader has chosen: it holds a
     picture's place while it loads, so the band does not flash another colour before the board. */
  --capture-felt: #1F5F43;

  /* A player's avatar ground, picked by their name. Deliberately not the game colours: those name a
     game and nothing else, and a person is not a game. All five carry --cream above 5:1. */
  --avatar-1: #8A3B2E;
  --avatar-2: #2E5F7A;
  --avatar-3: #5B4A96;
  --avatar-4: #2F6B4A;
  --avatar-5: #8A5A14;

  /* The leaderboard's first three. A medal is a disc with the place printed on it, so the colour is
     decoration over a number that already says the same thing; what these have to carry is --ink,
     which all three do above 5:1. */
  --medal-gold: #D9A520;
  --medal-silver: #A2A8B4;
  --medal-bronze: #C4825A;

  /* What refuses or destroys: errors, a wrong answer, a conflict, the destructive button. The same red
     as Blackjack's colour, but named for what it says, so a game colour only ever names its game. */
  --danger: #C8102E;
  --danger-edge: #8E0B20;

  --accent: #F5B335;
  --accent-edge: #BE7F12;
  --accent-ink: #2B1D05;

  --panel: #FFFDF9;
  --panel-edge: #D9D2C4;
  --ink: #1B2420;
  --ink-soft: #5E6B63;
  --field: #FFFFFF;

  --cream: #FFF6E3;
  --cream-soft: rgba(255, 246, 227, 0.72);
  --focus-ring: #FFF6E3;
  --scrollbar-thumb: rgba(255, 246, 227, 0.3);
  /* A hairline drawn on the rail: the edge under the header, and the seams between the menu's rows. */
  --rail-line: rgba(255, 246, 227, 0.14);

  --display: "Fredoka", system-ui, sans-serif;
  --body: "Nunito Sans", system-ui, sans-serif;

  --radius-control: 0.75rem;
  --radius-panel: 1.125rem;

  /* Two elevations: a panel resting, and the same panel lifted under the pointer. A menu hung off the
     rail is the rail carried on down, so it casts a shadow without the hard edge of an object. */
  --shadow-panel: 0 0.375rem 0 rgba(0, 0, 0, 0.2), 0 1rem 2rem rgba(0, 0, 0, 0.25);
  --shadow-panel-lifted: 0 0.5625rem 0 rgba(0, 0, 0, 0.2), 0 1.25rem 2.25rem rgba(0, 0, 0, 0.28);
  --shadow-rail: 0 1rem 2rem rgba(0, 0, 0, 0.25);
  --backdrop: rgba(0, 0, 0, 0.45);

  --duration-press: 80ms;
  --duration-hover: 120ms;
  --duration-menu: 160ms;
  --duration-panel: 240ms;
  --ease-panel: cubic-bezier(0.2, 0, 0.2, 1);
  /* Fast out and a long settle: a card or tile arriving on the table. */
  --ease-settle: cubic-bezier(0.22, 1, 0.36, 1);

  /* Named layers, lowest first. Numbers under 10 only order siblings inside one component. */
  --layer-bubble: 30;
  --layer-notice: 40;
  --layer-picker: 45;
  --layer-stock: 60;
  --layer-popover: 70;
  /* Over a resting pile, whose cards stack up to 100, and under a card in flight at 200. */
  --layer-pile-mark: 150;
  --layer-menu: 200;
  --layer-targets: 400;
  --layer-focus: 500;
  --layer-float: 600;
  --layer-seat-tag: 650;
  --layer-overlay: 700;
  --layer-over-overlay: 800;
  --layer-consent: 900;

  /* The size a game's board is drawn in, solved per game and set inline on its root; this is only
     what a game that has not mounted yet uses. */
  --game-unit: 1rem;

  --shell-width: 70rem;
  --shell-gutter: 1.5rem;
  --site-header-height: 2.5rem;
}

[data-theme="midnight"] {
  --table-center: var(--theme-midnight);
  --table-edge: #0C1E3D;
  --rail: #0A1830;
}

[data-theme="claret"] {
  --table-center: var(--theme-claret);
  --table-edge: #3A0F1E;
  --rail: #300B18;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  color-scheme: light;
  scrollbar-color: var(--scrollbar-thumb) var(--table-edge);
  scrollbar-gutter: stable;
  min-height: 100%;
  background: var(--table-edge);
  background-image: radial-gradient(ellipse 120% 90% at 50% -10%, var(--table-center), var(--table-edge) 70%);
  font-family: var(--body);
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

body {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  margin: 0;
  background: transparent;
  color: var(--cream);
  font-size: 1rem;
  line-height: 1.55;
  font-variant-numeric: tabular-nums;
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  opacity: 0.06;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22%3E%3Cfilter id=%22n%22%3E%3CfeTurbulence type=%22fractalNoise%22 baseFrequency=%220.9%22 numOctaves=%223%22/%3E%3C/filter%3E%3Crect width=%22100%25%22 height=%22100%25%22 filter=%22url(%23n)%22/%3E%3C/svg%3E");
}

.site-header,
.site-main,
.site-footer {
  position: relative;
  z-index: 1;
}

/* One rung above the other two, which are its equals only until something hangs out of it: a menu
   panel is trapped in this stacking context, and a tie would let the page it covers paint over it. */
.site-header {
  z-index: 2;
}

h1,
h2,
h3 {
  margin: 0 0 0.4em;
  font-family: var(--display);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.015em;
}

h1 {
  font-size: clamp(2rem, 5vw, 2.625rem);
}

h2 {
  font-size: 1.5rem;
}

h3 {
  font-size: 1.1875rem;
}

p {
  max-width: 60ch;
  margin: 0 0 1rem;
}

/* Full cream: it is read, not glanced at, and --cream-soft on the lit centre of the felt is 3.2:1. */
.lede {
  font-size: 1.1875rem;
  text-wrap: balance;
  color: var(--cream);
}

.page-intro {
  max-width: 90ch;
}

a {
  color: inherit;
  text-underline-offset: 0.15em;
}

:focus-visible {
  outline: 0.1875rem solid var(--focus-ring);
  outline-offset: 0.125rem;
  border-radius: 0.25rem;
}

.shell {
  width: 100%;
  max-width: var(--shell-width);
  margin-inline: auto;
  padding-inline: var(--shell-gutter);
}

.icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* A game's picture from the sprite, at the size of the type around it. */
.game-icon {
  width: 1em;
  height: 1em;
}

.visually-hidden {
  position: absolute;
  width: 0.0625rem;
  height: 0.0625rem;
  margin: -0.0625rem;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Its height is fractional, so parking it at exactly -100% leaves a rounded pixel row showing
   above the header it paints over. */
.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  padding: 0.5rem 1rem;
  border-radius: 0 0 var(--radius-control) 0;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 700;
  text-decoration: none;
  transform: translateY(calc(-100% - 0.25rem));
}

.skip-link:focus {
  transform: none;
}

.site-header {
  background: var(--rail);
  box-shadow: inset 0 -0.0625rem 0 var(--rail-line);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  height: var(--site-header-height);
}

.site-brand {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  min-width: 0;
}

.wordmark {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.25rem;
  letter-spacing: -0.02em;
  color: var(--cream);
  text-decoration: none;
  white-space: nowrap;
}

.wordmark__mark {
  width: 1.3em;
  height: 1.3em;
  margin-right: 0.3em;
  vertical-align: -0.3em;
}

.wordmark__try {
  color: var(--accent);
}

.site-brand__game {
  overflow: hidden;
  font-family: var(--display);
  font-weight: 600;
  font-size: 1rem;
  color: var(--cream-soft);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.site-brand__game::before {
  content: "/";
  margin-right: 0.5rem;
  opacity: 0.5;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  font-weight: 600;
}

.site-nav a {
  color: var(--cream-soft);
  text-decoration: none;
}

.site-nav a:hover {
  color: var(--cream);
}

/* Logging out is a form, so it is a button; in the header it reads as one more link beside the name. */
.site-nav .button--quiet {
  padding: 0;
  font: inherit;
}

.site-nav .button--quiet:hover {
  text-decoration: none;
}

/* Where the reader is: full cream and a bar under the word, so it is not told by colour alone. The
   bar is the accent, which on the rail is a mark rather than a control. */
.site-nav > a[aria-current],
.games-menu__toggle[aria-current] {
  color: var(--cream);
  text-decoration: underline 0.1875rem var(--accent);
  text-underline-offset: 0.5rem;
}

/* The games, from every page's header: the toggle reads as one more nav link, and the panel is the
   rail carrying on under it, because it is the site's navigation rather than a card of one game's. */
.games-menu {
  position: relative;
}

.games-menu__toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--cream-soft);
  cursor: pointer;
  list-style: none;
}

.games-menu__toggle::-webkit-details-marker {
  display: none;
}

.games-menu__chevron {
  width: 0.875rem;
  height: 0.5rem;
  will-change: rotate;
}

@media (prefers-reduced-motion: no-preference) {
  .games-menu__chevron {
    transition: rotate var(--duration-menu) var(--ease-panel);
  }
}

.games-menu[open] .games-menu__chevron {
  rotate: 180deg;
}

.games-menu__toggle:hover,
.games-menu[open] .games-menu__toggle {
  color: var(--cream);
}

/* Hung from the header's bottom edge: the toggle is centred in the header, so half the height it
   leaves over is the distance down to it. */
.games-menu__panel {
  position: absolute;
  top: calc(100% + (var(--site-header-height) - 100%) / 2);
  right: -1rem;
  z-index: var(--layer-menu);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.25rem;
  width: 24rem;
  margin: 0;
  padding: 0.75rem;
  border-radius: 0 0 var(--radius-panel) var(--radius-panel);
  background: var(--rail);
  box-shadow: var(--shadow-rail);
  list-style: none;
}

@media (prefers-reduced-motion: no-preference) {
  .games-menu__panel {
    transition: opacity var(--duration-menu) ease, translate var(--duration-menu) ease;
  }

  @starting-style {
    .games-menu[open] .games-menu__panel {
      opacity: 0;
      translate: 0 -0.375rem;
    }
  }
}

.site-nav .games-menu__game {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem;
  border-radius: var(--radius-control);
  color: var(--cream);
  font-family: var(--display);
  font-size: 1.0625rem;
}

.site-nav .games-menu__game:hover {
  background: rgba(255, 246, 227, 0.06);
}

.games-menu__game[aria-current] {
  background: rgba(255, 246, 227, 0.06);
  text-decoration: underline 0.1875rem var(--accent);
  text-underline-offset: 0.25rem;
}

/* The kinds of game, over the games themselves: a way to see several at once, so a row of its own
   rather than two more games. */
.games-menu__kinds {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.25rem;
  margin-bottom: 0.25rem;
  padding-bottom: 0.5rem;
  border-bottom: 0.0625rem solid var(--rail-line);
}

.site-nav .games-menu__kinds a {
  padding: 0.5rem;
  border-radius: var(--radius-control);
  background: rgba(255, 246, 227, 0.06);
  color: var(--cream);
  font-family: var(--display);
  font-size: 1rem;
  text-align: center;
}

.site-nav .games-menu__kinds a:hover {
  background: rgba(255, 246, 227, 0.12);
}

.games-menu__kinds a[aria-current] {
  text-decoration: underline 0.1875rem var(--accent);
  text-underline-offset: 0.25rem;
}

/* The game's picture set straight on the rail, like a thing on the table. */
.games-menu__icon {
  display: grid;
  place-items: center;
  flex: none;
  font-size: 2rem;
  filter: drop-shadow(0 0.0625rem 0.0625rem rgba(0, 0, 0, 0.35));
}

/* A game's dailies, from the header of its pages: the toggle reads as one more nav link, and the
   panel is a card hung under it rather than the header carrying on, since it is one game's calendar
   and not the site's navigation. */
.daily-menu {
  position: relative;
  margin-left: auto;
}

.daily-menu__toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--cream-soft);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

.daily-menu__toggle::-webkit-details-marker {
  display: none;
}

.daily-menu__toggle:hover,
.daily-menu[open] .daily-menu__toggle {
  color: var(--cream);
}

/* On a daily's own page the toggle is the one thing saying so, so it wears the accent. */
.daily-menu--playing .daily-menu__toggle {
  color: var(--accent);
}

.daily-menu__panel {
  --focus-ring: var(--ink);
  position: absolute;
  top: calc(100% + 0.75rem);
  right: 0;
  z-index: var(--layer-menu);
  width: 21rem;
  padding: 1.25rem;
  border-radius: var(--radius-panel);
  background: var(--panel);
  color: var(--ink);
  box-shadow: var(--shadow-panel);
}

/* The panel is drawn for the first time when the details opens, so its arrival is a starting style:
   it comes down out of the toggle rather than appearing under it. Shutting is the details' own doing
   and stays immediate. */
@media (prefers-reduced-motion: no-preference) {
  .daily-menu__panel {
    transition: opacity var(--duration-menu) ease, translate var(--duration-menu) ease;
  }

  @starting-style {
    .daily-menu[open] .daily-menu__panel {
      opacity: 0;
      translate: 0 -0.375rem;
    }
  }
}

.daily-menu__title {
  margin: 0;
  font-size: 1.1875rem;
}

.daily-menu__month {
  margin-top: 1rem;
}

.daily-menu__month-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.daily-menu__month h3 {
  margin: 0;
  font-size: 1rem;
}

.daily-menu__step {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-control);
  background: none;
  color: var(--ink);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}

.daily-menu__step:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 18%, var(--panel));
}

.daily-menu__step:disabled {
  color: var(--ink-soft);
  cursor: default;
}

.daily-menu__weekdays,
.daily-menu__days {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.daily-menu__weekdays {
  margin-bottom: 0.25rem;
  color: var(--ink-soft);
  font-size: 0.8125rem;
  font-weight: 600;
  text-align: center;
}

/* A day is a square: a link for a day that has a board, plain type for one that has none. */
.daily-menu__day {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  border-radius: var(--radius-control);
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
}

/* A day with no board is recessive, not absent: at the field border's own colour it read as a
   rendering fault on a month that has barely started, and washed halfway to the panel it was still
   1.9:1. It is the secondary ink at 5.5:1, and told from a day that has a board by its lighter weight
   and by having nothing to press, not by colour alone. */
.daily-menu__day--undealt {
  color: var(--ink-soft);
  font-weight: 400;
}

/* Hover is a wash laid over whatever fill the day already has rather than a background of its own,
   so a day keeps its state and only warms: swapping the background put the open day's cream number
   on a near-white ground and took a won day's amber off it. The wash is the accent, except on the
   open day, whose fill is the ink and which therefore lightens instead. */
a.daily-menu__day {
  --day-wash: var(--accent);
}

a.daily-menu__day[aria-current="date"] {
  --day-wash: var(--cream);
}

a.daily-menu__day::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--day-wash);
  opacity: 0;
}

a.daily-menu__day:hover::before {
  opacity: 0.18;
}

@media (prefers-reduced-motion: no-preference) {
  a.daily-menu__day::before {
    transition: opacity var(--duration-hover) ease;
  }
}

/* What happened on a day is what fills it: nothing for a board nobody has touched, a wash of the ink
   for one that was played and not cleared, the accent for one that was won. It was a dot under the
   date before, in a menu that also rings today and fills the day being read, and three marks in
   three languages left the reader working out which of them meant what. Colour is not the only
   signal: the link's label carries the word, and each key in the legend below is the day it names,
   drawn the same way at a smaller size. */
.daily-menu__day--played {
  background: color-mix(in srgb, var(--ink-soft) 20%, var(--panel));
}

.daily-menu__day--won {
  background: color-mix(in srgb, var(--accent) 45%, var(--panel));
}

/* The ring marks today wherever the reader is in the archive, so it is the one mark that is not an
   outcome and the one that has to survive on top of every fill. */
.daily-menu__day--today {
  box-shadow: inset 0 0 0 0.125rem var(--accent-edge);
}

/* The day being read is filled with the ink, which outranks the outcome under it: it answers which
   day is on the table, and that is the one day whose board the reader is already looking at. */
.daily-menu__day[aria-current="date"] {
  background: var(--ink);
  color: var(--cream);
}

.daily-menu__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  margin: 1rem 0 0;
  padding: 0;
  color: var(--ink-soft);
  font-size: 0.8125rem;
  list-style: none;
}

.daily-menu__legend li {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

/* A key is a day at a smaller size, down to the corner it is rounded to, so there is nothing to
   translate between the legend and the calendar above it. */
.daily-menu__key {
  width: 1.125rem;
  height: 1.125rem;
  border-radius: 0.375rem;
}

.daily-menu__key--today {
  box-shadow: inset 0 0 0 0.125rem var(--accent-edge);
}

.daily-menu__key--played {
  background: color-mix(in srgb, var(--ink-soft) 20%, var(--panel));
}

.daily-menu__key--won {
  background: color-mix(in srgb, var(--accent) 45%, var(--panel));
}

/* Where this game's day stands for everyone, from the panel that says where it stands for you. */
.daily-menu__board {
  display: inline-block;
  margin-top: 0.75rem;
  color: var(--ink-soft);
  font-size: 0.9375rem;
  font-weight: 600;
}

.daily-menu__board:hover {
  color: var(--ink);
}

/* The phone's navigation. It is in every page's markup and only the breakpoint decides which of the
   two is drawn; display:none keeps the hidden one out of the accessibility tree as well as the page. */
.site-menu {
  display: none;
  /* So the panel and the rows inside it can slide between nothing and whatever they turn out to hold. */
  interpolate-size: allow-keywords;
}

.site-menu__toggle {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  margin-right: -0.625rem;
  color: var(--cream);
  cursor: pointer;
  list-style: none;
}

.site-menu__toggle::-webkit-details-marker {
  display: none;
}

/* The same control both ways, and one turns into the other rather than replacing it: the outer bars
   swing together into the cross while the middle one goes out from between them, so the way out is
   visibly the way in. Transformed about the icon's own box, so the origin is the middle of the grid
   the bars are drawn on rather than the middle of a path. */
.site-menu__bar {
  transform-box: view-box;
  transform-origin: center;
}

@media (prefers-reduced-motion: no-preference) {
  .site-menu__bar {
    transition: transform var(--duration-panel) var(--ease-panel), opacity var(--duration-hover) ease;
  }
}

.site-menu[open] .site-menu__bar--top {
  transform: translateY(5px) rotate(45deg);
}

.site-menu[open] .site-menu__bar--middle {
  opacity: 0;
}

.site-menu[open] .site-menu__bar--bottom {
  transform: translateY(-5px) rotate(-45deg);
}

/* The header band carrying on down the page rather than a card floating over it, so it runs the full
   width and its rows are full-bleed. Hung off the header, so opening it never moves what is below. */
.site-menu__panel {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: var(--layer-menu);
  block-size: 0;
  overflow: hidden;
  background: var(--rail);
  box-shadow: var(--shadow-rail);
}

@media (prefers-reduced-motion: no-preference) {
  .site-menu__panel {
    transition: block-size var(--duration-panel) var(--ease-panel);
  }
}

.site-menu[open] .site-menu__panel {
  block-size: auto;
}

/* The panel is drawn into a page it was absent from, so the opening frame is spelled out; without it
   the first paint is already the open one and there is nothing to slide from. */
@starting-style {
  .site-menu[open] .site-menu__panel {
    block-size: 0;
  }
}

/* Closing is the same slide backwards, which it only gets if the content outlasts the attribute: the
   panel is hung inside this box, and the browser empties it the moment the menu is no longer open.
   Only the closed state asks for it — a transition is read from the state being moved to, so opening
   keeps the content the moment it is asked for rather than waiting on a step in the middle. */
@media (prefers-reduced-motion: no-preference) {
  .site-menu:not([open])::details-content {
    transition: content-visibility var(--duration-panel) allow-discrete;
  }
}

.site-menu__game > summary,
.site-menu__games a,
.site-menu__links a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-height: 3.25rem;
  padding-inline: var(--shell-gutter);
  border-top: 0.0625rem solid var(--rail-line);
  color: var(--cream);
  font-family: var(--display);
  font-size: 1.0625rem;
  font-weight: 600;
  text-decoration: none;
}

.site-menu__game > summary {
  cursor: pointer;
  list-style: none;
}

.site-menu__game > summary::-webkit-details-marker {
  display: none;
}

/* The one mark that says a row opens instead of going somewhere; it turns to point down when it has. */
.site-menu__game > summary::after {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 0.125rem solid var(--cream-soft);
  border-bottom: 0.125rem solid var(--cream-soft);
  transform: rotate(-45deg);
}

@media (prefers-reduced-motion: no-preference) {
  .site-menu__game > summary::after {
    transition: transform var(--duration-hover) ease;
  }
}

.site-menu__game[open] > summary::after {
  transform: rotate(45deg);
}

/* A game's variants unroll under it the way the panel unrolls under the header, so one row opening
   inside the menu reads as the same gesture as the menu itself. */
.site-menu__game::details-content {
  block-size: 0;
  overflow: hidden;
}

.site-menu__game[open]::details-content {
  block-size: auto;
}

@media (prefers-reduced-motion: no-preference) {
  .site-menu__game::details-content {
    transition: block-size var(--duration-panel) ease;
  }

  .site-menu__game:not([open])::details-content {
    transition: block-size var(--duration-panel) ease, content-visibility var(--duration-panel) allow-discrete;
  }
}

.site-menu__game ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A variant is a step inside its game, so its row starts further in and speaks more quietly. */
.site-menu__game a {
  min-height: 2.75rem;
  padding-left: calc(var(--shell-gutter) + 1.25rem);
  color: var(--cream-soft);
  font-size: 1rem;
  font-weight: 400;
}

/* The row being read, marked the way the desktop header marks its link. */
.site-menu__games a[aria-current],
.site-menu__links a[aria-current] {
  color: var(--cream);
  text-decoration: underline 0.1875rem var(--accent);
  text-underline-offset: 0.25rem;
}

.site-menu__games a:hover,
.site-menu__links a:hover,
.site-menu__game > summary:hover {
  background: rgba(255, 246, 227, 0.06);
  color: var(--cream);
}

.site-menu__account {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem var(--shell-gutter) 1.25rem;
}

.site-menu__account .account-name {
  color: var(--cream-soft);
}

/* Logging in is the one thing in the menu worth pressing rather than following, so the partial is
   asked for a real button and it only has to be told how wide to be. */
.site-menu__account .button,
.site-menu__account form {
  flex: 1;
}

.site-main {
  flex: 1;
  padding-block: 2rem 3rem;
}

/* The administrator's one line to every reader, under the header on every page but a game's. It is
   the rail again with the accent mixed into it: part of the header's own ground, so it reads as the
   site speaking rather than as a banner laid over the table, and amber enough to be seen. */
.site-notice {
  background: color-mix(in srgb, var(--accent) 32%, var(--rail));
  box-shadow: inset 0 -0.0625rem 0 var(--rail-line);
  color: var(--cream);
}

.site-notice__text {
  max-width: var(--shell-width);
  margin-block: 0;
  padding-block: 0.625rem;
  font-size: 0.9375rem;
  font-weight: 600;
  text-align: center;
  text-wrap: balance;
}

.site-footer {
  background: var(--rail);
}

.site-footer__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.5rem;
  padding-block: 1.25rem;
  font-size: 0.9375rem;
  color: var(--cream-soft);
}

/* The pages that are read rather than played, and who runs the site, next to the languages they are
   read in. Four links can outrun a phone's width beside the wordmark, so the row wraps. */
.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
}

.site-footer__links a {
  color: var(--cream-soft);
  text-decoration: none;
}

.site-footer__links a:hover {
  color: var(--cream);
  text-decoration: underline;
}

.site-footer__button {
  padding: 0;
  border: 0;
  background: none;
  color: var(--cream-soft);
  font: inherit;
  cursor: pointer;
}

.site-footer__button:hover {
  color: var(--cream);
  text-decoration: underline;
}

/* CookieConsent (wwwroot/lib/cookieconsent) drawn as a panel on the table: its colours, type and
   radii re-pointed at the site's tokens, and its buttons given the controls' hard edge. */
#cc-main {
  --cc-z-index: var(--layer-consent);
  --cc-font-family: var(--body);
  --cc-bg: var(--panel);
  --cc-primary-color: var(--ink);
  --cc-secondary-color: var(--ink-soft);
  --cc-link-color: var(--ink);
  --cc-modal-border-radius: var(--radius-panel);
  --cc-btn-border-radius: var(--radius-control);
  --cc-btn-primary-bg: var(--accent);
  --cc-btn-primary-color: var(--accent-ink);
  --cc-btn-primary-border-color: var(--accent);
  --cc-btn-primary-hover-bg: var(--accent);
  --cc-btn-primary-hover-color: var(--accent-ink);
  --cc-btn-primary-hover-border-color: var(--accent);
  --cc-btn-secondary-bg: var(--accent);
  --cc-btn-secondary-color: var(--accent-ink);
  --cc-btn-secondary-border-color: var(--accent);
  --cc-btn-secondary-hover-bg: var(--accent);
  --cc-btn-secondary-hover-color: var(--accent-ink);
  --cc-btn-secondary-hover-border-color: var(--accent);
  --cc-separator-border-color: var(--panel-edge);
  --cc-toggle-on-bg: var(--accent-edge);
  --cc-toggle-readonly-bg: var(--panel-edge);
  --cc-cookie-category-block-bg: var(--field);
  --cc-cookie-category-block-border: var(--panel-edge);
  --cc-cookie-category-block-hover-bg: var(--field);
  --cc-cookie-category-block-hover-border: var(--panel-edge);
  --cc-footer-bg: var(--panel);
  --cc-footer-border-color: var(--panel-edge);
  --cc-overlay-bg: var(--backdrop);
  --focus-ring: var(--ink);
}

#cc-main :is(.cm, .pm) {
  box-shadow: var(--shadow-panel);
}

#cc-main :is(.cm__title, .pm__title) {
  font-family: var(--display);
  font-weight: 600;
}

#cc-main :is(.cm__btn, .pm__btn) {
  box-shadow: 0 0.25rem 0 var(--accent-edge);
  font-family: var(--display);
  font-weight: 600;
}

#cc-main :is(.cm__btn, .pm__btn):hover {
  filter: brightness(1.06);
}

#cc-main :is(.cm__btn, .pm__btn):active {
  box-shadow: 0 0.0625rem 0 var(--accent-edge);
  transform: translateY(0.1875rem);
}

.language-picker {
  display: flex;
  gap: 0.75rem;
}

.language-picker a {
  color: var(--cream-soft);
  text-decoration: none;
}

/* On a page in a Latin language "Русский" and "Български" are the only Cyrillic, and in the site's face
   they would fetch the whole Cyrillic file at the priority of text being drawn. The system face has the letters. */
html:not(:lang(ru)):not(:lang(bg)) .language-picker a:is(:lang(ru), :lang(bg)) {
  font-family: system-ui, sans-serif;
}

.language-picker a:hover,
.language-picker a[aria-current] {
  color: var(--cream);
}

.theme-picker {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.theme-swatch {
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 0.125rem solid rgba(255, 246, 227, 0.35);
  border-radius: 50%;
  cursor: pointer;
}

.theme-swatch[aria-pressed="true"] {
  border-color: var(--cream);
}

.theme-swatch--baize {
  background: var(--theme-baize);
}

.theme-swatch--midnight {
  background: var(--theme-midnight);
}

.theme-swatch--claret {
  background: var(--theme-claret);
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2.75rem;
  padding-inline: 1.5rem;
  border: 0;
  border-radius: var(--radius-control);
  background: var(--accent);
  box-shadow: 0 0.25rem 0 var(--accent-edge);
  color: var(--accent-ink);
  font-family: var(--display);
  font-size: 1.0625rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

.button[hidden] {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  .button {
    transition: transform var(--duration-press) ease, box-shadow var(--duration-press) ease, filter var(--duration-press) ease;
  }
}

.button:hover {
  filter: brightness(1.06);
}

.button:active {
  transform: translateY(0.1875rem);
  box-shadow: 0 0.0625rem 0 var(--accent-edge);
}

.button--secondary {
  background: var(--panel);
  box-shadow: 0 0.25rem 0 var(--panel-edge);
  color: var(--ink);
}

.button--secondary:active {
  box-shadow: 0 0.0625rem 0 var(--panel-edge);
}

.button--quiet {
  height: auto;
  padding: 0.25rem;
  background: transparent;
  box-shadow: none;
  color: var(--cream-soft);
}

.button--quiet:hover {
  filter: none;
  color: var(--cream);
  text-decoration: underline;
}

/* On the felt a quiet button is cream; on a panel that is a button nobody can see, so it takes the
   panel's ink the way the panel re-points its focus ring. */
.panel .button--quiet {
  color: var(--ink-soft);
}

.panel .button--quiet:hover {
  color: var(--ink);
}

.button--quiet:active {
  transform: none;
  box-shadow: none;
}

.button--destructive {
  background: var(--danger);
  box-shadow: 0 0.25rem 0 var(--danger-edge);
  color: var(--cream);
}

.button--destructive:active {
  box-shadow: 0 0.0625rem 0 var(--danger-edge);
}

/* A button that cannot be pressed has lost its edge and gone flat and grey, rather than faded: a
   translucent fill over the felt took the label down to 2.5:1 with it. --panel-edge stands off every
   theme's felt at 3.2:1 or more and carries the ink at 10.6:1. On a panel that grey is the panel's own
   hairline, so there the button is its dashed outline round a label in the secondary ink, 5.5:1.
   Flat grey alone read as a pressed toggle, so off a panel it wears the same dashes on its own edge. */
.button:disabled,
.button[aria-disabled="true"] {
  background: var(--panel-edge);
  box-shadow: none;
  color: var(--ink);
  cursor: not-allowed;
  filter: none;
}

.button:is(:disabled, [aria-disabled="true"]):not(.panel *, :focus-visible) {
  outline: 0.125rem dashed var(--ink-soft);
  outline-offset: -0.125rem;
}

.panel .button:disabled,
.panel .button[aria-disabled="true"] {
  border: 0.125rem dashed var(--ink-soft);
  background: transparent;
  color: var(--ink-soft);
}

.button:disabled:active,
.button[aria-disabled="true"]:active {
  transform: none;
}

.panel {
  --focus-ring: var(--ink);
  padding: 1.75rem;
  border-radius: var(--radius-panel);
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  color: var(--ink);
}

/* A row of choices over a list, each an address of its own rather than a control on the page: the
   played games page's games, the leaderboard's window and its game. It lives on a panel, where a
   flat pill is what a thing on a panel is; the same row on the felt would need to be an object. */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.filters a {
  padding: 0.25rem 0.75rem;
  border: 0.0625rem solid var(--panel-edge);
  border-radius: var(--radius-control);
  color: var(--ink-soft);
  font-size: 0.9375rem;
  font-weight: 600;
  text-decoration: none;
}

.filters a:hover {
  color: var(--ink);
}

/* A row with one pill per game is kept to one line that scrolls sideways, fading at the edge it
   continues past. Its own width never widens the panel it sits in, so the row scrolls rather than the
   page, and the padding is room for a pill's focus ring, which the scroll box would clip. The fade
   stands in for a scrollbar, and a swipe settles on a pill's edge rather than halfway through one. */
.filters--scroll {
  flex-wrap: nowrap;
  position: relative;
  contain: inline-size;
  margin: -0.375rem -0.375rem 0.875rem;
  padding: 0.375rem;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

.filters--scroll::-webkit-scrollbar {
  display: none;
}

.filters--scroll a {
  flex: none;
  white-space: nowrap;
}

@media (pointer: coarse) {
  .filters--scroll {
    scroll-snap-type: x proximity;
    scroll-padding-inline: 0.375rem;
  }

  .filters--scroll a {
    scroll-snap-align: start;
  }
}

/* Each fade is a length of its own rather than a mask swapped for another, so one can ease in and out
   as the row reaches an end; a mask that snapped on and off read as the pills under it flickering. */
@property --fade-before {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

@property --fade-after {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

.filters--scroll {
  mask-image: linear-gradient(
    to right,
    transparent,
    #000 var(--fade-before),
    #000 calc(100% - var(--fade-after)),
    transparent);
}

.filters--scroll[data-more-before] {
  --fade-before: 3rem;
}

.filters--scroll[data-more-after] {
  --fade-after: 3rem;
}

/* Only once the row has been measured and drawn, or the fades would ease in as the page opens. */
@media (prefers-reduced-motion: no-preference) {
  .filters--scroll[data-measured] {
    transition: --fade-before 0.2s ease, --fade-after 0.2s ease;
  }
}

/* Wide enough for the pills to take a second line, the row wraps: a mouse has no swipe, and a pill cut
   off at the fade read as the end of the list. */
@media (min-width: 48rem) {
  .filters--scroll {
    flex-wrap: wrap;
    overflow-x: visible;
  }
}

/* The one being read is marked rather than offered, in the wash the settings sidebar marks a place
   with, and it carries the current page state as well so colour is not the only signal. */
.filters a[aria-current="page"] {
  border-color: var(--accent-edge);
  background: color-mix(in srgb, var(--accent) 28%, var(--panel));
  color: var(--accent-ink);
}

/* A player is the same disc wherever the site names them: their initial in the display face on one
   of the five avatar grounds, picked from their account id. What holds it sets the two sizes. */
.avatar {
  --avatar-size: 2.5rem;
  --avatar-face: 1rem;

  display: grid;
  place-items: center;
  flex: none;
  width: var(--avatar-size);
  height: var(--avatar-size);
  border-radius: 50%;
  background: var(--avatar-1);
  color: var(--cream);
  font-family: var(--display);
  font-size: var(--avatar-face);
  font-weight: 600;
  line-height: 1;
}

.avatar[data-tint="2"] { background: var(--avatar-2); }
.avatar[data-tint="3"] { background: var(--avatar-3); }
.avatar[data-tint="4"] { background: var(--avatar-4); }
.avatar[data-tint="5"] { background: var(--avatar-5); }

.field {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  max-width: 24rem;
  margin-bottom: 1.25rem;
}

.field label {
  font-weight: 600;
  font-size: 0.9375rem;
}

.field input,
.field select,
.field textarea {
  height: 2.75rem;
  padding-inline: 0.75rem;
  border: 0.125rem solid var(--panel-edge);
  border-radius: var(--radius-control);
  background: var(--field);
  color: var(--ink);
  font: inherit;
}

.field textarea {
  height: auto;
  padding-block: 0.5rem;
}

.field__error {
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--danger);
}

/* A refusal from the server, above the form it refuses. Account, match and admin pages all post
   forms, so it lives here rather than in any one of their sheets. */
.account-alert {
  margin-bottom: 1.25rem;
  padding: 0.75rem 1rem;
  border-left: 0.1875rem solid var(--danger);
  border-radius: var(--radius-control);
  background: rgba(200, 16, 46, 0.1);
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--danger);
}

.account-alert:empty {
  display: none;
}

/* The validation summary writes its messages as a list; one refusal is a sentence, not a bullet. */
.account-alert ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.account-alert li + li {
  margin-top: 0.25rem;
}

/* The alert block saying something went right. It wears the accent rather than the game red the
   alert keeps for a refusal, which is the one colour on these pages that means "this was refused".
   The admin editors say "Saved" with it, so it lives beside the alert rather than in account.css. */
.account-notice {
  margin-bottom: 1.25rem;
  padding: 0.75rem 1rem;
  border-left: 0.1875rem solid var(--accent-edge);
  border-radius: var(--radius-control);
  background: rgba(245, 179, 53, 0.14);
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--ink);
}

.validation-summary-valid {
  display: none;
}

.input-validation-error {
  border-color: var(--danger) !important;
}

.account-name {
  max-width: 12rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.game-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: 1.5rem;
  margin: 2.5rem 0 0;
  padding: 0;
  list-style: none;
}

/* Art, heading, blurb, play, browse: five shared rows, so the same part of every tile sits on
   the same line no matter how long its blurb runs. */
.game-tile {
  --focus-ring: var(--ink);
  --tile-art: 7.5rem;
  display: grid;
  grid-row: span 5;
  grid-template-rows: subgrid;
  row-gap: 0;
  overflow: hidden;
  border-radius: var(--radius-panel);
  background: linear-gradient(to bottom, var(--game-color) 0 var(--tile-art), var(--panel) var(--tile-art));
  box-shadow: var(--shadow-panel);
  color: var(--ink);
}

@media (prefers-reduced-motion: no-preference) {
  .game-tile {
    transition: transform var(--duration-hover) ease, box-shadow var(--duration-hover) ease;
  }
}

.game-tile:hover {
  transform: translateY(-0.1875rem);
  box-shadow: var(--shadow-panel-lifted);
}

.game-tile__art {
  display: grid;
  place-items: center;
  height: var(--tile-art);
  background: var(--game-color);
  color: var(--cream);
  font-size: 3.5rem;
  line-height: 1;
}

/* A captured board takes the band's place at the shape it was taken in, and the game's colour stays
   as the rule under it, so the tiles still tell apart at a glance. */
.game-tile__art:has(img) {
  height: auto;
  aspect-ratio: 1200 / 630;
  overflow: hidden;
  border-bottom: 0.25rem solid var(--game-color);
  background: var(--capture-felt);
}

.game-tile__art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The heading goes where Play goes, for a pointer; the keyboard has Play itself, so this is no tab stop. */
.game-tile__title {
  color: inherit;
  text-decoration: none;
}

.game-tile__title:hover {
  text-decoration: underline;
}

.game-tile__body {
  display: grid;
  grid-row: span 4;
  grid-template-rows: subgrid;
  row-gap: 0;
  align-content: start;
  padding: 1.25rem;
  background: var(--panel);
}

.game-tile h2 {
  margin: 0;
  align-self: start;
}

.game-tile p {
  margin: 0.5rem 0 0;
  align-self: start;
  font-size: 0.9375rem;
  line-height: 1.35;
  color: var(--ink-soft);
}

.game-tile .button {
  margin-top: 1rem;
  align-self: start;
}

/* A secondary button fills with --panel, which is the tile body it sits on, so on a tile it needs
   its own edge and the game's colour on the label to read as a control rather than as more panel. */
.game-tile .game-tile__more {
  margin-top: 0.5rem;
  border: 0.0625rem solid var(--panel-edge);
  color: var(--game-color);
}

/* The lobby sends people here to choose, so a choice is the lobby's tile again: an art band in the
   game's colour over a name and its one line, the whole of it one link. */
.choice-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: 1.25rem;
  margin: 2.5rem 0 0;
  padding: 0;
  list-style: none;
}

/* Every card in a row stands the same height, whatever its description runs to: the item stretches
   the card, and the card's body grows to fill it, since the card paints no ground of its own. */
.choice-list > li {
  display: grid;
}

.choice {
  --focus-ring: var(--ink);
  --choice-art: 1200 / 630;

  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--radius-panel);
  /* The card itself paints nothing: its parts do. A card with a ground of its own leaves a hairline
     of it along every edge the picture is rounded or fractionally short of, which is the felt here
     instead, and the picture's own felt hides it. */
  box-shadow: var(--shadow-panel);
  color: var(--ink);
  text-decoration: none;
}

@media (prefers-reduced-motion: no-preference) {
  .choice {
    transition: transform var(--duration-hover) ease, box-shadow var(--duration-hover) ease;
  }
}

.choice:hover {
  transform: translateY(-0.1875rem);
  box-shadow: var(--shadow-panel-lifted);
}

/* The card sits on the felt, so its own ring is the cream one. */
.choice:focus-visible {
  outline: 0.1875rem solid var(--cream);
  outline-offset: 0.125rem;
}

/* The band rounds no corner of its own: the card clips it, and one rounded edge antialiases cleanly
   where two of them laid over each other left a pale fringe tracing the curve. */
.choice__art {
  display: grid;
  place-items: center;
  overflow: hidden;
  /* The shape the pictures are taken in, so a board fills its band without being cropped into. */
  aspect-ratio: var(--choice-art);
  color: var(--cream);
  font-size: 2.5rem;
  line-height: 1;
}

/* The game's colour stands in for a picture that was never taken, behind the glyph. A band holding
   one is the felt the picture was taken on, so there is no ground of another colour to show a seam
   of, nor to flash before the picture arrives. */
.choice__art:not(:has(img)) {
  background: var(--game-color);
}

.choice__art:has(img) {
  background: var(--capture-felt);
}

/* The band is a window onto the table the picture was taken of, and fills the whole of it. */
.choice__art img {
  /* Stretched rather than centred, which is what gives it a height to fill: as a centred grid item
     its area is sized by the picture, and the picture then keeps its own shape and overruns the band. */
  align-self: stretch;
  justify-self: stretch;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.choice__body {
  flex: 1;
  padding: 0.75rem 1rem 1rem;
  background: var(--panel);
}

.choice :is(h2, h3) {
  margin: 0;
  font-size: 1.0625rem;
}

/* A page of many games lists them a family at a time, with a way to jump to each family. */
.browse-families ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 1.5rem 0 0;
  padding: 0;
  list-style: none;
}

.browse-families a {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: 0 0.875rem;
  border-radius: var(--radius-control);
  background: var(--panel);
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
}

.browse-families a:hover {
  text-decoration: underline;
}

.browse-family {
  margin: 3rem 0 0;
  scroll-margin-top: 1rem;
}

.browse-family__about {
  max-width: 90ch;
  margin: 0.5rem 0 0;
}

.browse-family__about + .choice-list {
  margin-top: 1.25rem;
}

.choice p {
  margin: 0.25rem 0 0;
  font-size: 0.875rem;
  color: var(--ink-soft);
}

/* What a shape is made of. */
.choice__meta {
  font-variant-numeric: tabular-nums;
}

/* A choice the player has won a board of, beside its name. */
.choice__won {
  display: inline-block;
  margin-top: 0.25rem;
  padding: 0 0.375rem;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--accent) 28%, var(--panel));
  color: var(--accent-ink);
  font-size: 0.8125rem;
  font-weight: 600;
}

/* How much of the family the player has won, over the cards: the reading and the length of it. */
.browse-collection {
  max-width: 24rem;
}

.browse-collection__count {
  margin: 0;
  color: var(--cream);
  font-variant-numeric: tabular-nums;
}

.browse-collection__bar {
  display: block;
  height: 0.375rem;
  margin-top: 0.375rem;
  border-radius: 0.25rem;
  background: rgba(0, 0, 0, 0.25);
  overflow: hidden;
}

.browse-collection__bar::before {
  display: block;
  width: calc(var(--won) * 100%);
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
  content: "";
}

/* What is written about the game, under the choices. It is a list on the felt rather than more
   cards: the cards above are the reason for the page, and this is what is left to read after it. */
.browse-reading {
  margin-top: 3rem;
}

.browse-reading h2,
.browse-about h2 {
  font-size: 1.1875rem;
}

.browse-reading ul {
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
}

.browse-reading li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
  align-items: baseline;
}

.browse-reading li + li {
  margin-top: 0.5rem;
}

.browse-reading time {
  color: var(--cream-soft);
  font-size: 0.875rem;
}

.browse-about {
  max-width: 68ch;
  margin-top: 3rem;
}

.browse-about p {
  margin: 0.75rem 0 0;
}

/* A visitor to the online games is told what a table asks of them before one of its buttons does. */
.category-account {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
  align-items: center;
  justify-content: space-between;
  margin-top: 2.5rem;
  padding: 1.25rem 1.5rem;
}

.category-account p {
  max-width: 44ch;
  margin: 0;
}

.category-account__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

/* How a table is reached, under the games: read once, after the page has already been used. */
.category-steps {
  max-width: 60ch;
  margin-top: 3rem;
}

.category-steps h2 {
  font-size: 1.1875rem;
}

.category-steps ol {
  margin: 0.75rem 0 0;
  padding-left: 1.25rem;
}

.category-steps li + li {
  margin-top: 0.5rem;
}

@media (max-width: 40rem) {
  :root {
    --shell-gutter: 1rem;
  }

  .site-main {
    padding-block: 1.5rem 2.5rem;
  }

  /* Brand and burger, one row: the header is the only thing between a phone and the table. */
  .site-header__inner {
    gap: 0.75rem;
  }

  .site-nav {
    display: none;
  }

  .site-menu {
    display: block;
  }

  /* The calendar is wider than a phone leaves beside the burger, so it hangs from the header instead. */
  .daily-menu {
    position: static;
  }

  /* The calendar icon alone, so the brand keeps its room; the toggle's own label still names it. */
  .daily-menu__label {
    display: none;
  }

  .daily-menu__panel {
    top: calc(var(--site-header-height) + 0.5rem);
    right: var(--shell-gutter);
    left: var(--shell-gutter);
    width: auto;
  }

  /* Two tiles to a row on a phone, with a band that is a strip of colour rather than a poster: one
     column of full-size tiles made the lobby ten screens tall. */
  .game-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
    margin-top: 1.75rem;
  }

  .game-tile {
    --tile-art: 4.5rem;
  }

  .game-tile__art {
    font-size: 2.5rem;
  }

  .game-tile__body {
    padding: 0.75rem;
  }

  .game-tile h2 {
    font-size: 1.1875rem;
  }

  .game-tile p {
    font-size: 0.875rem;
  }

  /* Half a phone is narrow for "Browse 17 layouts", so a label may take two lines rather than overflow. */
  .game-tile .button {
    height: auto;
    min-height: 2.75rem;
    margin-top: 0.75rem;
    padding: 0.5rem;
    font-size: 0.9375rem;
    line-height: 1.15;
    text-align: center;
  }

  .game-tile .game-tile__more {
    margin-top: 0.5rem;
  }
}
