/* ---------------------------------------------------------------------------------
   The "Game Box" look: a warm paper page, thick ink borders, hard offset shadows (never
   blurred), and three loud primaries used with meaning — yellow for the one thing to
   press, red for emphasis and heavy games, blue for links, medium games and keyboard
   focus. No gradients, no emoji, no illustrations.

   Every color used anywhere below comes from this block. Complexity always travels with
   a word (light / medium / heavy) and a pip count; the color only backs it up.
   --------------------------------------------------------------------------------- */
:root {
  color-scheme: light;
  --paper: #f4eee2; /* page background */
  --ink: #1c1a17; /* text, borders, shadows */
  --card: #ffffff; /* cards, inputs */
  --muted: #5c554c; /* secondary text (6.4:1 on paper, 7.4:1 on card) */
  --red: #d2402a; /* band, VS badge, heavy; white text on it is 4.7:1 */
  --yellow: #f0b429; /* the primary button, light games; ink text on it is 9.3:1 */
  --blue: #2a5cae; /* links, medium games, focus; 5.7:1 on paper */

  /* Derived from the tokens above, never new colors: hairlines and empty tracks. */
  --hairline: color-mix(in srgb, var(--ink) 22%, var(--paper));
  --track: color-mix(in srgb, var(--ink) 11%, var(--paper));

  /* Complexity: filled tiles and chips. */
  --light: var(--yellow);
  --on-light: var(--ink);
  --medium: var(--blue);
  --on-medium: var(--card);
  --heavy: var(--red);
  --on-heavy: var(--card);

  --action: var(--blue);

  --font-display: "Bricolage Grotesque", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Shapes. */
  --border: 3px;
  --border-thin: 2.5px;
  --radius: 14px;
  --radius-sm: 10px;
  --shadow: 7px 7px 0 var(--ink); /* main cards */
  --shadow-button: 6px 6px 0 var(--ink); /* the primary button */
  --shadow-small: 4px 4px 0 var(--ink); /* floating menus, tiles under a finger */

  /* Single source of truth for the mobile bottom tab bar's height — .primary-nav's own
     min-height and body's reserved padding-bottom both read this, so they can never
     drift apart and leave a gap the bar does (or doesn't) cover. */
  --bottom-nav-height: 60px;

  /* Type scale. Nothing outside this block sets a font-size. Display sizes use
     --font-display at weight 800 and tight tracking; body and below use --font-body
     (set once, on body, and inherited). */
  --text-display-size: clamp(2.25rem, 10.5vw, 3.5rem); /* the picked game's name */
  --text-display-lh: 0.95;
  --text-display-ls: -0.04em;

  --text-h1-size: clamp(1.9rem, 7.5vw, 2.5rem); /* page headings */
  --text-h1-lh: 1;
  --text-h1-ls: -0.035em;

  --text-h2-size: 1.5rem; /* card titles, catalog game names */
  --text-h2-lh: 1.1;
  --text-h2-ls: -0.03em;

  --text-body-size: 1rem;
  --text-body-lh: 1.5;

  --text-small-size: 0.875rem;
  --text-small-lh: 1.45;

  --text-xs-size: 0.8125rem;
  --text-xs-lh: 1.4;

  --space-1: 0.25rem; /* 4px */
  --space-2: 0.5rem; /* 8px */
  --space-3: 0.75rem; /* 12px */
  --space-4: 1rem; /* 16px */
  --space-5: 1.5rem; /* 24px */
  --space-6: 2rem; /* 32px */
  --space-7: 3rem; /* 48px */
}

@font-face {
  font-family: "Bricolage Grotesque";
  src: url("/static/fonts/bricolage-grotesque-variable.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Sans";
  src: url("/static/fonts/ibm-plex-sans-variable.woff2") format("woff2");
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------------------------
   Base
   --------------------------------------------------------------------------------- */
* {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-body-size);
  line-height: var(--text-body-lh);
  color: var(--ink);
  background: var(--paper);
  /* Nothing may push the page sideways, down to 360px. Long unbroken names wrap. */
  overflow-wrap: break-word;
}

a {
  color: var(--blue);
}

h1,
h2,
h3,
.display {
  font-family: var(--font-display);
  font-weight: 800;
  margin: 0 0 var(--space-2);
}

h1 {
  font-size: var(--text-h1-size);
  line-height: var(--text-h1-lh);
  letter-spacing: var(--text-h1-ls);
}

h2 {
  font-size: var(--text-h2-size);
  line-height: var(--text-h2-lh);
  letter-spacing: var(--text-h2-ls);
}

p {
  margin: 0 0 var(--space-4);
}

/* Visible on every interactive element, not just links and buttons — inputs and
   selects need it just as much when navigating a filter form by keyboard. */
:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------------------------
   Header — the die and the name; the nav is a tab bar (below).
   --------------------------------------------------------------------------------- */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: var(--paper);
  border-bottom: var(--border) solid var(--ink);
  position: relative;
}

.site-title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  min-width: 0;
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: -0.03em;
  font-size: 1.125rem;
  line-height: 1.05;
  text-decoration: none;
  color: var(--ink);
}

.die-icon {
  flex: none;
  display: block;
}

/* Tab bar. Under 768px this is the fixed bottom bar in ink — `position: fixed` lifts it
   out of .site-header's flex flow entirely, which is what keeps the header itself down
   to the mark, the name and the account control, a single row, on a phone. At 768px+
   it drops back into that flow as a top bar (see the media query below) rather than
   needing a second copy of the markup. */
.primary-nav {
  display: flex;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--ink);
  border-top: var(--border) solid var(--ink);
  z-index: 20;
  padding-bottom: env(safe-area-inset-bottom, 0);
}

.primary-nav-item {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--bottom-nav-height);
  padding: 0 var(--space-2);
  color: var(--paper);
  text-decoration: none;
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  border-top: 4px solid transparent;
  margin-top: -4px;
}

/* The active tab is never color alone — a yellow bar sits above it as well. */
.primary-nav-item.is-active {
  color: var(--yellow);
  border-top-color: var(--yellow);
}

.primary-nav-item:hover {
  color: var(--yellow);
}

/* Blue on ink would be 2.7:1, so a focused tab flips to paper with the blue outline
   inside it, where it reads. */
.primary-nav-item:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: -3px;
  background: var(--paper);
  color: var(--ink);
}

/* Reserves exactly the bottom bar's own height at the end of the page, so the fixed
   bar it sits under never overlaps whatever content happens to be last — the picker's
   result card, an expanded filter panel, anything. */
body {
  padding-bottom: var(--bottom-nav-height);
}

.account-control {
  color: var(--ink);
  text-decoration: none;
  font-weight: 600;
  min-height: 44px;
  max-width: 8rem;
  padding: 0 var(--space-3);
  border: var(--border-thin) solid var(--ink);
  border-radius: 999px;
  background: var(--card);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;
  line-height: calc(44px - 2 * var(--border-thin));
}

.account-control:hover {
  background: var(--yellow);
}

.account-menu {
  position: relative;
  flex: none;
}

.account-menu > summary {
  list-style: none;
  cursor: pointer;
}

.account-menu > summary::-webkit-details-marker {
  display: none;
}

/* A dropdown floating over content gets the same hard shadow as everything else that
   sits above the page. */
.account-menu-panel {
  position: absolute;
  right: 0;
  top: 100%;
  margin-top: var(--space-2);
  min-width: 11rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  background: var(--card);
  border: var(--border) solid var(--ink);
  border-radius: var(--radius);
  padding: var(--space-2);
  box-shadow: var(--shadow-small);
  z-index: 30;
}

.account-menu-panel a,
.account-menu-panel button {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  color: var(--ink);
  font-size: var(--text-body-size);
  font-weight: 600;
  text-decoration: none;
  background: none;
  border: none;
  font-family: inherit;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.account-menu-panel a:hover,
.account-menu-panel button:hover {
  background: var(--yellow);
}

@media (min-width: 768px) {
  body {
    padding-bottom: 0;
  }

  .site-header {
    padding: var(--space-2) var(--space-5);
    gap: var(--space-4);
  }

  .site-title {
    font-size: 1.375rem;
  }

  .primary-nav {
    position: static;
    margin-left: auto;
    background: none;
    border-top: none;
    padding-bottom: 0;
    gap: var(--space-2);
  }

  .primary-nav-item {
    flex: none;
    min-height: 44px;
    padding: 0 var(--space-4);
    margin-top: 0;
    border-top: none;
    color: var(--ink);
    border-radius: 999px;
    font-size: var(--text-body-size);
  }

  .primary-nav-item:hover {
    color: var(--ink);
    background: var(--yellow);
  }

  .primary-nav-item.is-active {
    background: var(--ink);
    color: var(--yellow);
  }

  .primary-nav-item:focus-visible {
    outline-offset: 2px;
    background: var(--paper);
  }
}

main {
  padding: var(--space-5) var(--space-4) var(--space-4);
  max-width: 1120px;
  margin: 0 auto;
}

/* The picker (landing and /pick) sits in a narrower centered column. */
.picker-column {
  max-width: 38rem;
  margin: 0 auto;
}

/* ---------------------------------------------------------------------------------
   Notices
   --------------------------------------------------------------------------------- */
.notice {
  padding: var(--space-3) var(--space-4);
  border: var(--border-thin) solid var(--ink);
  border-left: 10px solid var(--ink);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--ink);
  margin-bottom: var(--space-4);
}

.notice > :last-child {
  margin-bottom: 0;
}

.notice-error {
  border-left-color: var(--red);
}

/* Populated only when an htmx request is rejected (e.g. a failed CSRF check) — see
   static/js/site.js's htmx:responseError listener. Empty the rest of the time, so it
   takes up no space and shows no empty bordered box. */
#global-error:empty {
  display: none;
}

.notice-warning {
  border-left-color: var(--yellow);
}

.muted {
  color: var(--muted);
}

/* ---------------------------------------------------------------------------------
   Forms — large, tappable, no decoration. The submit flow must stay fast.
   --------------------------------------------------------------------------------- */
.submit-form input[type="text"],
.submit-form input[type="number"],
input[type="text"],
input[type="number"],
input[type="search"],
select,
textarea {
  width: 100%;
  max-width: 24rem;
  min-height: 44px;
  padding: 0.6rem 0.75rem;
  border: var(--border-thin) solid var(--ink);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: var(--text-body-size);
}

textarea {
  min-height: 5rem;
}

select {
  min-height: 44px;
}

input[type="checkbox"],
input[type="radio"] {
  width: 1.375rem;
  height: 1.375rem;
  accent-color: var(--ink);
  flex: none;
}

/* Live duplicate search under /submit/new's name field (see submit/_results.html) —
   never blocks, so the visual weight itself is the only signal: a high-confidence
   match (score >= 0.6) gets a thick border and full-strength text; a weaker one that
   still cleared the 0.3 detection floor is quieter, easy to skim past. */
.duplicate-matches {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.duplicate-match {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-3);
  border: var(--border-thin) solid var(--hairline);
  border-radius: var(--radius-sm);
  background: var(--card);
}

.duplicate-match--strong {
  border-color: var(--ink);
  border-width: var(--border);
}

.duplicate-match--weak {
  opacity: 0.8;
}

.duplicate-match-info {
  font-size: var(--text-small-size);
}

.form-field {
  margin: 1.25rem 0;
}

.form-field label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.35rem;
}

.form-field .required {
  color: var(--ink);
  font-weight: 800;
}

.field-hint {
  color: var(--muted);
  font-size: var(--text-small-size);
  margin: 0 0 0.4rem;
}

.field-error {
  color: var(--ink);
  font-size: var(--text-small-size);
  font-weight: 600;
  margin: 0.3rem 0 0;
  padding-left: var(--space-2);
  border-left: 5px solid var(--red);
}

.form-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}

.form-row .form-field {
  flex: 1 1 12rem;
  margin: 0;
}

fieldset.form-field {
  border: var(--border-thin) solid var(--ink);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  min-width: 0;
  background: var(--card);
}

fieldset.form-field legend {
  font-weight: 700;
  padding: 0 var(--space-2);
}

.tag-checklist {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 0.4rem 1rem;
  max-height: 12rem;
  overflow-y: auto;
  margin: var(--space-2) 0;
}

.tag-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: normal;
  min-height: 44px;
}

.propose-new {
  display: block;
  margin-top: var(--space-2);
  font-weight: normal;
  color: var(--muted);
}

.propose-new input {
  display: block;
  margin-top: 0.35rem;
}

.form-actions {
  margin-top: var(--space-5);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}

/* Button hierarchy — a screen gets exactly one .btn (primary: yellow, ink border, hard
   shadow); everything else that's a real action but not the point of the screen is
   .btn-secondary (paper, ink border); navigation and minor actions are .btn-tertiary (a
   plain link, in effect); destructive actions are .btn-danger (red, white text).

   Deliberately NOT a blanket `button:not(...)` selector defaulting every bare <button>
   to the primary look: a plain <button> gets only sane sizing/cursor from the bare
   `button` rule below; looking like something requires reaching for one of the classes
   here on purpose. */
button {
  font: inherit;
  cursor: pointer;
}

.btn,
.btn-secondary,
.btn-tertiary,
.btn-danger,
input[type="submit"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-height: 48px;
  padding: 0.6rem 1.4rem;
  border: var(--border) solid var(--ink);
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-size: 1.125rem;
  font-weight: 800;
  letter-spacing: -0.015em;
  line-height: 1.1;
  text-decoration: none;
  cursor: pointer;
}

.btn,
input[type="submit"] {
  background: var(--yellow);
  color: var(--ink);
  box-shadow: var(--shadow-button);
  transition:
    transform 80ms ease-out,
    box-shadow 80ms ease-out;
}

/* Pressing it pushes it into the page: the shadow closes up as the button moves down
   and right by the same amount. */
.btn:hover,
input[type="submit"]:hover {
  transform: translate(2px, 2px);
  box-shadow: 4px 4px 0 var(--ink);
}

.btn:active,
input[type="submit"]:active {
  transform: translate(6px, 6px);
  box-shadow: 0 0 0 var(--ink);
}

.btn-secondary {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-body-size);
  font-weight: 700;
  letter-spacing: 0;
}

.btn-secondary:hover {
  background: var(--yellow);
}

.btn-tertiary {
  border-color: transparent;
  background: none;
  padding-left: 0;
  padding-right: 0;
  color: var(--blue);
  font-family: var(--font-body);
  font-size: var(--text-body-size);
  font-weight: 600;
  letter-spacing: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 2px;
}

.btn-tertiary:hover {
  color: var(--ink);
}

.btn-danger {
  background: var(--red);
  color: var(--card);
  font-family: var(--font-body);
  font-size: var(--text-body-size);
  font-weight: 700;
  letter-spacing: 0;
}

.btn-danger:hover {
  background: var(--ink);
}

/* No button is full-width unless it's the single primary action on a narrow screen. */

/* ---------------------------------------------------------------------------------
   Account page (/account) — name/email summary plus the delete section.
   --------------------------------------------------------------------------------- */
.account-details {
  margin: 0 0 var(--space-6);
}

.account-details dt {
  color: var(--muted);
  font-size: var(--text-small-size);
  margin-top: var(--space-3);
}

.account-details dd {
  margin: 0;
  font-size: var(--text-body-size);
}

.account-delete {
  padding-top: var(--space-4);
  border-top: var(--border-thin) solid var(--ink);
}

/* ---------------------------------------------------------------------------------
   Chips and status badges: small pills. A bare .chip is a neutral label (a year, a
   tag); the --light/--medium/--heavy variants are the complexity colors, filled.
   --------------------------------------------------------------------------------- */
.chip,
.status-badge,
.shelf-badge {
  display: inline-block;
  padding: 0.1rem 0.7rem;
  border: var(--border-thin) solid var(--ink);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  font-size: var(--text-small-size);
  font-weight: 700;
  line-height: 1.4;
  white-space: nowrap;
}

.chip--light {
  background: var(--light);
  color: var(--on-light);
  text-transform: capitalize;
}

.chip--medium {
  background: var(--medium);
  color: var(--on-medium);
  text-transform: capitalize;
}

.chip--heavy {
  background: var(--heavy);
  color: var(--on-heavy);
  text-transform: capitalize;
}

.status-pending,
.shelf-badge-pending {
  background: var(--yellow);
  color: var(--ink);
}

.status-approved,
.shelf-badge-owned {
  background: var(--ink);
  color: var(--paper);
}

.status-rejected,
.shelf-badge-rejected {
  background: var(--red);
  color: var(--card);
}

.shelf-badge-wishlist {
  background: var(--card);
  color: var(--ink);
}

/* ---------------------------------------------------------------------------------
   The spine — a thick colored left edge in the game's complexity color, on every
   catalog row, shelf row and detail card. A game with no recorded complexity gets a
   --track spine, never no spine at all.
   --------------------------------------------------------------------------------- */
.spine--light {
  --spine-color: var(--light);
}

.spine--medium {
  --spine-color: var(--medium);
}

.spine--heavy {
  --spine-color: var(--heavy);
}

.spine--none {
  --spine-color: var(--track);
}

/* ---------------------------------------------------------------------------------
   The pip track — five squares, filled to the rounded complexity value. The
   accessibility mechanism for complexity: the fill count carries the meaning, not
   just the color, so color-blindness doesn't lose the information.
   --------------------------------------------------------------------------------- */
.pip-track {
  display: inline-flex;
  gap: 0.2rem;
  vertical-align: middle;
}

.pip {
  width: 0.7rem;
  height: 0.7rem;
  background: var(--card);
  border: 2px solid var(--ink);
  border-radius: 2px;
}

.pip--filled {
  background: var(--pip-color, var(--ink));
}

.pip-track--light .pip--filled {
  --pip-color: var(--light);
}

.pip-track--medium .pip--filled {
  --pip-color: var(--medium);
}

.pip-track--heavy .pip--filled {
  --pip-color: var(--heavy);
}

/* ---------------------------------------------------------------------------------
   Meeples — picker result only. Filled to max_players (capped at 6 seats shown) in
   ink, remaining seats in --track.
   --------------------------------------------------------------------------------- */
.meeple-track {
  display: inline-flex;
  gap: 0.15rem;
  vertical-align: middle;
}

.meeple {
  width: 1.25rem;
  height: 1.25rem;
  fill: var(--track);
  stroke: var(--ink);
  stroke-width: 1.5;
}

.meeple--filled {
  fill: var(--ink);
}

.meeple-overflow {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-h2-size);
  color: var(--ink);
  margin-left: 0.1rem;
}

/* ---------------------------------------------------------------------------------
   Catalog / shelf layout
   --------------------------------------------------------------------------------- */
.catalog-layout {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  align-items: flex-start;
}

.filter-toggle {
  display: none;
  width: 100%;
  min-height: 48px;
  margin-bottom: var(--space-2);
  padding: 0.6rem 0.85rem;
  border: var(--border) solid var(--ink);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.filter-toggle[aria-expanded="true"] {
  background: var(--ink);
  color: var(--yellow);
}

.catalog-filters {
  flex: 0 0 14rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0;
}

/* .form-field's own margin (needed for the submit form, which isn't a flex column with
   a gap) stacks on top of this container's `gap` instead of collapsing into it. The
   gap alone is the spacing here; the fields' own margin needs to get out of its way. */
.catalog-filters > .form-field,
.catalog-filters fieldset.form-field,
.catalog-filters .form-actions {
  margin: 0;
}

.catalog-filters .form-row {
  flex-direction: column;
  gap: var(--space-3);
}

/* The row's 12rem flex-basis is a width in the wrapped layout; in this column it would
   become a height. */
.catalog-filters .form-row .form-field {
  flex: none;
}

.catalog-results {
  flex: 1 1 24rem;
  min-width: 0;
}

.catalog-summary {
  color: var(--muted);
  font-weight: 600;
  margin: 0 0 var(--space-2);
}

.checkbox-field {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: normal;
  min-height: 44px;
}

.pagination {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-4);
  align-items: center;
}

.pagination a,
.pagination-current {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0.25rem 0.8rem;
  border: var(--border-thin) solid var(--ink);
  border-radius: 999px;
  background: var(--card);
  text-decoration: none;
  color: var(--ink);
  font-weight: 700;
  font-size: var(--text-body-size);
}

.pagination a:hover {
  background: var(--yellow);
}

.pagination-current {
  background: var(--ink);
  color: var(--yellow);
}

/* ---------------------------------------------------------------------------------
   Detail page — a card like the picker's, minus the band.
   --------------------------------------------------------------------------------- */
.game-detail-card {
  background: var(--card);
  border: var(--border) solid var(--ink);
  border-left: 14px solid var(--spine-color);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--space-5);
  max-width: 44rem;
}

.game-detail {
  margin: var(--space-5) 0;
}

.detail-row {
  display: flex;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: var(--border-thin) solid var(--hairline);
}

.detail-row dt {
  flex: 0 0 9rem;
  font-weight: 700;
  color: var(--muted);
}

.detail-row dd {
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
}

.complexity-value {
  font-family: var(--font-display);
  font-weight: 800;
}

.complexity-label {
  color: var(--muted);
}

/* ---------------------------------------------------------------------------------
   Catalog / shelf table
   --------------------------------------------------------------------------------- */
.table-scroll {
  overflow-x: auto;
  background: var(--card);
  border: var(--border) solid var(--ink);
  border-radius: var(--radius);
}

.catalog-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-body-size);
}

.catalog-table th,
.catalog-table td {
  text-align: left;
  padding: var(--space-3) var(--space-3);
  border-bottom: var(--border-thin) solid var(--hairline);
}

.catalog-table tbody tr:last-child td {
  border-bottom: none;
}

/* The spine: a colored left border on the first cell of every row, driven by the
   spine--* class on the <tr> (set via ctg.display.complexity_bucket). Applied to the
   cell, not the row: border-left on <tr> doesn't render reliably under
   border-collapse. */
.catalog-table tr[class*="spine--"] > td:first-child {
  border-left: 12px solid var(--spine-color);
}

.catalog-table td[data-label="Name"] {
  font-family: var(--font-display);
  font-size: var(--text-h2-size);
  line-height: var(--text-h2-lh);
  letter-spacing: var(--text-h2-ls);
  font-weight: 800;
}

.catalog-table td[data-label="Name"] a {
  color: var(--ink);
  text-decoration: none;
}

.catalog-table td[data-label="Name"] a:hover {
  text-decoration: underline;
  text-decoration-thickness: 3px;
  text-decoration-color: var(--red);
  text-underline-offset: 4px;
}

/* The metadata run and tags run — one line each, quiet relative to the name above
   them, close to it since they describe the same game, not a separate idea. */
.meta-line {
  color: var(--muted);
  font-size: var(--text-small-size);
  line-height: var(--text-small-lh);
}

.catalog-table td[data-label="Details"] {
  min-width: 11rem;
}

.tags-line {
  color: var(--muted);
  font-size: var(--text-xs-size);
  line-height: var(--text-xs-lh);
}

/* Letting the wide, variable-length columns wrap keeps the short fixed-format ones
   compact without forcing the whole table wider than its box. Scoped to desktop only:
   mobile's stacked-card layout below sets its own rules. */
@media (min-width: 641px) {
  .catalog-table td[data-label="Complexity"],
  .catalog-table td[data-label="Status"] {
    white-space: nowrap;
  }
}

.catalog-table th {
  color: var(--ink);
  background: var(--yellow);
  border-bottom: var(--border) solid var(--ink);
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-small-size);
  letter-spacing: 0;
}

/* ---------------------------------------------------------------------------------
   Shelf control (add/remove/wishlist) — the one-click-add surface. Buttons stay
   compact so a dense list is still scannable, but each hit target still clears 44px.
   --------------------------------------------------------------------------------- */
.shelf-control {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  white-space: normal;
}

.shelf-signin-prompt {
  color: var(--blue);
  font-size: var(--text-small-size);
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* Secondary outline buttons, not primary — these are real actions (own a game, want to
   play it, remove it) but never the point of whatever screen they're on, and this app
   is used standing up, so they stay buttons rather than demoting to text links: thumb
   targets matter more than visual quiet here. .shelf-btn-secondary is kept as an alias
   (identical styling) — existing markup already applies it alongside .shelf-btn. */
.shelf-btn,
.shelf-btn-secondary {
  font: inherit;
  font-size: var(--text-small-size);
  font-weight: 700;
  min-height: 44px;
  padding: 0.4rem 0.85rem;
  border: var(--border-thin) solid var(--ink);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
}

.shelf-btn:hover,
.shelf-btn-secondary:hover {
  background: var(--yellow);
}

.shelf-btn-remove:hover {
  background: var(--red);
  color: var(--card);
}

.shelf-rejected-note {
  font-size: var(--text-small-size);
  color: var(--muted);
}

.shelf-notes {
  min-width: 12rem;
}

.shelf-notes-text {
  margin: 0 0 0.4rem;
  font-size: var(--text-body-size);
}

.shelf-notes-form textarea {
  width: 100%;
  max-width: 20rem;
}

.shelf-notes-form .form-field {
  margin: 0 0 var(--space-2);
}

.shelf-notes-form .form-actions {
  margin-top: var(--space-2);
}

/* ---------------------------------------------------------------------------------
   The picker — the most important screen in the app. One pill switch for the mode,
   one big yellow button, and a result that looks like the box a game comes in.
   --------------------------------------------------------------------------------- */
.mode-switch {
  display: flex;
  gap: var(--space-2);
  margin: 0 0 var(--space-5);
}

.mode-pill {
  flex: 1 1 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 var(--space-3);
  border: var(--border) solid var(--ink);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.mode-pill:hover {
  background: var(--yellow);
}

.mode-pill.is-active {
  background: var(--ink);
  color: var(--yellow);
}

.mode-blurb {
  color: var(--muted);
  font-size: var(--text-body-size);
  margin: 0 0 var(--space-5);
}

.pick-filters {
  margin: 0;
}

/* The picker's filters start collapsed on every viewport: the default path is open the
   page, tap one button, get a game, and a page-length filter form between those two
   steps defeats that. Same toggle interaction as .filter-toggle/.catalog-filters
   (shared JS, see catalog.js), different CSS: no media query gate. */
.pick-filter-toggle {
  display: block;
  text-align: left;
}

.pick-filters-panel {
  display: none;
  flex-direction: column;
  gap: var(--space-3);
  border: var(--border) solid var(--ink);
  border-radius: var(--radius);
  background: var(--card);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}

.pick-filters-panel.is-open,
.pick-filters-panel:target {
  display: flex;
}

/* Same as .catalog-filters above: the panel's gap is its only spacing. */
.pick-filters-panel > .form-field,
.pick-filters-panel fieldset.form-field {
  margin: 0;
}

.pick-filters-panel fieldset.form-field {
  background: var(--paper);
}

.pick-scope-toggle {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
}

.pick-match-count {
  font-weight: 600;
  color: var(--muted);
  margin: 0 0 var(--space-4);
}

/* --- the game box ---------------------------------------------------------------- */
.gamebox {
  margin: var(--space-4) 0 0;
  background: var(--card);
  border: var(--border) solid var(--ink);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.gamebox-band {
  margin: 0;
  padding: var(--space-2) var(--space-4);
  background: var(--red);
  color: var(--card);
  border-bottom: var(--border) solid var(--ink);
  font-family: var(--font-display);
  font-size: var(--text-small-size);
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.gamebox-body {
  padding: var(--space-5) var(--space-4) var(--space-4);
}

.gamebox h2 {
  margin: 0 0 var(--space-3);
  line-height: var(--text-display-lh);
}

.pick-result-name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-display-size);
  letter-spacing: var(--text-display-ls);
  line-height: var(--text-display-lh);
  color: var(--ink);
  text-decoration: none;
  display: block;
  text-wrap: balance;
}

.pick-result-name:hover {
  text-decoration: underline;
  text-decoration-thickness: 4px;
  text-decoration-color: var(--red);
  text-underline-offset: 6px;
}

.gamebox-sub {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
}

/* Four stat tiles, two by two. The Weight tile is filled with the game's complexity
   color; the others stay paper. */
.stat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
  margin: 0 0 var(--space-4);
}

.stat-tile {
  min-width: 0;
  padding: var(--space-3);
  border: var(--border-thin) solid var(--ink);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
}

.stat-tile dt {
  font-size: var(--text-xs-size);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin: 0 0 var(--space-1);
}

.stat-tile:not([class*="stat-tile--"]) dt {
  color: var(--muted);
}

.stat-tile dd {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--space-2);
}

.stat-value {
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.15;
  text-transform: capitalize;
}

.stat-unit {
  font-size: var(--text-small-size);
  font-weight: 600;
}

.stat-tile--light {
  background: var(--light);
  color: var(--on-light);
}

.stat-tile--medium {
  background: var(--medium);
  color: var(--on-medium);
}

.stat-tile--heavy {
  background: var(--heavy);
  color: var(--on-heavy);
}

.stat-tile--none {
  background: var(--paper);
}

.stat-tile--none dt {
  color: var(--muted);
}

/* Pips and meeples on a colored tile draw in the tile's own text color. */
.stat-tile .pip {
  border-color: currentColor;
  background: transparent;
}

.stat-tile .pip--filled {
  background: currentColor;
}

.stat-tile .meeple-track {
  flex-wrap: wrap;
  gap: 0.1rem;
}

.stat-tile .meeple-overflow {
  font-size: var(--text-small-size);
  margin: 0;
}

.stat-tile .meeple {
  width: 0.95rem;
  height: 0.95rem;
  fill: transparent;
  stroke: var(--ink);
}

.stat-tile .meeple--filled {
  fill: var(--ink);
}

.gamebox-shelf {
  padding-top: var(--space-3);
  border-top: var(--border-thin) dashed var(--hairline);
}

/* Buttons below the box: the one primary action, then the quiet link back to the
   filters that made the pool. */
.gamebox-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  margin: var(--space-5) 0 0;
}

.gamebox-actions .btn {
  min-width: 11rem;
}

.filter-link {
  flex: 1 1 100%;
  color: var(--muted);
  font-size: var(--text-small-size);
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 2px;
}

.filter-link:hover {
  color: var(--ink);
}

/* The spin: the whole card spins and reveals as a unit, not just the name.
   .pick-spin-window and .pick-result-content are mutually exclusive, both toggled by the
   single .is-spinning class pick.js puts on .pick-result-card: never both visible at
   once, so there's no overlay-beneath-content ordering to get wrong. A no-JS client, or
   one with prefers-reduced-motion, never adds .is-spinning at all — .pick-spin-window
   stays display:none (its default, below) and .pick-result-content is simply what's
   there.

   The window's height is a fixed one-line box, held in the --spin-row-height custom
   property rather than baked separately into both this rule and .pick-spin-reel-item —
   pick.js reads that property with getComputedStyle() instead of measuring a rendered
   element's box, because .pick-spin-window is display:none until .is-spinning is added,
   and a display:none element always measures zero. transform: translateY() on
   .pick-spin-reel is the only thing that moves; composites on the GPU rather than
   thrashing text nodes. */
.pick-spin-window {
  --spin-row-height: 64px;
  display: none;
  height: var(--spin-row-height);
  overflow: hidden;
}

.pick-result-card.is-spinning .pick-spin-window {
  display: block;
}

.pick-result-card.is-spinning .pick-result-content {
  display: none;
}

.pick-spin-reel {
  display: flex;
  flex-direction: column;
}

.pick-spin-reel-item {
  height: var(--spin-row-height);
  display: flex;
  align-items: center;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-display-size);
  line-height: 1;
  letter-spacing: var(--text-display-ls);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- knockout ------------------------------------------------------------------- */
.ko-progress {
  display: inline-block;
  margin: 0 0 var(--space-2);
  padding: 0.15rem 0.8rem;
  border: var(--border-thin) solid var(--ink);
  border-radius: 999px;
  background: var(--card);
  font-weight: 700;
  font-size: var(--text-small-size);
}

.versus {
  display: grid;
  gap: var(--space-4);
  position: relative;
  margin: var(--space-4) 0 var(--space-5);
}

.vs-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-2);
  min-height: 9rem;
  padding: var(--space-4);
  text-align: left;
  color: var(--ink);
  background: var(--card);
  border: var(--border) solid var(--ink);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  transition:
    transform 80ms ease-out,
    box-shadow 80ms ease-out,
    background-color 80ms ease-out;
}

.vs-card:hover {
  background: var(--yellow);
  transform: translate(3px, 3px);
  box-shadow: 4px 4px 0 var(--ink);
}

.vs-card:active {
  transform: translate(7px, 7px);
  box-shadow: 0 0 0 var(--ink);
}

.vs-name {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 8vw, 2.5rem);
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 0.98;
  text-wrap: balance;
}

.vs-meta {
  font-size: var(--text-small-size);
  font-weight: 600;
  color: var(--muted);
}

.vs-card:hover .vs-meta {
  color: var(--ink);
}

/* The red VS badge sits on the seam between the two cards. */
.vs-badge {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  border: var(--border) solid var(--ink);
  border-radius: 50%;
  background: var(--red);
  color: var(--card);
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  box-shadow: var(--shadow-small);
  pointer-events: none;
}

.ko-swap {
  margin: 0;
}

@media (min-width: 640px) {
  .versus {
    grid-template-columns: 1fr 1fr;
    column-gap: 4.5rem; /* room for the VS badge between the cards */
  }
}

/* --- mood ----------------------------------------------------------------------- */
.mood-questions {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.mood-question {
  border: none;
  margin: 0;
  padding: 0;
  min-width: 0;
}

.mood-question legend {
  padding: 0;
  margin-bottom: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-h2-size);
  font-weight: 800;
  letter-spacing: var(--text-h2-ls);
}

.mood-choices {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}

/* The radios themselves are visually hidden; the labels are the buttons. They stay in
   the tab order and keep their arrow-key behavior. */
.mood-radio {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
  pointer-events: none;
}

.mood-choice {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 4.5rem;
  padding: var(--space-3);
  text-align: center;
  background: var(--card);
  border: var(--border) solid var(--ink);
  border-radius: var(--radius);
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
  cursor: pointer;
}

.mood-choice:hover {
  background: var(--paper);
}

.mood-radio:checked + .mood-choice {
  background: var(--yellow);
  box-shadow: var(--shadow-small);
  transform: translate(-2px, -2px);
}

.mood-skip {
  grid-column: 1 / -1;
  justify-self: start;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--space-3);
  border: var(--border-thin) solid transparent;
  border-radius: 999px;
  color: var(--muted);
  font-weight: 600;
  cursor: pointer;
}

.mood-radio:checked + .mood-skip {
  border-color: var(--ink);
  background: var(--card);
  color: var(--ink);
}

.mood-radio:focus-visible + .mood-choice,
.mood-radio:focus-visible + .mood-skip {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------------
   Mobile — the stacked-card catalog and the collapsed filter sidebar.
   --------------------------------------------------------------------------------- */
@media (max-width: 640px) {
  .filter-toggle {
    display: block;
  }

  .catalog-filters {
    display: none;
    flex: 1 1 100%;
    border: var(--border) solid var(--ink);
    border-radius: var(--radius);
    background: var(--card);
    padding: var(--space-4);
  }

  .catalog-filters.is-open {
    display: flex;
  }

  .catalog-results {
    flex: 1 1 100%;
  }

  .detail-row {
    flex-direction: column;
    gap: 0.2rem;
  }

  .detail-row dt {
    flex: none;
  }

  .table-scroll {
    background: none;
    border: none;
    border-radius: 0;
    overflow: visible;
  }

  .catalog-table thead {
    display: none;
  }

  .catalog-table,
  .catalog-table tbody {
    display: block;
    width: 100%;
  }

  /* Each row becomes a card: name, metadata run, complexity, tags, a divider, then
     shelf controls — the same order as the picker result — stacked instead of the
     wrapping "label: value" chip row. The spine moves from the first <td> (desktop) to
     the whole row here, since <tr> is a flex block on mobile, not a table row. */
  .catalog-table tr {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin-bottom: var(--space-4);
    background: var(--card);
    border: var(--border) solid var(--ink);
    border-radius: var(--radius);
    padding: var(--space-3) var(--space-3) var(--space-3) var(--space-3);
  }

  .catalog-table tr[class*="spine--"] {
    border-left: 14px solid var(--spine-color);
  }

  .catalog-table tr[class*="spine--"] > td:first-child {
    border-left: none;
  }

  .catalog-table td {
    display: block;
    border-bottom: none !important;
    white-space: normal;
    padding: 0;
  }

  /* The divider that separates identity (name, metadata, complexity, tags) from
     action (the shelf buttons). */
  .catalog-table td[data-label="Shelf"] {
    margin-top: var(--space-2);
    padding-top: var(--space-2);
    border-top: var(--border-thin) dashed var(--hairline);
  }

  .catalog-table td[data-label="Notes"] {
    margin-top: var(--space-1);
  }
}

/* ---------------------------------------------------------------------------------
   Footer — quiet, on every page. Lives inside <main> so it's part of normal scrollable
   flow, appearing after whatever content the page has, rather than fixed — that's what
   keeps it from ever overlapping .primary-nav (which reserves its own space via body's
   padding-bottom) or adding height above a page's primary action.
   --------------------------------------------------------------------------------- */
.site-footer {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: var(--border-thin) solid var(--ink);
  color: var(--muted);
  font-size: var(--text-xs-size);
  line-height: var(--text-xs-lh);
  text-align: center;
}

.site-footer p {
  margin: 0 0 var(--space-1);
}

.site-footer p:last-child {
  margin-bottom: 0;
}

.site-footer a {
  color: var(--ink);
  font-weight: 600;
}

.site-footer a:hover {
  color: var(--blue);
}

/* Text for screen readers only — e.g. the footer support link's "(opens in a new tab)". */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------------------------
   Legal pages (/privacy, /terms) — long-form markdown content. Scoped to
   .legal-content rather than bare element selectors: nowhere else in the app renders
   arbitrary markdown, so there's no reason for these rules to reach any further.
   --------------------------------------------------------------------------------- */
.legal-content {
  max-width: 40rem;
  background: var(--card);
  border: var(--border) solid var(--ink);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--space-5) var(--space-4);
}

.legal-content h1 {
  margin-top: 0;
}

.legal-content h2 {
  margin-top: var(--space-6);
}

.legal-content ul,
.legal-content ol {
  margin: 0 0 var(--space-4);
  padding-left: 1.25rem;
}

.legal-content li {
  margin-bottom: var(--space-2);
}

.legal-content table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 var(--space-4);
  font-size: var(--text-small-size);
  display: block;
  overflow-x: auto;
}

.legal-content th,
.legal-content td {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-bottom: var(--border-thin) solid var(--hairline);
}

.legal-content th {
  font-family: var(--font-display);
  font-weight: 800;
}

.legal-content code {
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 0.9em;
  background: var(--track);
  padding: 0.1em 0.3em;
  border-radius: 3px;
}

.legal-content pre {
  overflow-x: auto;
  background: var(--track);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  margin: 0 0 var(--space-4);
}

.legal-content pre code {
  background: none;
  padding: 0;
}

/* ---------------------------------------------------------------------------------
   Moderation queue (/moderate). Internal tool, one operator — density and speed over
   polish, but reusing the same tokens/buttons as the rest of the site rather than
   looking like a different application.
   --------------------------------------------------------------------------------- */
.moderate-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
}

.moderate-tab {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--space-4);
  border: var(--border-thin) solid var(--ink);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  text-decoration: none;
  font-weight: 700;
}

.moderate-tab:hover {
  background: var(--yellow);
}

.moderate-tab.is-active {
  background: var(--ink);
  color: var(--yellow);
}

.moderate-inline-terms {
  border-left-color: var(--yellow);
}

.moderate-inline-term {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
}

.moderate-inline-term-action {
  display: inline-flex;
}

.moderate-reject {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: var(--border-thin) solid var(--ink);
}

.reject-presets {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.btn-chip {
  min-height: 44px;
  padding: 0.4rem 0.9rem;
  border: var(--border-thin) solid var(--ink);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: var(--text-small-size);
  font-weight: 700;
  cursor: pointer;
}

.btn-chip:hover {
  background: var(--red);
  color: var(--card);
}

.reject-custom-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 32rem;
}

.reject-custom-form textarea {
  font: inherit;
  padding: var(--space-2);
  border: var(--border-thin) solid var(--ink);
  border-radius: var(--radius-sm);
  resize: vertical;
}

/* ---------------------------------------------------------------------------------
   Landing page (/). The picker goes first, deliberately compact — the headline, the
   mode switch and the button must all be visible at 360x740 without scrolling; the
   three explainer blocks and the sign-in CTA live below the fold on purpose.
   --------------------------------------------------------------------------------- */
.hero {
  max-width: 38rem;
  margin: 0 auto;
  padding-top: var(--space-2);
  padding-bottom: var(--space-5);
}

.hero-headline {
  font-size: var(--text-display-size);
  line-height: var(--text-display-lh);
  letter-spacing: var(--text-display-ls);
  margin: 0 0 var(--space-4);
  text-wrap: balance;
}

.hero-pick-form {
  margin: 0 0 var(--space-4);
}

.hero-narrow-link {
  margin: var(--space-2) 0 0;
}

.hero-narrow-link a {
  color: var(--muted);
  font-size: var(--text-small-size);
  font-weight: 600;
}

.landing-explainer {
  max-width: 38rem;
  margin: 0 auto;
  padding-top: var(--space-5);
  border-top: var(--border) solid var(--ink);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.landing-block h2 {
  font-size: var(--text-h2-size);
}

.landing-block p {
  margin: 0;
}

/* ---------------------------------------------------------------------------------
   PWA install bar — a quiet, dismissible strip, never the point of any screen. Plain
   document flow at the top of <body>, not fixed, so it never competes with .primary-nav's
   own fixed bottom bar on mobile.
   --------------------------------------------------------------------------------- */
.pwa-install-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--yellow);
  border-bottom: var(--border) solid var(--ink);
  font-size: var(--text-small-size);
}

/* A normal-priority author rule always beats a normal-priority user-agent rule,
   regardless of specificity — so `.pwa-install-bar`'s own `display: flex` above wins
   over the UA stylesheet's `[hidden] { display: none }` with nothing here to say
   otherwise, and the bar renders regardless of its `hidden` attribute. This one is
   an author rule too, so it competes on specificity instead (a class+attribute
   selector beats a bare class selector) and wins. */
.pwa-install-bar[hidden] {
  display: none;
}

.pwa-install-text {
  margin: 0;
  color: var(--ink);
  font-weight: 600;
}

.pwa-install-actions {
  display: flex;
  gap: var(--space-2);
  flex-shrink: 0;
}

.pwa-install-actions .btn-secondary,
.pwa-install-actions .btn-tertiary {
  min-height: 44px;
}

.pwa-install-actions .btn-tertiary {
  color: var(--ink);
  padding-left: var(--space-2);
  padding-right: var(--space-2);
}

/* ---------------------------------------------------------------------------------
   Offline fallback page — deliberately standalone markup, not an extension of
   base.html: it must render with nothing but this stylesheet, itself precached by the
   service worker, and must never carry a CSRF token into a cache entry the way any
   base.html-derived page would.
   --------------------------------------------------------------------------------- */
.offline-page {
  max-width: 28rem;
  margin: var(--space-6) auto;
  padding: var(--space-5) var(--space-4);
  text-align: center;
  background: var(--card);
  border: var(--border) solid var(--ink);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.offline-icon {
  border-radius: 20%;
  margin-bottom: var(--space-4);
}

/* Batch approve on /moderate, and the source list on a machine-submitted game. */
.batch-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-3) 0;
}

.batch-check,
#select-all-games {
  width: 1.5rem;
  height: 1.5rem;
}

.moderate-sources dt {
  font-weight: 700;
  margin-top: var(--space-2);
}

.moderate-sources dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* Filter landing pages (/games/...). */
.landing-list {
  list-style: none;
  padding: 0;
  margin: var(--space-4) 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.landing-list li {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  align-items: baseline;
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  background: var(--card);
  border: var(--border-thin) solid var(--ink);
  border-left: 12px solid var(--spine-color, var(--track));
  border-radius: var(--radius-sm);
}

/* The stats page's bars. */
progress {
  appearance: none;
  width: 100%;
  min-width: 5rem;
  height: 1rem;
  border: var(--border-thin) solid var(--ink);
  border-radius: 999px;
  background: var(--card);
  overflow: hidden;
}

progress::-webkit-progress-bar {
  background: var(--card);
}

progress::-webkit-progress-value {
  background: var(--yellow);
}

progress::-moz-progress-bar {
  background: var(--yellow);
}
