/* Over Achiever — dark-first console UI.
   Structure in greyscale, meaning in colour: teal for progress, gold for a
   finished game, violet for the achievements almost nobody has.
   Mono is used for every label, count and percentage; the sans is for names. */

:root {
  color-scheme: dark;
  --bg: #0a0c0d;
  --bg-2: #0e1113;
  --bg-3: #141819;
  --line: #1b2023;
  --line-2: #272f33;
  --fg: #e9edf0;
  --muted: #8b959c;
  --faint: #5c666d;
  --accent: #3fe0ab;
  --accent-dim: rgba(63, 224, 171, 0.14);
  --gold: #f0b45c;
  --gold-dim: rgba(240, 180, 92, 0.14);
  --silver: #c3ccd2;
  --silver-dim: rgba(195, 204, 210, 0.1);
  --bronze: #c98a5e;
  --bronze-dim: rgba(201, 138, 94, 0.12);
  --ultra: #c891ec;
  /* Voxel faces. They need a clear light/mid/dark relationship or the cubes
     read as loose triangles rather than solid blocks. */
  --v1: #151c21;
  --v2: #0f1417;
  --v3: #0a0e10;
  --radius: 9px;
  --max: 1460px;
  --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

/* Flip the theme ten times in a row and you get this. It is only ever set on
   the element, never saved, so a reload or another press of the button puts
   things back. Same variables as every other theme — the whole site turns, and
   nothing needed a special case to make it happen. */
:root[data-theme="party"] {
  color-scheme: dark;
  --bg: #17022b;
  --bg-2: #23063d;
  --bg-3: #300a52;
  --line: #4b1180;
  --line-2: #6b1cb3;
  --fg: #fff2a8;
  --muted: #ffb3f0;
  --faint: #c98ee8;
  --accent: #35ff9e;
  --accent-dim: rgba(53, 255, 158, 0.2);
  --gold: #ffd93d;
  --gold-dim: rgba(255, 217, 61, 0.2);
  --silver: #ff9ff3;
  --bronze: #ff8c42;
  --ultra: #57e2ff;
  /* Kept close to --bg on purpose. Given real contrast, the voxel field stops
     dead at the bottom edge of the (opaque) header and reads as the background
     starting halfway down the page. It's texture in every other theme; it
     should be texture here too, however loud the rest of this gets. */
  --v1: #22053c;
  --v2: #1c0333;
  --v3: #170229;
  --sans: "Comic Sans MS", "Chalkboard SE", "Segoe Print", cursive;
}
/* Everything leans very slightly, because at that point why not. */
:root[data-theme="party"] .card,
:root[data-theme="party"] .row,
:root[data-theme="party"] .preview,
:root[data-theme="party"] .features li {
  transform: rotate(-0.35deg);
}
:root[data-theme="party"] .brand-mark { animation: party-spin 2.4s linear infinite; display: inline-block; }
@keyframes party-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  :root[data-theme="party"] .brand-mark { animation: none; }
}

/* The design is built for dark. Light keeps the same structure and swaps the
   surfaces, so the toggle still works for anyone who wants it. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #ffffff;
  --bg-2: #f7f9fa;
  --bg-3: #eef2f4;
  --line: #e3e8ea;
  --line-2: #cfd6da;
  --fg: #10161a;
  --muted: #5b666e;
  --faint: #8d979e;
  --accent: #0d9c6d;
  --accent-dim: rgba(13, 156, 109, 0.12);
  --gold: #a9761b;
  --gold-dim: rgba(169, 118, 27, 0.12);
  --silver: #7d8790;
  --silver-dim: rgba(125, 135, 144, 0.1);
  --bronze: #9c6338;
  --bronze-dim: rgba(156, 99, 56, 0.1);
  --ultra: #7c3aa8;
  --v1: #f7f9fa;
  --v2: #f1f4f6;
  --v3: #fbfcfd;
}

/* ------------------------------------------------------------- background */

/*
 * A field of isometric cubes, drifting slowly behind everything.
 *
 * Three conic gradients tile into cube faces — no images and no canvas, so it
 * costs nothing to load and nothing per frame beyond a composited transform.
 * The element is oversized and moves by a whole number of tiles, which is what
 * makes the loop seamless.
 *
 * Kept very faint on purpose: it should read as texture behind the page, not
 * as something competing with it. The mask fades it out down the page so text
 * lower down sits on flat colour.
 */
.voxels {
  position: fixed;
  /* The overhang has to outlast the drift below, or the field slides off the
     right and bottom of the window and leaves bare page behind it. 30% is
     plenty on a desktop; the floor keeps a narrow phone covered too. */
  inset: min(-30%, -160px);
  z-index: -1;
  pointer-events: none;
  --vs: 76px;
  background:
    repeating-conic-gradient(from 30deg, #0000 0 120deg, var(--v3) 0 180deg) calc(0.5 * var(--vs))
      calc(0.866 * var(--vs)),
    repeating-conic-gradient(from 30deg, var(--v1) 0 60deg, var(--v2) 0 120deg, var(--v3) 0 180deg);
  background-size: var(--vs) calc(0.866 * var(--vs));
  /* Texture, not decoration — it should register only if you look for it.
     Never reaches zero either: fading out entirely put the end of the pattern
     inside the window, leaving a faint horizontal edge with bare page below it.
     Ending at partial opacity means it just keeps getting quieter and runs off
     the bottom of the screen instead of stopping on it. */
  opacity: 0.42;
  /* Two stops, one constant slope. Three stops meant the fade changed rate
     partway down, and that kink showed up as a band across the middle of the
     page. */
  -webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.95), rgba(0, 0, 0, 0.12));
  mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.95), rgba(0, 0, 0, 0.12));
  /* One tile per cycle, at the same speed the old eight-tile drift ran at. */
  animation: voxel-drift 19s linear infinite;
}

/*
 * Exactly one tile, so the pattern at the end of a cycle is pixel-identical to
 * the pattern at the start and the loop can't be seen. It used to travel eight
 * tiles — seamless in the same way, but far enough that the field ran out of
 * overhang partway through and left an empty strip down the side of the page
 * until it snapped back.
 */
@keyframes voxel-drift {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(calc(-1 * var(--vs)), calc(-0.866 * var(--vs)), 0); }
}

* { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  /* Space for the scrollbar is always held, whether or not one is showing.
     Without it, expanding an achievement made the page taller than the window,
     the scrollbar appeared, and every column on the page jumped sideways by its
     width — a visible nudge on something that had nothing to do with the row
     being opened. */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 15px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  /* Keeps the disclaimer and version badge at the foot of the window on a
     short page. Without this they sat wherever the content happened to end,
     which on the signed-out page left them stranded up in the middle. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
/* These centre themselves with `margin: 0 auto`, which as flex children would
   shrink them to fit their content instead of letting them fill the window.
   The explicit width restores that, and max-width still does the centring.
   Named individually on purpose: applied to every child it also caught the
   fixed-position voxel field, which is inset past both edges by design and
   ended up covering only part of the window. */
.topbar, main, .foot, .version { width: 100%; }
main { flex: 1 0 auto; }

a { color: inherit; text-decoration: none; }
button, input, textarea { font: inherit; color: inherit; }

a, button, .row, .card, .chip, .ach-row, .guide, .mark, .side-item {
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease,
    box-shadow 0.15s ease, opacity 0.15s ease;
}

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* Small caps labels: the mono voice used for every metric in the UI. */
.k {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}

.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ chrome */

/* Header and announcement slot travel together, so a banner is always visible
   and the page below it simply starts lower. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--bg);
}

.top {
  display: flex;
  align-items: center;
  gap: 22px;
  height: 62px;
  padding: 0 26px;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}

.brand { display: flex; align-items: center; gap: 10px; flex: none; }
.brand-mark { color: var(--accent); font-size: 19px; line-height: 1; }
.brand-text { display: grid; gap: 1px; }
.brand-text b { font-size: 14.5px; font-weight: 700; letter-spacing: 0.06em; }
.brand-text i {
  font-family: var(--mono);
  font-style: normal;
  font-size: 8.5px;
  letter-spacing: 0.16em;
  color: var(--faint);
}

.top-actions { display: flex; align-items: center; gap: 14px; margin-left: auto; flex: none; }

.top-right { display: flex; align-items: center; gap: 10px; }
.who-wrap { position: relative; display: flex; align-items: center; gap: 10px; }

/* Label only — not a control. */
.top-right .who { display: grid; gap: 1px; text-align: right; }
.top-right .who b { font-size: 13px; font-weight: 600; }
.top-right .who span {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}
/* The single control in the bar: avatar plus a chevron, so it reads as a menu
   rather than as decoration. Never hidden at any width — it's the only route to
   signing out. */
.who-trigger {
  position: relative;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 3px 4px 3px 3px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  color: var(--faint);
  cursor: pointer;
  flex: none;
}
.who-trigger:hover, .who-wrap.open .who-trigger { border-color: var(--line-2); background: var(--bg-2); }
.who-trigger:hover { color: var(--muted); }

.who-chev {
  width: 13px;
  height: 13px;
  flex: none;
  transition: transform 0.18s ease;
}
.who-wrap.open .who-chev { transform: rotate(180deg); }


.who-face {
  width: 30px;
  height: 30px;
  border-radius: 5px;
  display: grid;
  place-items: center;
  flex: none;
  cursor: pointer;
  object-fit: cover;
  background: var(--bg-3);
  border: 1px solid transparent;
}
.who-face { pointer-events: none; }
.who-face-blank {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
  border-color: var(--line);
}

.who-menu {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  min-width: 178px;
  display: none;
  flex-direction: column;
  padding: 5px;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: var(--bg-2);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.5);
  z-index: 50;
}
.who-wrap.open .who-menu { display: flex; }
.who-menu a {
  padding: 8px 10px;
  border-radius: 5px;
  font-size: 13px;
  color: var(--muted);
  white-space: nowrap;
}
.who-menu a:hover { background: var(--bg-3); color: var(--fg); }
/* Set apart, so the one irreversible item isn't next to the harmless ones. */
.who-menu .who-out {
  margin-top: 4px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  border-radius: 0 0 5px 5px;
}
.who-menu .who-out:hover { color: var(--gold); }
/* ---- notifications ------------------------------------------------------
 *
 * Borrows the account menu's panel shape deliberately: same corner, same
 * surface, same shadow. Two different-looking popups hanging off the same
 * corner of the bar would read as two different systems.
 */
.bell-wrap { position: relative; display: flex; align-items: center; }
.bell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 7px;
  background: none;
  color: var(--faint);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.bell:hover, .bell-wrap.open .bell { color: var(--fg); background: var(--bg-3); border-color: var(--line-2); }
.bell.has { color: var(--muted); }
.bell-mark { font-size: 14px; line-height: 1; }
/* Sits on the corner rather than beside it, so the bar doesn't reflow by a few
   pixels the moment something arrives. */
.bell-n {
  position: absolute;
  top: -3px;
  right: -3px;
  min-width: 15px;
  padding: 0 4px;
  border-radius: 999px;
  border: 2px solid var(--bg);
  background: var(--accent);
  color: var(--bg);
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
}

.bell-panel {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  width: 320px;
  max-width: calc(100vw - 24px);
  display: none;
  flex-direction: column;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: var(--bg-2);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.5);
  z-index: 50;
  overflow: hidden;
}
.bell-wrap.open .bell-panel { display: flex; }
.bell-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
}
.bell-head-acts { display: flex; align-items: center; gap: 4px; }
.bell-cog, .bell-clear {
  border: 0;
  background: none;
  color: var(--faint);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 4px;
}
.bell-cog:hover, .bell-clear:hover { color: var(--fg); }
.bell-clear { font-size: 11px; }
.bell-clear:disabled { opacity: 0.5; cursor: default; }

/* Foot of the panel: the way out to the full record. */
.bell-history {
  display: block;
  padding: 9px 12px;
  border-top: 1px solid var(--line);
  text-align: center;
  font-size: 11.5px;
  color: var(--faint);
}
.bell-history:hover { color: var(--fg); background: var(--bg-3); }

/* The history page reuses the panel's rows, so it needs the surface they were
   drawn against — otherwise they sit on the page background with borders that
   go nowhere. */
.note-history {
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: var(--bg-2);
  overflow: hidden;
}
.note-history .bell-row { padding: 11px 14px; font-size: 13px; }
.note-history .bell-group { padding: 9px 14px 7px; }

/* Capped and scrolling: a long list must not push the panel off the screen. */
.bell-list { max-height: 340px; overflow-y: auto; }
/* Three columns: marker, message, age. The age used to trail the sentence as
   inline text, so it landed wherever that row happened to wrap — a different
   place on every line, and unreadable as a column of dates. */
.bell-row {
  display: grid;
  grid-template-columns: 6px minmax(0, 1fr) auto;
  align-items: start;
  gap: 8px;
  padding: 9px 12px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
}
.bell-row:last-child { border-bottom: 0; }
.bell-text { min-width: 0; }

/* Sticky so the heading for the group you're reading stays visible while the
   list scrolls under it — otherwise scrolling into the middle tells you
   nothing about when any of it happened. */
.bell-group {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 7px 12px 5px;
  background: var(--bg-2);
  border-bottom: 1px solid var(--line);
}
.bell-row:hover { background: var(--bg-3); color: var(--fg); }
.bell-row b { color: var(--fg); font-weight: 600; }
.bell-row.unread { color: var(--fg); }
/* The dot holds its space on read rows too, so nothing shifts sideways as
   entries are marked read. */
.bell-dot {
  flex: none;
  width: 6px;
  height: 6px;
  margin-top: 6px;
  border-radius: 50%;
  background: transparent;
}
.bell-row.unread .bell-dot, .bell-go .bell-dot { background: var(--accent); }
/* Nowrap and tabular so the column keeps one width and the digits line up
   rather than jittering between "now" and "16mo". */
.bell-when {
  /* Every row is its own grid, so an `auto` column is sized by that row alone
     and the times landed at three different offsets down the list. A fixed
     width is what makes them a column. 4 characters is the widest this format
     produces — see fmtAgoBrief. */
  min-width: 34px;
  text-align: right;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 10px;
  line-height: 1.75;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* Says plainly that the page is a slice of the queue, not all of it. */
.rv-more {
  margin: 18px 0 0;
  text-align: center;
  font-size: 12px;
  color: var(--faint);
}

.bell-go { background: var(--accent-dim); }
/* Only on the queue row: it's the one line whose number doesn't reset, so how
   much of it is new has to be said rather than implied by a dot. */
.bell-new {
  margin-left: 7px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--bg);
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
}
.bell-empty { padding: 18px 14px; font-size: 12.5px; color: var(--faint); line-height: 1.5; }

.bell-prefs { padding: 4px 12px 10px; border-top: 1px solid var(--line); }
.bell-pref { display: flex; align-items: flex-start; gap: 9px; padding: 7px 0; cursor: pointer; }
.bell-pref input { width: 14px; height: 14px; margin-top: 2px; accent-color: var(--accent); flex: none; cursor: pointer; }
.bell-pref b { display: block; font-size: 12.5px; font-weight: 600; color: var(--fg); }
.bell-hint { display: block; font-size: 11px; color: var(--faint); line-height: 1.4; }

/* Pending count beside the review item in the menu. */
.menu-count {
  margin-left: 8px;
  background: var(--accent);
  color: var(--bg);
  border-radius: 9px;
  padding: 0 6px;
  font-size: 11px;
  font-weight: 700;
}
.who-menu a:has(.menu-count) { display: flex; align-items: center; justify-content: space-between; }

.theme-toggle {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: none;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.theme-toggle:hover { color: var(--fg); border-color: var(--line-2); }
.theme-toggle svg { width: 15px; height: 15px; display: block; }
.theme-toggle .sun { display: block; }
.theme-toggle .moon { display: none; }
:root[data-theme="light"] .theme-toggle .sun { display: none; }
:root[data-theme="light"] .theme-toggle .moon { display: block; }

main { max-width: var(--max); margin: 0 auto; padding: 0 26px 90px; }
/* The landing page sizes itself to the window; that trailing space would put a
   scrollbar on a page built not to have one. */
main:has(> .landing) { padding-bottom: 0; }
/* Same reasoning for the small print below it. */
body:has(.landing) .foot { padding-bottom: 16px; font-size: 11px; }

/* Centred on the same axis as the version badge below it. Left-aligned, the
   disclaimer sat on the content column's edge while the version sat on the
   page's centre line, and the two read as misaligned. */
.foot {
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 26px 30px;
  color: var(--faint);
  font-size: 11.5px;
  line-height: 1.6;
  text-align: center;
}
.foot p {
  max-width: 76ch;
  margin: 0 auto;
}

.version {
  display: block;
  text-align: center;
  color: #ffffff;
  font-family: var(--mono);
  font-size: 11px;
  padding: 0 20px 28px;
}
.version:hover { text-decoration: underline; text-underline-offset: 3px; }
.version:empty { display: none; }

/* ------------------------------------------------------------ announcement */

/* The slot is always present and collapsed. Animating grid-template-rows from
   0fr to 1fr is the one way to ease to an automatic height, so the banner opens
   smoothly without anyone having to measure it first. */
.pip-slot {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.32s cubic-bezier(0.22, 0.75, 0.3, 1);
}
.pip-slot.open { grid-template-rows: 1fr; }

.pip-inner {
  overflow: hidden;
  min-height: 0;
}

/* Sized to its text and centred, rather than a full-width bar. */
.pip {
  display: flex;
  align-items: center;
  gap: 10px;
  width: fit-content;
  max-width: min(560px, calc(100% - 52px));
  margin: 6px auto 14px;
  padding: 10px 12px 10px 14px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--bg-2);
  color: var(--fg);
  font-size: 13px;
  line-height: 1.45;
  /* Contents fade and settle slightly after the row opens, so the text doesn't
     appear to stretch as the height grows. */
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 0.22s ease 0.06s, transform 0.22s ease 0.06s;
}
.pip-slot.open .pip { opacity: 1; transform: none; }

.pip-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
  background: var(--gold);
  box-shadow: 0 0 0 3px var(--gold-dim);
}
.pip-ok .pip-dot { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }

/* ------------------------------------------------------------- live feed */

/* Lives in the header bar itself. Empty most of the time, and takes no space
   when empty, so the bar looks the same as it always did until something
   actually happens. */
.ticker {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.ticker.show { opacity: 1; transform: none; }
.ticker:empty { display: none; }

.feed-icon {
  width: 24px;
  height: 24px;
  border-radius: 5px;
  flex: none;
  background: var(--bg-3);
  border: 1px solid var(--line);
}

.feed-text {
  min-width: 0;
  font-size: 12.5px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.feed-text b { font-weight: 600; color: var(--fg); }

.feed-game {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  color: var(--faint);
  margin-left: 8px;
}

/* Same tier colours as the achievement list: violet under 1%, amber under 5%,
   muted above. The class comes from rarityTier(), so a rare unlock announces
   itself as rare. */
.feed-rarity {
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 600;
  flex: none;
  color: var(--faint);
}

.head-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Two settings, one under the other — side by side they read as one control
   split in half. */
.head-actions.is-stacked { flex-direction: column; align-items: stretch; gap: 8px; }

/* ------------------------------------------------------------ review queue */

/* A stack of things to decide on, each carrying everything the decision needs.
   The old version was an index of links with no styles of its own at all — the
   classes it used were dropped in a redesign and nothing replaced them. */

.rv-count {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold);
  border: 1px solid var(--gold);
  background: var(--gold-dim);
  border-radius: 999px;
  padding: 5px 11px;
  white-space: nowrap;
}

.rv-list { display: grid; gap: 16px; margin-top: 24px; }

.rv-card {
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--bg-2);
  overflow: hidden;
  /* The accent edge marks it as something waiting on you. */
  box-shadow: inset 3px 0 0 var(--accent-dim);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.rv-card:hover { border-color: var(--line-2); box-shadow: inset 3px 0 0 var(--accent); }
/* Your own edit isn't yours to judge, so it doesn't ask to be. */
.rv-card.is-own { box-shadow: inset 3px 0 0 var(--line-2); opacity: 0.86; }

.rv-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  padding: 15px 18px 13px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-3);
}
.rv-title { min-width: 0; }
.rv-title h2 {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 0;
}
.rv-game {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  margin-top: 4px;
}
.rv-who {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 12.5px;
  color: var(--muted);
}
.rv-who b { color: var(--fg); font-weight: 600; }

.rv-body { padding: 16px 18px; }
.rv-body .k { display: block; margin-bottom: 10px; }
.rv-steps {
  margin: 0;
  padding-left: 20px;
  display: grid;
  gap: 7px;
  font-size: 13.5px;
  line-height: 1.55;
}
.rv-steps li::marker { color: var(--accent); font-family: var(--mono); font-size: 12px; }
.rv-steps.was { font-size: 12.5px; }
.rv-steps.was li { color: var(--faint); }
.rv-steps.was li::marker { color: var(--line-2); }

.rv-note {
  margin: 14px 0 0;
  padding-left: 13px;
  border-left: 2px solid var(--line-2);
  color: var(--muted);
  font-size: 13px;
  line-height: 1.55;
}

.rv-was { margin-top: 16px; }
.rv-was summary {
  cursor: pointer;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  padding: 6px 0;
}
.rv-was summary:hover { color: var(--muted); }
.rv-was[open] summary { color: var(--muted); margin-bottom: 8px; }

.rv-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  padding: 13px 18px 15px;
  border-top: 1px solid var(--line);
}
.rv-progress { flex: 1 1 220px; min-width: 0; }
.rv-progress .meter { margin: 0 0 7px; }
.rv-progress.is-danger .meter > i { background: var(--danger); }
.rv-verdict { font-size: 12px; color: var(--faint); }
.rv-progress.is-danger .rv-verdict { color: var(--danger); }

.rv-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rv-score {
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  min-width: 2.4ch;
  text-align: right;
}
.rv-score.is-danger { color: var(--danger); }

.rv-vote {
  background: none;
  border: 1px solid var(--line-2);
  border-radius: 7px;
  padding: 6px 13px;
  font-size: 12.5px;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.rv-vote:hover:not(:disabled) { color: var(--fg); border-color: var(--faint); }
.rv-vote.is-on { color: var(--accent); border-color: var(--accent); background: var(--accent-dim); }
.rv-vote.down.is-on { color: var(--danger); border-color: var(--danger); background: var(--danger-dim); }
.rv-vote:disabled { opacity: 0.4; cursor: default; }

.rv-open {
  font-size: 12.5px;
  color: var(--faint);
  padding: 6px 4px;
  white-space: nowrap;
}
.rv-open:hover { color: var(--accent); }

@media (max-width: 640px) {
  .rv-foot { flex-direction: column; align-items: stretch; }
  .rv-actions { justify-content: space-between; }
}

/* -------------------------------------------------------- report a bug */

/* One line under the disclaimer: the version, then the report link. The form
   only appears once asked for, so the resting state is two pieces of small
   text and nothing else. */
.foot-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px 10px;
  padding-bottom: 26px;
}
.foot-bar .version { padding: 0; }
.foot-bar #bugSlot { display: contents; }
.foot-bar #bugSlot:has(.bug-form),
.foot-bar #bugSlot:has(.bug-thanks) { display: block; flex-basis: 100%; text-align: center; }
.bug-open::before { content: '·'; color: var(--line-2); margin-right: 10px; border: 0; }

.bug-open {
  background: none;
  border: 0;
  padding: 2px 6px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--faint);
  cursor: pointer;
  border-bottom: 1px dotted var(--line-2);
}
.bug-open:hover { color: var(--muted); border-bottom-color: var(--faint); }

.bug-form {
  width: min(460px, calc(100vw - 40px));
  margin: 10px auto 0;
  text-align: left;
  border: 1px solid var(--line-2);
  border-radius: 9px;
  background: var(--bg-2);
  padding: 14px;
}
.bug-form .quiet-line { margin: 0 0 10px; }
.bug-text { width: 100%; resize: vertical; }
.bug-actions { display: flex; gap: 8px; margin-top: 10px; }
.bug-status:not(:empty) { margin: 10px 0 0; font-size: 12.5px; color: var(--muted); }
.bug-thanks { margin: 0; font-size: 12.5px; color: var(--accent); }

/* ------------------------------------------------------------------ admin */

/* A control surface, not a page of the site. The public app is cards, prose and
   room to breathe; this is denser on purpose — sidebar, tables, and a drawer so
   any row can be opened rather than only acted on. */

:root { --danger: #ff7a6b; --danger-dim: rgba(255, 122, 107, 0.12); }
:root[data-theme="light"] { --danger: #c0392b; --danger-dim: rgba(192, 57, 43, 0.09); }

.adm-mono, .adm-mono-title { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.adm-mono-title { font-size: 14px !important; font-weight: 600; letter-spacing: -0.01em; }
.adm-wrap { overflow-wrap: anywhere; }
.adm-quiet { color: var(--faint); }
/* What a reporter typed, under the guide they reported. Its own line because
   it's a sentence, not a field. */
.adm-note { display: block; color: var(--faint); font-size: 12px; margin-top: 3px; }
.adm-note::before { content: '“'; }
.adm-note::after { content: '”'; }
.adm-primary { font-weight: 600; }

.adm-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 14px;
  margin-top: 14px;
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: 9px 9px 0 0;
  border-bottom: 0;
}
.adm-mark { color: var(--accent); font-size: 11px; }
.adm-bar-title { font-weight: 600; font-size: 13px; }
.adm-env {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 4px;
  background: var(--gold-dim);
  color: var(--gold);
  box-shadow: inset 0 0 0 1px currentColor;
}
.adm-bar-right { margin-left: auto; font-size: 11.5px; color: var(--faint); }

.adm-shell {
  display: grid;
  grid-template-columns: 178px minmax(0, 1fr);
  border: 1px solid var(--line-2);
  border-radius: 0 0 9px 9px;
  overflow: hidden;
  background: var(--bg);
}
.adm-side { border-right: 1px solid var(--line); padding: 12px 9px; background: var(--bg-2); }
.adm-side-group { margin-bottom: 16px; }
.adm-side-group:last-child { margin-bottom: 0; }
.adm-side-group .k { display: block; padding: 0 8px 6px; }
.adm-nav {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-radius: 6px;
  padding: 7px 9px;
  font-size: 13px;
  color: var(--muted);
  cursor: pointer;
}
.adm-nav:hover { background: var(--bg-3); color: var(--fg); }
.adm-nav[aria-current="page"] { background: var(--accent-dim); color: var(--accent); }
.adm-count { margin-left: auto; font-family: var(--mono); font-size: 10.5px; color: var(--faint); font-variant-numeric: tabular-nums; }
.adm-nav[aria-current="page"] .adm-count { color: var(--accent); }
.adm-count.alert { color: var(--gold); }

.adm-main { padding: 18px 20px 26px; min-width: 0; }
.adm-page-top { margin-bottom: 16px; }
.adm-main h1 { font-size: 18px; font-weight: 600; letter-spacing: -0.02em; margin: 0; }
.adm-main .lede { font-size: 13px; margin-top: 3px; }

/* ---- metrics ---- */
.adm-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(146px, 1fr));
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg-2);
  overflow: hidden;
}
.adm-metric {
  position: relative;
  padding: 11px 13px 12px;
  border: 0;
  border-left: 1px solid var(--line);
  background: none;
  text-align: left;
  cursor: pointer;
  color: inherit;
}
.adm-metric:first-child { border-left: 0; }
.adm-metric:disabled { cursor: default; }
.adm-metric:not(:disabled):hover { background: var(--bg-3); }
.adm-metric[data-state="warn"]::before,
.adm-metric[data-state="bad"]::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  background: var(--gold);
}
.adm-metric[data-state="bad"]::before { background: var(--danger); }
.adm-metric-v { font-size: 19px; font-weight: 600; letter-spacing: -0.02em; margin-top: 6px; font-variant-numeric: tabular-nums; }
.adm-metric-v small { font-family: var(--mono); font-size: 11px; font-weight: 400; color: var(--faint); }
.adm-metric-note { font-size: 11px; color: var(--faint); margin: 2px 0 0; }

.adm-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--accent-dim);
  color: var(--accent);
  box-shadow: inset 0 0 0 1px currentColor;
}
.adm-pill.bad { background: var(--danger-dim); color: var(--danger); }
.adm-dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }

/* ---- panels and tables ---- */
.adm-panel { border: 1px solid var(--line); border-radius: 8px; background: var(--bg-2); overflow: hidden; }
.adm-panel + .adm-panel { margin-top: 16px; }
.adm-panel-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 13px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-3);
}
.adm-panel-head h2 { font-size: 12.5px; font-weight: 600; margin: 0; }
.adm-panel-head .k { margin-left: auto; }
.adm-note-row { padding: 10px 13px; margin: 0; color: var(--muted); font-size: 12.5px; border-bottom: 1px solid var(--line); }

.adm-scroll { overflow-x: auto; }
.adm-table { width: 100%; border-collapse: collapse; min-width: 520px; }
.adm-table th {
  text-align: left;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  font-weight: 400;
  padding: 8px 13px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.adm-table td { padding: 0 13px; height: 38px; border-bottom: 1px solid var(--line); vertical-align: middle; font-size: 12.5px; }
.adm-table tr:last-child td { border-bottom: 0; }
.adm-table td.num, .adm-table th.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.adm-table td.acts { text-align: right; white-space: nowrap; }
tr.adm-openable { cursor: pointer; }
tr.adm-openable:hover { background: var(--bg-3); }
tr.is-open { background: var(--accent-dim); box-shadow: inset 2px 0 0 var(--accent); }

.adm-ref {
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
  border-bottom: 1px dotted var(--line-2);
}
.adm-ref:hover { color: var(--accent); border-bottom-color: var(--accent); }

.adm-tag {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
  border: 1px solid var(--line-2);
  border-radius: 4px;
  padding: 2px 6px;
  white-space: nowrap;
  margin-left: 6px;
}
/* Leading a cell rather than trailing it, as in the error log, where the tag is
   the severity and has to be read before the message it labels. */
.adm-table td > .adm-tag:first-child { margin-left: 0; margin-right: 7px; }
.adm-tag.ok { color: var(--accent); border-color: var(--accent); }
.adm-tag.warn { color: var(--gold); border-color: var(--gold); }
.adm-tag.bad { color: var(--danger); border-color: var(--danger); }

.adm-mini {
  background: none;
  border: 1px solid var(--line-2);
  border-radius: 5px;
  padding: 4px 10px;
  font-size: 11.5px;
  color: var(--muted);
  cursor: pointer;
}
.adm-mini:hover { color: var(--fg); border-color: var(--faint); }
.adm-mini.danger:hover { color: var(--danger); border-color: var(--danger); }
.adm-mini:disabled { opacity: 0.4; cursor: default; }
.adm-mini + .adm-mini { margin-left: 6px; }

.adm-toolbar { display: flex; gap: 8px; padding: 11px 13px; border-top: 1px solid var(--line); }
.adm-input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--mono);
  font-size: 12px;
  padding: 7px 10px;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-2);
  border-radius: 5px;
}
.adm-input::placeholder { color: var(--faint); }

.adm-empty { padding: 26px 13px; text-align: center; color: var(--faint); margin: 0; }

/* Actions that belong to a whole panel rather than a row — clearing the error
   log, for instance — sat under its table and away from the rows. */
.adm-panel-foot { display: flex; justify-content: flex-end; padding: 10px 13px; border-top: 1px solid var(--line-2); }

/* A stack trace keeps its own line breaks and scrolls sideways rather than
   wrapping: a wrapped frame reads as two, and the file it names ends up split
   across lines. */
.adm-stack {
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.55;
  color: var(--muted);
  background: var(--bg-3);
  border: 1px solid var(--line-2);
  border-radius: 5px;
  padding: 10px 11px;
  margin: 0;
  max-height: 300px;
  overflow: auto;
  white-space: pre;
}
.adm-err { color: var(--danger); font-size: 12.5px; margin-top: 10px; }
.adm-foot-note {
  margin: 14px 0 0;
  padding-left: 12px;
  border-left: 2px solid var(--line-2);
  color: var(--faint);
  font-size: 11.5px;
  line-height: 1.6;
}
.adm-panel .adm-foot-note { margin: 0; padding: 11px 13px; border-left: 0; border-top: 1px solid var(--line); }

.adm-compare { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--line); }
.adm-compare-col { background: var(--bg-2); padding: 13px; }
.adm-compare-col.real { box-shadow: inset 2px 0 0 var(--accent); }
.adm-compare-sub { font-size: 12.5px; color: var(--muted); margin-top: 3px; }
.adm-panel .sw { max-width: none; border-radius: 0; border-left: 0; border-right: 0; border-bottom: 0; }

/* ---- detail drawer ---- */
.adm-scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.24s ease;
  z-index: 40;
}
.adm-scrim.open { opacity: 1; pointer-events: auto; }
.adm-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(440px, 100vw);
  background: var(--bg-2);
  border-left: 1px solid var(--line-2);
  z-index: 50;
  transform: translateX(100%);
  transition: transform 0.26s cubic-bezier(0.22, 0.75, 0.3, 1);
  display: flex;
  flex-direction: column;
}
.adm-drawer.open { transform: none; }
.adm-drawer-head { display: flex; align-items: flex-start; gap: 12px; padding: 14px 15px; border-bottom: 1px solid var(--line); }
.adm-drawer-head h2 { font-size: 15px; font-weight: 600; margin: 3px 0 0; letter-spacing: -0.01em; overflow-wrap: anywhere; }
.adm-x {
  margin-left: auto;
  flex: none;
  width: 26px;
  height: 26px;
  background: none;
  border: 1px solid var(--line-2);
  border-radius: 5px;
  color: var(--muted);
  cursor: pointer;
}
.adm-x:hover { color: var(--fg); border-color: var(--faint); }
.adm-drawer-body { flex: 1 1 auto; overflow-y: auto; padding: 15px; }
.adm-drawer-foot { border-top: 1px solid var(--line); padding: 12px 15px; display: flex; gap: 8px; flex-wrap: wrap; background: var(--bg-3); }

.adm-facts { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 7px 14px; margin: 0; }
.adm-facts dt {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  padding-top: 2px;
}
.adm-facts dd { margin: 0; font-size: 12.5px; overflow-wrap: anywhere; }
.adm-label { display: block; margin: 18px 0 8px; }
.adm-quote { border-left: 2px solid var(--line-2); padding-left: 11px; color: var(--muted); font-size: 12.5px; margin: 0; white-space: pre-wrap; }
.adm-steps { padding-left: 17px; margin: 0; color: var(--muted); font-size: 12.5px; line-height: 1.55; }
.adm-steps li { margin: 4px 0; }
.adm-steps.was li { color: var(--faint); text-decoration: line-through; text-decoration-color: var(--line-2); }
.adm-links { display: flex; flex-direction: column; gap: 2px; }
.adm-link {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: 6px;
  border: 1px solid transparent;
  color: var(--muted);
  font-size: 12.5px;
}
.adm-link:hover { background: var(--bg-3); color: var(--fg); border-color: var(--line); }
.adm-arrow { margin-left: auto; color: var(--faint); font-size: 11px; }

@media (max-width: 760px) {
  .adm-shell { grid-template-columns: minmax(0, 1fr); }
  .adm-side { border-right: 0; border-bottom: 1px solid var(--line); display: flex; gap: 6px; overflow-x: auto; padding: 10px; }
  .adm-side-group { margin: 0; display: flex; gap: 4px; }
  .adm-side-group .k { display: none; }
  .adm-nav { width: auto; white-space: nowrap; }
}
@media (prefers-reduced-motion: reduce) {
  .adm-drawer, .adm-scrim { transition: none; }
}

/* ------------------------------------------------------- privacy switches */

/* Whether your name is public is not something to have to infer from a button
   being green, so this states it: a switch that is visibly on or off, the
   setting named, and what is true right now spelled out underneath. */
.sw {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 11px;
  width: 100%;
  max-width: 420px;
  text-align: left;
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: 9px;
  padding: 10px 13px;
  cursor: pointer;
}
.sw:hover { border-color: var(--faint); }
.sw:disabled { opacity: 0.6; cursor: default; }
.sw:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.sw-track {
  position: relative;
  width: 36px;
  height: 20px;
  border-radius: 999px;
  background: var(--line-2);
  border: 1px solid transparent;
  transition: background-color 0.18s ease;
  flex: none;
}
.sw-track > i {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--faint);
  transition: transform 0.18s cubic-bezier(0.22, 0.75, 0.3, 1), background-color 0.18s ease;
}
.sw.is-on .sw-track { background: var(--accent-dim); border-color: var(--accent); }
.sw.is-on .sw-track > i { transform: translateX(16px); background: var(--accent); }

.sw-text { display: block; min-width: 0; }
.sw-text b { display: block; font-size: 13.5px; font-weight: 600; }
.sw-text em {
  display: block;
  font-style: normal;
  font-size: 12px;
  color: var(--muted);
  margin-top: 2px;
}

.sw-state {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  border: 1px solid var(--line-2);
  border-radius: 5px;
  padding: 3px 7px;
  flex: none;
}
.sw.is-on .sw-state { color: var(--accent); border-color: var(--accent); }

@media (max-width: 700px) {
  .sw { max-width: none; }
}

.pip-x {
  flex: none;
  margin-left: 2px;
  border: 0;
  background: none;
  color: var(--faint);
  font-size: 17px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}
.pip-x:hover { color: var(--fg); }

@media (max-width: 560px) {
  .pip { max-width: calc(100% - 24px); margin: 6px auto 12px; border-radius: 12px; }
}

.loading, .empty { padding: 70px 0; color: var(--muted); text-align: center; }

.error {
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  padding: 14px 16px;
  color: var(--muted);
  margin: 20px 0;
  background: var(--bg-2);
}

/* ----------------------------------------------------------------- landing */

/* Narrower than the app's own 1460px: a signed-out page is one column of copy
   and one picture, and at full width those drift so far apart they stop
   reading as a pair. */
/* Sized to the window so there's no scrollbar, with the space that's left
   shared out between the two rows rather than piled at the bottom. Carrying
   less is what makes this possible — see renderLanding. */
.landing {
  max-width: 1120px;
  margin: 0 auto;
  min-height: calc(100vh - 190px); /* header, footer and version badge */
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Spacing scales with the window: roomy on a tall screen, tighter on a short
     one, rather than a fixed value that either wastes space or overflows. */
  gap: clamp(16px, 3.4vh, 58px);
  padding: clamp(6px, 2.6vh, 40px) 0;
}

.lp-hero {
  display: grid;
  /* The panel is the thing being shown; give it the room and let the copy take
     what's left. Widening it costs almost no height, which is the constraint. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 620px);
  gap: clamp(30px, 4vw, 56px);
  align-items: center;
}
/* Both columns fill their track. `.preview` centres itself with `margin: 0
   auto`, which as a grid child shrink-wraps it to its content — it was
   rendering at 383px inside a 620px column. */
.lp-hero > * { width: 100%; min-width: 0; }
.lp-top { max-width: 480px; }
.lp-cta { display: flex; flex-direction: column; align-items: flex-start; }

/* Sits above the headline rather than across the top of the window: a full-
   width banner would be the first and loudest thing on a page whose job is to
   explain what this is. */
.soon {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 16px;
  padding: 5px 12px 5px 10px;
  border-radius: 999px;
  background: var(--gold-dim);
  color: var(--gold);
  box-shadow: inset 0 0 0 1px currentColor;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.soon-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  animation: soon-pulse 2.4s ease-in-out infinite;
}
@keyframes soon-pulse {
  50% { opacity: 0.3; }
}
@media (prefers-reduced-motion: reduce) {
  .soon-dot { animation: none; }
}

.eyebrow {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 14px;
}

.landing h1 {
  font-size: clamp(27px, 3.2vw, 38px);
  line-height: 1.14;
  letter-spacing: -0.03em;
  margin: 0 0 16px;
  font-weight: 700;
  text-wrap: balance;
}
/* The turn of the sentence, picked out rather than shouted. */
.landing h1 em { font-style: normal; color: var(--accent); }

.lp-lede {
  color: var(--muted);
  font-size: 16px;
  line-height: 1.6;
  margin: 0 0 30px;
  max-width: 50ch;
}

.btn-lg { padding: 13px 22px; font-size: 15px; border-radius: 8px; }
.steam-mark { width: 18px; height: 18px; }

.lp-fine { color: var(--faint); font-size: 12.5px; margin: 14px 0 0; }
.landing-notes:not(:empty) {
  max-width: 62ch;
  margin: 26px auto 0;
  text-align: left;
}

/* ---- the mock library ---- */

/* The centrepiece. Everything else on the page is styled to belong to it. */
.preview {
  max-width: 620px;
  margin: 0 auto;
  border: 1px solid var(--line-2);
  border-radius: 13px;
  background: var(--bg-2);
  padding: 0 0 10px;
  overflow: hidden;
  box-shadow: 0 34px 80px -38px rgba(0, 0, 0, 0.95), 0 0 0 1px rgba(63, 224, 171, 0.05);
  animation: pv-in 0.9s cubic-bezier(0.22, 0.75, 0.3, 1) both;
}
@keyframes pv-in {
  from { opacity: 0; transform: translateY(20px); }
}

/* A thin accent along the top edge, so the panel reads as lit rather than
   drawn. */
.preview::before {
  content: '';
  display: block;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), var(--ultra), transparent);
  opacity: 0.75;
}

.pv-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-3);
}
.pv-mark { color: var(--accent); font-size: 11px; }

/* Which part of the app is on show. Reads as the app's own navigation, and
   tells you the panel is touring rather than glitching. */
.pv-tabs { display: flex; gap: 3px; min-width: 0; overflow: hidden; }
.pv-tab {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  padding: 4px 8px;
  border-radius: 5px;
  white-space: nowrap;
  transition: color 0.4s ease, background-color 0.4s ease;
}
.pv-tab.is-on { color: var(--accent); background: var(--accent-dim); }

/* The height is set in script from the incoming scene's own height, and eased
   here — otherwise the panel snapped between scenes of different lengths. */
/* No height transition: the panel is held at the height of its tallest scene
   (see size() in heroPreview) so it never changes shape at all. Easing between
   the heights meant the panel — and everything laid out beside it — grew and
   shrank every nine seconds. */
/*
 * The stage is held at the height of its tallest scene, so the shorter ones
 * have slack — up to sixty-odd pixels on the shortest.
 *
 * That slack is centred rather than spread through the rows. Pushing the rows
 * apart to fill the box does technically fill it, but the space lands in one
 * gap in the middle and the scene reads as broken rather than as full: three
 * leaderboard rows with thirty pixels of nothing between them look like a
 * loading error. Centring splits the difference above and below, where it
 * reads as the panel's own padding.
 */
.pv-stage { overflow: hidden; display: grid; align-items: stretch; }
.pv-scene {
  min-height: 100%;
  align-content: center;
  animation: pv-scene 1.1s cubic-bezier(0.33, 0, 0.2, 1);
}
@keyframes pv-scene {
  from { opacity: 0; transform: translateY(6px); }
  45% { opacity: 0.4; }
}

@media (prefers-reduced-motion: reduce) {
  .pv-scene { animation: none; }
}

/* The live unlock ticker, the same idea as the one in the real header. This is
   what stops the picture sitting there inert. */
.pv-live {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  color: var(--faint);
  text-transform: uppercase;
}
.pv-live i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  animation: pv-pulse 2.4s ease-in-out infinite;
}
@keyframes pv-pulse {
  50% { opacity: 0.25; }
}

/* The unlock that just landed. */
.pv-shot {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 11px 16px;
  /* Sits between the figures and the stage, so it needs an edge on both sides
     — it used to butt against the header, which supplied the one above. */
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: linear-gradient(90deg, var(--accent-dim), transparent 62%);
}
.pv-shot-icon {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  display: grid;
  place-items: center;
  color: var(--accent);
  background: var(--accent-dim);
  box-shadow: inset 0 0 0 1px rgba(63, 224, 171, 0.35);
  flex: none;
}
/* The shared check glyph carries no colour of its own. */
.pv-shot-icon svg { width: 16px; height: 16px; stroke: currentColor; }
/* With real art the tile becomes a frame for it: the tint and inset ring were
   standing in for the picture, and they fight it once it's there. */
.pv-shot-icon.has-art { background: var(--bg-3); box-shadow: inset 0 0 0 1px var(--line-2); padding: 0; overflow: hidden; }
.pv-shot-icon.has-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pv-shot-icon.t-rare { color: var(--gold); background: rgba(240, 180, 92, 0.14); box-shadow: inset 0 0 0 1px rgba(240, 180, 92, 0.38); }
.pv-shot-icon.t-ultra { color: var(--ultra); background: rgba(200, 145, 236, 0.15); box-shadow: inset 0 0 0 1px rgba(200, 145, 236, 0.4); }

.pv-shot-text { min-width: 0; }
.pv-shot-text b { display: block; font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }
.pv-shot-game {
  display: block;
  font-size: 11.5px;
  color: var(--faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* How rare it is, in the app's own colours. */
.pv-chip {
  font-family: var(--mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  padding: 4px 8px;
  border-radius: 999px;
  color: var(--muted);
  background: var(--bg-3);
  box-shadow: inset 0 0 0 1px var(--line-2);
  flex: none;
}
.pv-chip.t-rare { color: var(--gold); background: rgba(240, 180, 92, 0.1); box-shadow: inset 0 0 0 1px rgba(240, 180, 92, 0.4); }
.pv-chip.t-ultra { color: var(--ultra); background: rgba(200, 145, 236, 0.12); box-shadow: inset 0 0 0 1px rgba(200, 145, 236, 0.45); }

/*
 * An unlock arrives, sits long enough to read, and then leaves.
 *
 * It used to fade up in place and stay until the next one faded up over the
 * top of it. With no gap there was nothing to mark one unlock ending and the
 * next beginning, so it read as a single restless element rather than as
 * things happening — and the fade was so soft that the arrival barely
 * registered at all.
 *
 * The row keeps its space while empty. Collapsing it would resize the panel,
 * which is the thing the pinned stage height exists to prevent.
 */
/*
 * The unlock is wiped on from the left, not faded up.
 *
 * Two attempts at this were a fade plus a few pixels of vertical travel, which
 * is what everything else on the page already does — so however the numbers
 * were tuned it kept reading as the same soft nudge. Revealing the row
 * left-to-right is a different motion rather than a different amount of the
 * same one, and it matches what the thing actually is: an achievement toast
 * arriving from the edge.
 */
.pv-shot.is-out { animation: pv-leave 0.3s cubic-bezier(0.5, 0, 1, 1) forwards; }
.pv-shot.is-new { animation: pv-arrive 0.62s cubic-bezier(0.2, 0.75, 0.2, 1); }
@keyframes pv-arrive {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0 0 0 0); }
}
@keyframes pv-leave {
  to { clip-path: inset(0 0 0 100%); opacity: 0.4; }
}

/* A single pass of light across the row as it lands. Ends off the right edge —
   without `forwards` it snaps back to sitting over the middle of the row. */
.pv-shot { position: relative; overflow: hidden; }
.pv-shot.is-new::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(100deg, transparent 34%, rgba(63, 224, 171, 0.18) 50%, transparent 66%);
  animation: pv-sweep 0.95s ease-out forwards;
}
@keyframes pv-sweep {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}

/* A ring flares off the icon as it lands. The tile itself doesn't move —
   scaling it was what made this feel jumpy the last time round. */
.pv-shot.is-new .pv-shot-icon { animation: pv-flare 1s ease-out; }
@keyframes pv-flare {
  from { box-shadow: 0 0 0 0 rgba(63, 224, 171, 0.55); }
  to { box-shadow: 0 0 0 12px rgba(63, 224, 171, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .pv-shot.is-out, .pv-shot.is-new, .pv-shot.is-new .pv-shot-icon { animation: none; }
}

/*
 * The VAC marker. Amber rather than red: it is a fact to weigh, not a verdict,
 * and red is the colour this app uses for something being wrong. Small and
 * outlined so it sits beside a name without shouting over it — the detail is on
 * hover, and the badge only has to be noticeable enough to hover.
 */
/*
 * One badge shape, many tones. Adding a badge is a tone here and an entry in
 * lib/badges.js — nothing else.
 *
 * Named `nametag`, not `badge`: `.badge` was already the "secret achievement"
 * marker further down this file, and being defined later it quietly won every
 * colour these set. The VAC badge rendered with a red background and grey text
 * for exactly that reason.
 *
 * Wrapped in a container of their own so the gap between them is theirs. Loose
 * in a flex row they inherited the parent's 9px gap and read as three separate
 * things scattered along the line rather than one cluster belonging to the name.
 */
.nametags { display: inline-flex; align-items: center; gap: 3px; margin-left: 6px; vertical-align: middle; }
.nametags:empty { display: none; }
/* Glyphs sit lower than capitals do and vary in height between fonts, so they
   get a box of their own to centre in rather than being left on the text
   baseline. Sized a touch bigger than the label, or they read as specks. */
.nt-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  line-height: 1;
  height: 100%;
}
.nametag {
  /* A fixed height, so every badge is the same size whether it carries a word
     or just a glyph — mixed heights beside a name looked like a mistake. The
     name they sit next to is 15px, so 18px keeps them clearly subordinate
     without leaving them floating. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 18px;
  gap: 4px;
  padding: 0 6px;
  border: 1px solid var(--line-2);
  border-radius: 4px;
  background: var(--bg-3);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  cursor: help;
  white-space: nowrap;
}

.nt-accent { border-color: rgba(63, 224, 171, 0.45); background: var(--accent-dim); color: var(--accent); }
.nt-silver { border-color: rgba(195, 204, 210, 0.4); background: var(--silver-dim); color: var(--silver); }
.nt-gold { border-color: rgba(240, 180, 92, 0.55); background: var(--gold-dim); color: var(--gold); }

/*
 * VAC. The only badge here that isn't a compliment, so it is the only one that
 * doesn't share the palette — black with a red edge, against a page where red
 * appears nowhere else. It has to be findable at a glance in a row of green and
 * gold, which an amber chip sitting between two other warm chips was not.
 */
.nt-warn {
  border-color: rgba(255, 90, 90, 0.65);
  background: #17090b;
  color: #ff6b6b;
  box-shadow: 0 0 0 1px rgba(255, 90, 90, 0.12), 0 0 10px -3px rgba(255, 60, 60, 0.5);
}

/*
 * The supporter star. Nobody earns these, so they get the one bit of shine in
 * the set — a gradient face and a soft glow, gold pitched brighter than silver
 * so the tiers read apart at a glance without a word on them.
 */
.nt-supporter, .nt-supporter-gold { border-color: transparent; }
.nt-supporter {
  background: linear-gradient(140deg, rgba(220, 228, 234, 0.26), rgba(150, 162, 172, 0.14));
  color: #e6ecf1;
  box-shadow: 0 0 0 1px rgba(210, 220, 228, 0.35), 0 0 10px -4px rgba(220, 230, 240, 0.6);
}
.nt-supporter-gold {
  background: linear-gradient(140deg, rgba(255, 214, 130, 0.34), rgba(212, 145, 46, 0.2));
  color: #ffd98a;
  box-shadow: 0 0 0 1px rgba(255, 200, 110, 0.5), 0 0 12px -3px rgba(255, 190, 80, 0.75);
}

/* Leaderboard placement. The medal colours for the first three, one shared
   tone past that — fourth is not meaningfully better than fifth. */
.nt-place-gold { border-color: rgba(240, 180, 92, 0.6); background: var(--gold-dim); color: var(--gold); }
.nt-place-silver { border-color: rgba(195, 204, 210, 0.45); background: var(--silver-dim); color: var(--silver); }
.nt-place-bronze { border-color: rgba(201, 138, 94, 0.5); background: var(--bronze-dim); color: var(--bronze); }
.nt-place-top { border-color: var(--line-2); background: var(--bg-3); color: var(--muted); }

/* Likes received. Warmer than the rest of the set — it's the one badge given
   by other people rather than counted off your library. */
.nt-thanked-bronze { border-color: rgba(201, 138, 94, 0.45); background: var(--bronze-dim); color: var(--bronze); }
.nt-thanked-silver { border-color: rgba(195, 204, 210, 0.4); background: var(--silver-dim); color: var(--silver); }
.nt-thanked-gold { border-color: rgba(240, 180, 92, 0.55); background: var(--gold-dim); color: var(--gold); }
.nt-thanked-ultra { border-color: rgba(200, 145, 236, 0.5); background: rgba(200, 145, 236, 0.14); color: var(--ultra); }

/* Perfect-game tiers, stepping up a colour at a time the way a Steam profile
   level does. The number on it is the real count; only the look changes. */
.nt-perfect-bronze { border-color: rgba(201, 138, 94, 0.45); background: var(--bronze-dim); color: var(--bronze); }
.nt-perfect-silver { border-color: rgba(195, 204, 210, 0.4); background: var(--silver-dim); color: var(--silver); }
.nt-perfect-gold { border-color: rgba(240, 180, 92, 0.55); background: var(--gold-dim); color: var(--gold); }
.nt-perfect-accent { border-color: rgba(63, 224, 171, 0.5); background: var(--accent-dim); color: var(--accent); }
.nt-perfect-ultra { border-color: rgba(200, 145, 236, 0.5); background: rgba(200, 145, 236, 0.14); color: var(--ultra); }
/* The top tier is the only one that isn't a flat colour — it should look like
   something you don't see often. */
.nt-perfect-prism {
  border-color: transparent;
  background: linear-gradient(100deg, rgba(63, 224, 171, 0.22), rgba(200, 145, 236, 0.22) 45%, rgba(240, 180, 92, 0.22));
  color: var(--fg);
  box-shadow: 0 0 0 1px rgba(200, 145, 236, 0.4), 0 0 12px -4px rgba(200, 145, 236, 0.55);
}

/* One block per group of settings, so the page reads as a list of decisions
   rather than a wall of switches. */
.pref-block { margin: 0 0 34px; }
.pref-block h2 { font-size: 17px; font-weight: 700; letter-spacing: -0.01em; margin: 0 0 4px; }
.pref-block .lede { margin: 0 0 14px; max-width: 62ch; }
.pref-switches { display: grid; gap: 10px; max-width: 560px; }
.pref-cool { display: grid; gap: 5px; }
/* Says why, under the switch it applies to. Amber rather than red: this is a
   wait, not a refusal — and going quieter is still allowed right now. */
.pref-cool-note {
  padding-left: 2px;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--gold);
}

/* ---- "coming soon" -------------------------------------------------------
 *
 * Blocks, not a gradient: the site's own voxel pattern doing the work. They
 * jump between positions on a steps() clock rather than sliding, which is what
 * makes it read as retro rather than as a loading shimmer.
 */
.sp-veil {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(4, 6, 7, 0.72);
  backdrop-filter: blur(2px);
  animation: sp-in 0.18s ease-out;
}
@keyframes sp-in { from { opacity: 0; } }

.sp-panel {
  position: relative;
  width: min(440px, 100%);
  overflow: hidden;
  border: 1px solid var(--line-2);
  border-radius: 12px;
  background: var(--bg-2);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
}
.sp-blocks { position: absolute; inset: 0; overflow: hidden; }
.sp-block {
  position: absolute;
  width: 14px;
  height: 14px;
  background: var(--accent);
  opacity: 0;
  /* steps() is the whole effect — it teleports between frames instead of
     easing, the way a low-framerate sprite would. */
  animation-name: sp-blip;
  animation-timing-function: steps(4, end);
  animation-iteration-count: infinite;
}
@keyframes sp-blip {
  0% { opacity: 0; transform: translate(0, 0) scale(0.6); }
  20% { opacity: 0.5; }
  50% { opacity: 0.16; transform: translate(26px, -18px) scale(1); }
  80% { opacity: 0.42; }
  100% { opacity: 0; transform: translate(52px, -36px) scale(0.6); }
}
.sp-block:nth-child(3n) { background: var(--ultra); width: 10px; height: 10px; }
.sp-block:nth-child(4n) { background: var(--gold); width: 18px; height: 18px; }

.sp-body { position: relative; padding: 34px 30px 28px; text-align: center; }
.sp-kicker {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--faint);
}
.sp-title {
  margin: 8px 0 12px;
  font-family: var(--mono);
  font-size: 30px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--accent);
  text-shadow: 0 0 22px rgba(63, 224, 171, 0.45);
  animation: sp-flicker 3.4s steps(1, end) infinite;
}
@keyframes sp-flicker {
  0%, 92%, 96%, 100% { opacity: 1; }
  94% { opacity: 0.55; }
}
.sp-note { margin: 0 auto 22px; max-width: 34ch; font-size: 13px; color: var(--muted); line-height: 1.55; }
.sp-close {
  padding: 7px 20px;
  border: 1px solid var(--line-2);
  border-radius: 7px;
  background: var(--bg-3);
  color: var(--fg);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.sp-close:hover { border-color: var(--accent); color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .sp-block, .sp-title, .sp-veil { animation: none; }
  .sp-block { opacity: 0.22; }
}

/* Reporting a guide. Quiet by default — the rarest action on the row, and a
   loud report button invites reports rather than waiting for a warranted one. */
.report-btn { color: var(--faint); border-color: var(--line); }
.report-btn:hover { color: var(--gold); border-color: rgba(240, 180, 92, 0.45); }
.report-form {
  display: grid;
  gap: 8px;
  margin-top: 10px;
  padding: 12px 14px;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: var(--bg-3);
  max-width: 420px;
}
.report-reason, .report-note {
  padding: 7px 9px;
  border: 1px solid var(--line-2);
  border-radius: 6px;
  background: var(--bg-2);
  color: var(--fg);
  font: inherit;
  font-size: 13px;
}
.report-acts { display: flex; gap: 8px; }

.badge-prefs { margin: 22px 0 4px; }
.badge-pref-list { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.badge-pref {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px 8px 10px;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: var(--bg-2);
  cursor: pointer;
}
.badge-pref input { width: 14px; height: 14px; accent-color: var(--accent); cursor: pointer; }
.badge-pref .nametag { margin-left: 0; }
.bp-note { font-size: 12px; color: var(--faint); }
.badge-pref.is-fixed { cursor: default; }
/* Earned but not on show, because the account isn't listed. Dimmed so it reads
   as available-but-inactive rather than as something that's broken. */
/* Not yours yet. Dimmed further than a gated one — that's earned but inactive,
   this you don't have at all — but the badge itself keeps its colour so you can
   see what you'd be getting. */
.badge-pref.is-locked { opacity: 0.6; cursor: default; }
.badge-pref.is-locked input { cursor: default; }
.bp-how {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  border-bottom: 1px solid rgba(63, 224, 171, 0.4);
}
.bp-how:hover { color: var(--fg); border-bottom-color: var(--fg); }

.badge-pref.is-gated { opacity: 0.72; }
.badge-pref.is-gated .nametag { filter: saturate(0.6); }
.bp-fixed {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}

/* Site-wide count on the live ticker, kept quieter than the achievement name. */
.feed-here {
  margin-left: 9px;
  color: var(--faint);
  font-size: 11.5px;
}

.pv-stats-label {
  padding: 11px 17px 0;
  border-top: 1px solid var(--line);
}
/* Four across, so it wraps to two rows rather than squeezing four big numbers
   into a strip that only ever held three. */
.pv-stats { display: grid; grid-template-columns: repeat(4, 1fr); }
.pv-stat { padding: 13px 14px; border-right: 1px solid var(--line); }
.pv-stat:last-child { border-right: 0; }
@media (max-width: 560px) {
  .pv-stats { grid-template-columns: repeat(2, 1fr); }
  .pv-stat:nth-child(2) { border-right: 0; }
  .pv-stat:nth-child(3), .pv-stat:nth-child(4) { border-top: 1px solid var(--line); }
}
.pv-stat strong {
  display: block;
  font-size: 29px;
  font-weight: 700;
  letter-spacing: -0.035em;
  margin-top: 7px;
  font-variant-numeric: tabular-nums;
}
.pv-stat.is-gold strong { color: var(--gold); }
.pv-stat.is-accent strong { color: var(--accent); }
/* A tick of the counter, so the number is seen to move. */
.pv-stat.is-bumped strong { animation: pv-bump 0.55s ease; }
@keyframes pv-bump {
  40% { transform: translateY(-3px); opacity: 0.75; }
}

/* The community steps. This is the part of the app worth showing off, so it
   gets the room the example progress bars used to take. */
.pv-guide { padding: 16px; }
.pv-guide-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 9px;
}
.pv-guide-name { display: block; font-size: 14.5px; font-weight: 600; margin-bottom: 12px; }

.pv-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.pv-steps li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 10px;
  align-items: start;
  font-size: 13px;
  color: var(--muted);
  line-height: 1.45;
}
.pv-tick {
  width: 16px;
  height: 16px;
  margin-top: 1px;
  border-radius: 5px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line-2);
  color: var(--accent);
}
.pv-tick svg { width: 9px; height: 9px; stroke: currentColor; }
.pv-steps li.is-done .pv-tick { border-color: var(--accent); background: var(--accent-dim); }
.pv-steps li.is-done { color: var(--fg); }

.pv-credit {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--faint);
}
.pv-credit b { color: var(--muted); font-weight: 600; }
.pv-helpful { display: inline-flex; align-items: center; gap: 6px; color: var(--accent); }
.pv-helpful svg { width: 10px; height: 10px; stroke: currentColor; }

/* ---- review scene ---- */
.pv-review { padding: 16px; }
.pv-review-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 9px;
}
.pv-review-note {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
  padding-left: 11px;
  border-left: 2px solid var(--line-2);
}
.pv-review-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--faint);
}
.pv-review-foot b { color: var(--muted); font-weight: 600; }
.pv-votes { display: inline-flex; gap: 6px; }
.pv-vote {
  font-family: var(--mono);
  font-size: 11px;
  padding: 3px 8px;
  border-radius: 5px;
  border: 1px solid var(--line-2);
  color: var(--faint);
}
.pv-vote.is-up { color: var(--accent); border-color: var(--accent); background: var(--accent-dim); }

/* ---- rarity scene: the one place these colours mean anything ---- */
.pv-rares { padding: 14px 16px; display: grid; gap: 2px; }
.pv-rare-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 11px;
  align-items: center;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
}
.pv-rare-row:last-child { border-bottom: 0; }
.pv-rare-name { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pv-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--line-2); }
.pv-dot.t-rare { background: var(--gold); box-shadow: 0 0 8px -1px var(--gold); }
.pv-dot.t-ultra { background: var(--ultra); box-shadow: 0 0 8px -1px var(--ultra); }

/* ---- leaderboard scene ---- */
.pv-board { padding: 14px 16px; display: grid; gap: 2px; }
.pv-board-row {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto;
  gap: 11px;
  align-items: center;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
}
.pv-board-row:last-child { border-bottom: 0; }
.pv-rank {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--faint);
  text-align: center;
}
.pv-board-name { font-size: 13px; }
.pv-board-n {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.pv-board-row.medal-1 .pv-rank, .pv-board-row.medal-1 .pv-board-name, .pv-board-row.medal-1 .pv-board-n { color: var(--gold); }
.pv-board-row.medal-2 .pv-rank, .pv-board-row.medal-2 .pv-board-name, .pv-board-row.medal-2 .pv-board-n { color: var(--silver, #c7ced4); }
.pv-board-row.medal-3 .pv-rank, .pv-board-row.medal-3 .pv-board-name, .pv-board-row.medal-3 .pv-board-n { color: var(--bronze, #cd8f52); }

.pv-rows { padding: 2px 16px 0; }
.pv-row {
  display: grid;
  grid-template-columns: 46px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.pv-art {
  height: 30px;
  border-radius: 4px;
  /* The gradient shows through while the capsule loads, and stays as the whole
     picture for a game Steam has no art for — so a missing image degrades to
     what this used to be rather than to a gap. */
  background: linear-gradient(135deg, var(--line-2), var(--bg-3));
}
/* Steam's capsule is 231x87; the column is 46px wide, so it has to be cropped
   rather than squashed — a stretched capsule is more obviously wrong than a
   cropped one. */
img.pv-art {
  width: 46px;
  object-fit: cover;
  display: block;
}
.pv-art.t-rare { background: linear-gradient(135deg, rgba(245, 176, 65, 0.5), var(--bg-3)); }
.pv-art.t-ultra { background: linear-gradient(135deg, rgba(167, 120, 255, 0.5), var(--bg-3)); }
.pv-name { font-size: 13.5px; margin-bottom: 8px; }
/* The filled part of a bar glows a little, so a finished game reads across the
   panel rather than needing to be measured against the others. */
.pv-row .meter > i { box-shadow: 0 0 10px -1px currentColor; color: var(--accent); }
.pv-row .meter.m-rare > i { color: var(--gold); }
.pv-row .meter.m-ultra > i { color: var(--ultra); }
.pv-row.is-perfect .meter > i { color: var(--gold); }
.pv-row.is-perfect .pv-name { color: var(--accent); }
.pv-pct {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.pv-row.is-perfect .pv-pct { color: var(--accent); }
/* The bars creep up as the fake unlocks land. */
.pv-row .meter > i { transition: width 0.7s cubic-bezier(0.22, 0.75, 0.3, 1); }

/* Anyone who has asked for less movement gets the picture, and none of the
   motion. */
@media (prefers-reduced-motion: reduce) {
  .preview, .pv-live i, .pv-ticker.is-new, .pv-stat.is-bumped strong { animation: none; }
  .pv-row .meter > i { transition: none; }
}

/* ---- what you get ---- */

/* Panels cut from the same cloth as the preview above: same border, same
   surface, same radius — so the page reads as one piece rather than a hero
   with a list bolted underneath. */
.features {
  list-style: none;
  max-width: 1000px;
  margin: 0 auto;
  padding: 0;
  display: grid;
  /* Four across, so it stands the same height as the other panel in the deck.
     Fixed rather than auto-fit: with four cards, auto-fit chose three columns
     at most widths and left the fourth stranded on a row of its own. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}
.features li {
  display: block;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: var(--bg-2);
  padding: 13px 15px 15px;
}
.features .feat-mark { margin-bottom: 9px; width: 26px; height: 26px; }
.features p { font-size: 12.5px; }
.features li:hover { border-color: var(--line-2); }
.features b { display: block; font-size: 14px; font-weight: 600; margin-bottom: 5px; }
.features p { color: var(--muted); margin: 0; font-size: 13.5px; line-height: 1.55; }

/* ---- how the guides get written ---- */

/* ---- the alternating band under the hero ---- */

.deck {
  border-top: 1px solid var(--line);
  padding-top: clamp(15px, 2.4vh, 32px);
}
/* Height is pinned in script to the taller panel — see size() in landingDeck.
   Nothing here animates it, because the page is centred and a height change
   moves everything above it. */
.deck-stage { display: grid; align-content: start; }
/* Both panels centre themselves with `margin: 0 auto`, which as a grid child
   shrink-wraps them to their content instead of filling the band — the three
   steps collapsed into a single stacked column and the band doubled in height.
   The explicit width restores it; max-width still does the centring. */
.deck-stage > * { width: 100%; }
.deck-panel { animation: deck-in 0.85s cubic-bezier(0.33, 0, 0.2, 1); }
@keyframes deck-in {
  from { opacity: 0; transform: translateY(6px); }
  45% { opacity: 0.4; }
}

.deck-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  margin-top: 11px;
}
.deck-dots { display: flex; gap: 7px; }
.deck-dot {
  width: 26px;
  height: 4px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--line-2);
  cursor: pointer;
  transition: background-color 0.3s ease, width 0.3s ease;
}
.deck-dot:hover { background: var(--faint); }
.deck-dot.is-on { background: var(--accent); width: 38px; }
.deck-pause {
  background: none;
  border: 0;
  padding: 2px 4px;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}
.deck-pause:hover { color: var(--fg); }
.deck-dot:focus-visible, .deck-pause:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  .deck-stage { transition: none; }
  .deck-panel { animation: none; }
}

.cp {
  max-width: 1000px;
  margin: 0 auto;
}
.cp h2 {
  font-size: 16.5px;
  font-weight: 600;
  line-height: 1.35;
  margin: 0 0 18px;
  text-align: center;
  text-wrap: balance;
}
.cp-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 22px 40px;
}
.cp-steps li { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 13px; align-items: start; }
.cp-steps b { display: block; font-size: 14px; font-weight: 600; margin-bottom: 5px; }
.cp-steps p { color: var(--muted); margin: 0; font-size: 13.5px; line-height: 1.55; }
.cp-n {
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--accent);
  color: var(--accent);
  font-family: var(--mono);
  font-size: 11.5px;
}
.cp-foot {
  margin: 22px 0 0;
  text-align: center;
  color: var(--faint);
  font-size: 12.5px;
}

.feat-mark {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border-radius: 7px;
  background: var(--accent-dim);
  color: var(--accent);
  font-size: 13px;
}

/* Below this the two columns won't both fit, so it stacks and centres — and a
   phone scrolls, which is normal there. */
@media (max-width: 940px) {
  .landing { min-height: 0; display: block; padding: 40px 0; }
  .lp-hero { grid-template-columns: minmax(0, 1fr); gap: 38px; margin-bottom: 44px; }
  .lp-top { max-width: none; text-align: center; }
  .lp-lede { margin-left: auto; margin-right: auto; }
  .lp-cta { align-items: center; }
}

@media (max-width: 620px) {
  .features { grid-template-columns: minmax(0, 1fr); }
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--accent);
  color: #04120c;
  border: 0;
  border-radius: 7px;
  padding: 11px 18px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.btn:hover { opacity: 0.9; }

.btn-ghost {
  background: none;
  color: var(--muted);
  border: 1px solid var(--line-2);
  padding: 6px 11px;
  border-radius: 6px;
  font-size: 12.5px;
  cursor: pointer;
}
.btn-ghost:hover { color: var(--fg); border-color: var(--faint); }
.btn-ghost.is-on { color: var(--accent); border-color: var(--accent); }

/* The theme-button easter egg. Slides in from the bottom corner like the real
   thing does in-game, then sees itself out. */
.egg {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 50;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 13px;
  max-width: min(380px, calc(100vw - 40px));
  padding: 13px 15px;
  border: 1px solid var(--line-2);
  border-radius: 11px;
  background: var(--bg-2);
  box-shadow: 0 24px 60px -26px rgba(0, 0, 0, 0.95);
  transform: translateY(140%);
  opacity: 0;
  transition: transform 0.55s cubic-bezier(0.22, 0.75, 0.3, 1), opacity 0.45s ease;
}
.egg.is-in { transform: translateY(0); opacity: 1; }
.egg-icon {
  width: 38px;
  height: 38px;
  border-radius: 9px;
  display: grid;
  place-items: center;
  color: var(--ultra);
  background: rgba(200, 145, 236, 0.15);
  box-shadow: inset 0 0 0 1px rgba(200, 145, 236, 0.4);
}
.egg-icon svg { width: 17px; height: 17px; stroke: currentColor; }
.egg-text { min-width: 0; }
.egg-text b { display: block; font-size: 14px; font-weight: 600; margin: 3px 0 2px; }
.egg-desc { display: block; font-size: 12px; color: var(--faint); }

@media (prefers-reduced-motion: reduce) {
  .egg { transition: opacity 0.3s ease; transform: none; }
}

/* Steam confirmed the author held the achievement when they wrote the steps.
   Not a requirement for writing them — see authorHasIt in lib/community.js —
   so it's shown as a mark of confidence rather than a badge of authority. */
.has-it {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin: 0 2px;
  padding: 2px 7px;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--accent);
  background: var(--accent-dim);
  vertical-align: middle;
}
.has-it svg { width: 8px; height: 8px; stroke: currentColor; }

.demo-note {
  margin-top: 24px;
  font-size: 13px;
  color: var(--faint);
  border-left: 2px solid var(--line-2);
  padding-left: 12px;
}

/* -------------------------------------------------------------- page head */

.page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  padding: 34px 0 20px;
}
.page-head h1 { font-size: 34px; font-weight: 700; letter-spacing: -0.025em; margin: 0 0 8px; }
.page-head .lede { color: var(--muted); margin: 0; font-size: 13.5px; max-width: 46ch; }

/* --------------------------------------------------------------- stat strip */

.stats {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-2);
  overflow: hidden;
}
.stat { padding: 15px 18px; border-left: 1px solid var(--line); }
.stat:first-child { border-left: 0; }
.stat-v { font-size: 25px; font-weight: 700; letter-spacing: -0.02em; margin-top: 7px; }
.stat-v small {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  color: var(--faint);
  margin-left: 3px;
}
.stat.is-accent .stat-v { color: var(--accent); }
.stat.is-gold .stat-v { color: var(--gold); }

/* While the library is being measured these figures are still climbing, so
   they're dimmed and gently pulsed rather than presented as final. */
.is-measuring .stat-v {
  color: var(--muted);
  animation: stat-pending 1.8s ease-in-out infinite;
}
@keyframes stat-pending {
  50% { opacity: 0.55; }
}

/* ------------------------------------------------------- measure progress */

.measure-host:not(:empty) { margin-top: 14px; }

.measure {
  border: 1px solid var(--line-2);
  border-radius: 8px;
  padding: 11px 13px 13px;
  background: var(--bg-2);
}
.measure-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.measure-text { font-size: 12.5px; color: var(--fg); }
.measure-count {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.measure .meter { margin-top: 10px; }
/* Faster than the library bars: this one moves constantly and the slow ease
   makes it look like it's lagging behind the count next to it. */
.measure .meter > i { transition: width 0.25s linear; }

/* ------------------------------------------------------------------ shell */

.shell {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  gap: 34px;
  padding-top: 26px;
}

.side section { margin-bottom: 26px; }
.side h4 { margin: 0 0 9px; font-weight: 400; }
.side-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  text-align: left;
  padding: 7px 11px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  font-size: 13.5px;
  cursor: pointer;
}
.side-item:hover { color: var(--fg); background: var(--bg-2); }
.side-item em { font-family: var(--mono); font-style: normal; font-size: 11.5px; color: var(--faint); }
.side-item.on {
  background: var(--bg-2);
  color: var(--fg);
  font-weight: 600;
  box-shadow: inset 2px 0 0 var(--accent);
}
.side-item.on em { color: var(--accent); }

.side-buttons { display: grid; gap: 8px; margin-top: 14px; }
.side-buttons .btn-ghost { text-align: center; }

.seg { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.seg button {
  padding: 7px 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: none;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  cursor: pointer;
}
.seg button:hover { color: var(--fg); }
.seg button.on { background: var(--bg-3); color: var(--fg); border-color: var(--line-2); }

/* ---------------------------------------------------- library search */

.lib-search { position: relative; margin-bottom: 16px; }

.lib-search-box {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 42px;
  padding: 0 13px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--bg-2);
}
.lib-search-box:focus-within { border-color: var(--line-2); }
.lib-search-box svg { width: 15px; height: 15px; color: var(--faint); flex: none; }
.lib-search-input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  outline: none;
  font-size: 14px;
}
.lib-search-input::placeholder { color: var(--faint); }
.lib-search-input::-webkit-search-cancel-button { display: none; }
.lib-search-box kbd {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--faint);
  border: 1px solid var(--line-2);
  border-radius: 4px;
  padding: 1px 5px;
  flex: none;
}

/* Suggestions. Overlays the list rather than pushing it down, so the rows
   underneath don't jump around while you type. */
.suggest {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 30;
  display: none;
  flex-direction: column;
  padding: 5px;
  border: 1px solid var(--line-2);
  border-radius: 9px;
  background: var(--bg-2);
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.55);
}
.suggest.open { display: flex; }

.suggest-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 7px 8px;
  border-radius: 6px;
}
.suggest-row.on { background: var(--bg-3); }
.suggest-art {
  width: 62px;
  height: 29px;
  object-fit: cover;
  border-radius: 4px;
  flex: none;
  background: var(--bg-3);
}
.suggest-name {
  flex: 1;
  min-width: 0;
  font-size: 13.5px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.suggest-meta { font-size: 12px; color: var(--faint); flex: none; }
.suggest-none { padding: 12px 10px; font-size: 13px; color: var(--faint); }

.list-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 11px;
  border-bottom: 1px solid var(--line);
}

/* ------------------------------------------------------------- game rows */

.row {
  display: grid;
  grid-template-columns: 148px minmax(0, 1fr) 88px 92px 14px;
  align-items: center;
  gap: 0 18px;
  padding: 13px 6px;
  border-bottom: 1px solid var(--line);
}
.row:hover { background: var(--bg-2); }

.cover {
  width: 148px;
  height: 69px;
  object-fit: cover;
  border-radius: 5px;
  display: block;
  background: var(--bg-3);
}
.cover-dead {
  display: grid;
  place-items: center;
  color: var(--faint);
  font-family: var(--mono);
  font-size: 13px;
  border: 1px solid var(--line);
}
/* Icon fallback for games with no store art: the same icon blurred up to fill
   the tile, with the sharp 32px copy centred on it. Fills the frame with the
   game's own colours instead of leaving a small square floating in space. */
.cover-fallback {
  position: relative;
  overflow: hidden;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  background: var(--bg-3);
}
.cover-fallback::before {
  content: "";
  position: absolute;
  inset: -30%;
  background-image: var(--art);
  background-size: cover;
  background-position: center;
  /* Heavier blur and lower opacity: the source is only 32px, so a light blur
     reads as blotches rather than as a wash of the game's colour. */
  filter: blur(20px) saturate(1.2);
  opacity: 0.38;
}
/* Darkens the edges so the tile resolves to a centred badge instead of an
   evenly muddy rectangle. */
.cover-fallback::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0) 35%, rgba(0, 0, 0, 0.4) 100%);
}
:root[data-theme="light"] .cover-fallback::after {
  background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0) 35%, rgba(255, 255, 255, 0.5) 100%);
}

/* Sized against the tile rather than pinned to 32px, so it stays proportionate
   in the tall grid card as well as the short list row. */
.cover-icon {
  position: relative;
  z-index: 1;
  height: clamp(32px, 46%, 54px);
  width: auto;
  aspect-ratio: 1;
  border-radius: 7px;
  display: block;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.55);
}
/* The dropdown tile is 29px tall, so the 32px floor above would push the icon
   out of its own frame. Sized for this one explicitly rather than lowering the
   floor everywhere — 32px is right in the library, where the icon is the only
   thing identifying the game. */
.suggest-art .cover-icon { height: 19px; border-radius: 4px; box-shadow: 0 1px 5px rgba(0, 0, 0, 0.5); }

.row-main { min-width: 0; }
.row-title { display: flex; align-items: center; gap: 9px; }
.row-title b { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.row-meta {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--faint);
  margin-top: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tag {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border: 1px solid currentColor;
  border-radius: 4px;
  padding: 2px 6px;
  flex: none;
  /* Sits inside a heading that wraps on long titles. Without this the pill
     wrapped inside itself — "5" on one line and "LEFT" on the next, in its own
     box — which is what it did next to "SpongeBob SquarePants: Battle for
     Bikini Bottom - Rehydrated" at a narrow width. */
  white-space: nowrap;
  display: inline-block;
  vertical-align: middle;
}
.tag-perfect { color: var(--gold); }
.tag-left { color: var(--accent); }

.meter { height: 3px; border-radius: 3px; background: var(--line); overflow: hidden; margin-top: 9px; }
.meter > i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 3px;
  background: var(--accent);
  transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.is-perfect .meter > i { background: var(--gold); }

.col { text-align: right; }
.col .k { display: block; margin-bottom: 5px; }
.col-v { font-family: var(--mono); font-size: 13px; font-variant-numeric: tabular-nums; color: var(--muted); }
.col-v b { color: var(--fg); font-weight: 600; }
.col-pct { font-family: var(--mono); font-size: 13px; font-weight: 700; color: var(--accent); }
.is-perfect .col-pct { color: var(--gold); }
.chev { color: var(--faint); font-size: 15px; text-align: right; }
.row:hover .chev { color: var(--fg); }

/* ------------------------------------------------------------ leaderboard */

/* Board switcher, sitting where the library's controls sit. */
.board-bar { margin: 22px 0 14px; }

/* The explanation, kept at the bottom and quiet — the same weight as the
   disclaimer under the library rather than a slab above the table. */
.board-note {
  color: var(--faint);
  font-size: 11.5px;
  line-height: 1.6;
  max-width: 76ch;
  margin: 22px 0 0;
}

.row.board-row {
  grid-template-columns: 34px 44px minmax(0, 1fr) auto;
  gap: 0 16px;
}
.board-row.is-me { background: var(--accent-dim); box-shadow: inset 2px 0 0 var(--accent); }
.board-row.is-me:hover { background: var(--accent-dim); }

/* The three figures travel together, so they can sit in a right-hand block on a
   wide screen and drop under the name on a narrow one. */
.board-figs { display: flex; align-items: flex-start; gap: 26px; }
.board-figs .col { min-width: 76px; }

.rank { font-size: 15px; font-weight: 700; color: var(--faint); text-align: right; }

/* Podium: the whole row carries the metal, not just the number. Classes rather
   than :nth-child, so the medal follows the top three however the list is
   built. Declared after .is-me so a medal still shows on your own row — the
   "You" tag is what identifies it. */
.board-row.medal-1 { background: var(--gold-dim); box-shadow: inset 3px 0 0 var(--gold); }
.board-row.medal-2 { background: var(--silver-dim); box-shadow: inset 3px 0 0 var(--silver); }
.board-row.medal-3 { background: var(--bronze-dim); box-shadow: inset 3px 0 0 var(--bronze); }
.board-row.medal-1:hover { background: var(--gold-dim); }
.board-row.medal-2:hover { background: var(--silver-dim); }
.board-row.medal-3:hover { background: var(--bronze-dim); }

.board-row.medal-1 .rank, .board-row.medal-1 .row-title b { color: var(--gold); }
.board-row.medal-2 .rank, .board-row.medal-2 .row-title b { color: var(--silver); }
.board-row.medal-3 .rank, .board-row.medal-3 .row-title b { color: var(--bronze); }

.board-row.medal-1 .col-pct { color: var(--gold); }
.board-row.medal-2 .col-pct { color: var(--silver); }
.board-row.medal-3 .col-pct { color: var(--bronze); }

.board-row.medal-1 .board-avatar { border-color: var(--gold); }
.board-row.medal-2 .board-avatar { border-color: var(--silver); }
.board-row.medal-3 .board-avatar { border-color: var(--bronze); }
.board-avatar {
  width: 44px;
  height: 44px;
  border-radius: 6px;
  display: grid;
  place-items: center;
  background: var(--bg-3);
  border: 1px solid var(--line);
  object-fit: cover;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--faint);
}

/* The figures drop below the name and align under it, instead of being squeezed
   into columns too narrow to read.
   (The rules here previously tried to hide columns with :nth-of-type, which
   counts every div in the row rather than only the .col ones — so it matched
   nothing and the row stayed crammed.) */
@media (max-width: 760px) {
  /* .row.board-row, not .board-row: the game-row override further down this
     file has the same specificity and would otherwise win on source order,
     which is exactly what was crushing these rows. */
  .row.board-row { grid-template-columns: 24px 36px minmax(0, 1fr); gap: 0 12px; }
  .board-avatar { width: 36px; height: 36px; }
  .board-figs {
    grid-column: 1 / -1;
    margin-top: 10px;
    padding-left: 72px;
    gap: 22px;
  }
  .board-figs .col { text-align: left; min-width: 0; }
}

/* -------------------------------------------------------------- grid view */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(212px, 1fr));
  gap: 16px;
  padding-top: 18px;
}
.card {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg-2);
  overflow: hidden;
  display: block;
}
.card:hover { border-color: var(--line-2); }
.card .cover { width: 100%; height: auto; aspect-ratio: 460 / 215; border-radius: 0; }
.card-body { padding: 12px 13px 13px; }
.card-body .row-title { margin-bottom: 2px; }
.card-body .row-title b { font-size: 14px; }
.card-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-top: 10px;
}

/* -------------------------------------------------------------- game view */

.crumb {
  display: inline-block;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 26px 0 14px;
}
.crumb:hover { color: var(--fg); }

.hero {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-2);
}
/* Full strength. It was at 0.42 and then dimmed again by a gradient covering
   the whole box, which turned bright store art into brown mud. */
.hero-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Darkens only the lower band, where the title and percentage sit. The top two
   thirds of the artwork are left alone. */
.hero-in {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  min-height: 224px;
  padding: 26px 28px 30px;
  background: linear-gradient(
    to top,
    rgba(8, 10, 11, 0.93) 0%,
    rgba(8, 10, 11, 0.72) 30%,
    rgba(8, 10, 11, 0.22) 62%,
    rgba(8, 10, 11, 0) 88%
  );
}
:root[data-theme="light"] .hero-in {
  background: linear-gradient(
    to top,
    rgba(255, 255, 255, 0.95) 0%,
    rgba(255, 255, 255, 0.78) 30%,
    rgba(255, 255, 255, 0.25) 62%,
    rgba(255, 255, 255, 0) 88%
  );
}
/* The art now sits at full strength behind the title, so the text needs its own
   contrast rather than relying on a dimmed backdrop. */
.hero h1 { text-shadow: 0 2px 14px rgba(0, 0, 0, 0.75); }
.hero-pct b { text-shadow: 0 2px 14px rgba(0, 0, 0, 0.75); }
:root[data-theme="light"] .hero h1,
:root[data-theme="light"] .hero-pct b { text-shadow: 0 2px 14px rgba(255, 255, 255, 0.8); }
.hero h1 { font-size: 33px; font-weight: 700; letter-spacing: -0.025em; margin: 0 0 7px; }
.hero .row-meta { margin: 0; }
.hero-pct { text-align: right; flex: none; }
.hero-pct b { display: block; font-size: 38px; font-weight: 700; letter-spacing: -0.03em; color: var(--accent); }
.hero.is-perfect .hero-pct b { color: var(--gold); }
.hero-bar { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: var(--line); }
.hero-bar > i { display: block; height: 100%; background: var(--accent); }
.hero.is-perfect .hero-bar > i { background: var(--gold); }

.game-stats { margin-top: 18px; }

.ach-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin: 30px 0 14px;
}
.ach-head h2 { font-size: 19px; font-weight: 700; letter-spacing: -0.02em; margin: 0; }
.chips { display: flex; gap: 7px; flex-wrap: wrap; }
.chip {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 6px 9px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.chip:hover { color: var(--fg); border-color: var(--line-2); }
.chip[aria-selected="true"] { color: var(--accent); border-color: var(--accent); background: var(--accent-dim); }

/* ------------------------------------------------------------- dropdowns */

.sel { position: relative; display: inline-block; }
.sel-block { display: block; flex: 1; min-width: 0; }

.sel-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 6px 9px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.sel-btn:hover, .sel.open .sel-btn { color: var(--fg); border-color: var(--line-2); }
.sel-chev { width: 12px; height: 12px; flex: none; transition: transform 0.18s ease; }
.sel.open .sel-chev { transform: rotate(180deg); }

.sel-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 100%;
  width: max-content;
  max-width: 260px;
  z-index: 45;
  display: none;
  flex-direction: column;
  padding: 5px;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: var(--bg-2);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.5);
}
.sel.open .sel-menu { display: flex; }

.sel-item {
  text-align: left;
  white-space: nowrap;
  border: 0;
  background: none;
  border-radius: 5px;
  padding: 8px 10px;
  font-size: 13px;
  color: var(--muted);
  cursor: pointer;
}
.sel-item:hover { background: var(--bg-3); color: var(--fg); }
.sel-item.on { color: var(--accent); }

/* The library's sort sits in a row of taller controls, so it matches them. */
.tool-row .sel-btn {
  height: 34px;
  padding: 0 11px;
  border-radius: 6px;
  background: var(--bg-2);
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
}

.filter {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 11px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--bg-2);
  min-width: 210px;
}
.filter svg { width: 13px; height: 13px; color: var(--faint); flex: none; }
.filter input { flex: 1; min-width: 0; border: 0; background: none; outline: none; font-size: 13px; }
.filter input::placeholder { color: var(--faint); }

/* ----------------------------------------------------------- achievements */

.list { border-top: 1px solid var(--line); }
.ach { border-bottom: 1px solid var(--line); }

.ach-row {
  display: grid;
  grid-template-columns: 20px 44px minmax(0, 1fr) 108px 92px;
  align-items: center;
  gap: 0 14px;
  padding: 12px 6px;
  cursor: pointer;
}
.ach-row:hover { background: var(--bg-2); }
.ach.done > .ach-row { background: var(--accent-dim); box-shadow: inset 2px 0 0 var(--accent); }
.ach.done > .ach-row:hover { background: var(--accent-dim); }

/* Steam's answer, shown rather than offered. It used to be a button you could
   tick yourself; that tick counted towards totals that get ranked publicly, so
   it's gone. No pointer, no hover — nothing here invites a click. */
.mark {
  width: 16px;
  height: 16px;
  border: 1.5px solid var(--line-2);
  border-radius: 4px;
  background: none;
  padding: 0;
  display: grid;
  place-items: center;
  flex: none;
}
.mark svg { width: 10px; height: 10px; opacity: 0; }
.mark[data-state="steam"] { background: var(--accent); border-color: var(--accent); }
.mark[data-state="steam"] svg { opacity: 1; stroke: var(--bg); }

.a-icon {
  width: 44px;
  height: 44px;
  border-radius: 6px;
  display: block;
  background: var(--bg-3);
  border: 1px solid var(--line);
}
.a-icon-dead {
  display: grid;
  place-items: center;
  font-family: var(--mono);
  font-size: 14px;
  color: var(--accent);
}
.ach:not(.done) .a-icon-dead { color: var(--faint); }

.a-main { min-width: 0; }
.ach-name { display: flex; align-items: center; gap: 8px; font-size: 14.5px; font-weight: 600; }
.ach-desc { color: var(--muted); font-size: 13px; margin-top: 2px; }

.a-global { text-align: right; }
.a-global .k { display: inline-block; margin-right: 7px; }
.a-global .pct { font-family: var(--mono); font-size: 12px; font-variant-numeric: tabular-nums; }
.a-global .meter { margin-top: 6px; }
/* The tiers colour anything that carries them, not just the achievement list —
   scoping them to .rarity meant the same percentage in the ticker came out
   plain. */
.rarity { color: var(--muted); }
.t-uncommon { color: var(--muted); }
.t-rare { color: var(--gold); font-weight: 600; }
.t-ultra { color: var(--ultra); font-weight: 700; }
.t-rare + .meter > i, .meter.m-rare > i { background: var(--gold); }
.meter.m-ultra > i { background: var(--ultra); }

.a-date {
  text-align: right;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

.badge {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  border: 1px solid var(--line-2);
  border-radius: 4px;
  padding: 2px 6px;
}
.pill { font-family: var(--mono); font-size: 11px; color: var(--muted); }

/* -------------------------------------------------------------- spoilers */

.spoiler-name {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-weight: 400;
  font-style: italic;
  color: var(--faint);
  cursor: pointer;
  text-align: left;
  border-bottom: 1px dashed var(--line-2);
}
.spoiler-name:hover { color: var(--muted); }
.spoiler-hint {
  font-family: var(--mono);
  font-style: normal;
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  border: 1px dashed var(--line-2);
  border-radius: 4px;
  padding: 2px 6px;
  margin-left: 8px;
}
.badge-btn { cursor: pointer; background: none; font-family: var(--mono); }
.badge-btn:hover { color: var(--fg); border-color: var(--faint); }
.spoiler-warn { color: var(--muted); font-size: 13px; margin: 0 0 10px; max-width: 46ch; }

/* -------------------------------------------------------------- expanded */

/*
 * The expanded body needs a surface of its own.
 *
 * It had none — no background, no border, no shadow — so the page's voxel
 * pattern showed straight through the steps, notes and guides. On a plain
 * background that reads as "attached to the row above"; on a patterned one it
 * reads as text floating over wallpaper, and there was nothing to say the row
 * was open at all besides the content appearing.
 *
 * Inset from the row rather than flush with it, so the open panel is visibly a
 * thing that belongs to the achievement above it rather than the next item in
 * the list.
 */
/*
 * The opened body.
 *
 * Full width and square: no margin, no radius, no shadow. As an inset card it
 * read as a separate object floating over the list rather than as the row's own
 * contents unfolding. The left padding keeps the text lined up past the icon.
 *
 * The surface is a gradient rather than a flat grey. Three things are stacked,
 * all of them faint on purpose — this sits behind the steps you came to read,
 * so it has to give the panel presence without competing with the words on it:
 *
 *   a wash of the accent at the top, tying the open panel to the green used
 *   everywhere else for "this one is yours / this one is done";
 *   a soft light from the top-left, so the panel reads as lit rather than
 *   painted;
 *   a vertical shift from --bg-2 down to --bg-3, which makes it darken towards
 *   the next achievement instead of ending on the same tone it started with.
 */
.panel {
  display: grid;
  grid-template-rows: 1fr;
  border: 0;
  border-radius: 0;
  background:
    linear-gradient(180deg, rgba(63, 224, 171, 0.055) 0%, rgba(63, 224, 171, 0) 38%),
    radial-gradient(120% 80% at 8% 0%, rgba(120, 190, 255, 0.045) 0%, rgba(120, 190, 255, 0) 60%),
    linear-gradient(180deg, var(--bg-2) 0%, var(--bg-3) 100%);
  /* A lit top edge, the way the accent marks a row as yours. Inset rather than
     a border so it doesn't add height and shift everything below it. */
  box-shadow: inset 0 1px 0 rgba(63, 224, 171, 0.16);
}

/* Everything visible lives here. With the padding on the outer element it
   showed as a stripe before the panel had any height to hold it. */
.panel-inner {
  overflow: hidden;
  display: grid;
  gap: 18px;
  /* Sat at 78px to line up under the achievement title, which pushed every
     word into the right two-thirds of a wide screen and left the panel looking
     like it belonged to nothing. A short indent reads as "inside this row"
     just as well. */
  padding: 20px 22px 26px 26px;
}

/*
 * Only the row you just opened animates — never the ones already showing.
 *
 * Opening a row repaints the whole list, so every open panel is a brand new
 * element and, with the animation on .panel itself, they all replayed their
 * entrance at once and the page jumped. See state.justOpened.
 */
.panel.is-new { animation: panel-open 0.44s cubic-bezier(0.22, 0.61, 0.36, 1); }
.panel.is-new > .panel-inner { animation: panel-rise 0.44s cubic-bezier(0.22, 0.61, 0.36, 1); }

/* Closing runs the same thing backwards, slightly quicker — waiting to get rid
   of something feels slower than waiting for it to arrive. */
/* The browser's own scroll anchoring latches onto something inside the panel
   and chases it as the panel shrinks, which fights the row we are deliberately
   holding still. Turned off here only. */
.panel.is-closing { animation: panel-close 0.34s cubic-bezier(0.4, 0, 0.7, 0.4) forwards; overflow-anchor: none; }
.panel.is-closing > .panel-inner { animation: panel-sink 0.34s cubic-bezier(0.4, 0, 0.7, 0.4) forwards; }

/* 0fr → 1fr is the one way to animate to a height nobody knows in advance. */
@keyframes panel-open {
  from { grid-template-rows: 0fr; }
  to { grid-template-rows: 1fr; }
}
@keyframes panel-close {
  from { grid-template-rows: 1fr; }
  to { grid-template-rows: 0fr; }
}
/* The lift: the contents settle into place rather than being there the instant
   the space appears. */
@keyframes panel-rise {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: none; }
}
@keyframes panel-sink {
  from { opacity: 1; transform: none; }
  to { opacity: 0; transform: translateY(-8px); }
}

@media (prefers-reduced-motion: reduce) {
  /* The close still needs its class removed by the timeout in the click
     handler, which runs either way. */
  .panel.is-new, .panel.is-new > .panel-inner,
  .panel.is-closing, .panel.is-closing > .panel-inner { animation: none; }
}

@media (max-width: 720px) {
  .panel-inner { padding: 16px 14px 20px; }
}
.panel h3 {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 400;
  margin: 0 0 11px;
}

/* The panel is the part you actually read — steps, notes, guides — so it runs
   a notch larger than the list it opens out of. */
.panel-inner .comm-steps li { font-size: 15px; }
.panel-inner .subtask span { font-size: 15px; }
.panel-inner .btn-ghost {
  font-size: 13.5px;
  padding: 9px 14px;
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.028);
  color: var(--fg);
}
.panel-inner .btn-ghost:hover {
  border-color: rgba(63, 224, 171, 0.45);
  background: rgba(63, 224, 171, 0.09);
}

.guides { display: flex; flex-wrap: wrap; gap: 7px; }
/* These are the way out to every guide on the internet, and they were a thin
   grey outline on a dark panel — easy to scan past entirely. A filled face and
   a brighter edge make them read as buttons rather than as labels. */
.guide {
  border: 1px solid var(--line-2);
  border-radius: 7px;
  padding: 9px 14px;
  font-size: 13.5px;
  color: var(--fg);
  background: rgba(255, 255, 255, 0.028);
}
.guide:hover {
  color: var(--fg);
  border-color: rgba(63, 224, 171, 0.45);
  background: rgba(63, 224, 171, 0.09);
}
.credit { color: var(--faint); font-size: 12.5px; margin: 11px 0 0; }

.subtasks { list-style: none; margin: 0 0 8px; padding: 0; display: grid; gap: 2px; }
.subtask { display: flex; align-items: center; gap: 9px; padding: 3px 0; }
.subtask input[type="checkbox"] { width: 14px; height: 14px; accent-color: var(--accent); flex: none; cursor: pointer; }
.subtask span { flex: 1; font-size: 14px; }
.subtask.done span { color: var(--faint); text-decoration: line-through; }
.subtask .del {
  background: none; border: 0; color: var(--faint);
  cursor: pointer; padding: 0 4px; font-size: 15px; line-height: 1; opacity: 0;
}
.subtask:hover .del { opacity: 1; }
.subtask .del:hover { color: var(--fg); }

.untick {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin: 2px 0 6px;
  padding: 8px 10px;
  border-left: 2px solid var(--line-2);
  background: var(--bg-2);
  border-radius: 0 6px 6px 0;
  font-size: 13px;
  color: var(--muted);
}
.link-btn {
  background: none; border: 0; padding: 0; font: inherit;
  color: var(--fg); text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}
.link-btn.quiet { color: var(--muted); text-decoration: none; }
.link-btn.quiet:hover { color: var(--fg); }

.add-sub {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  padding: 7px 0;
  outline: none;
  font-size: 14px;
}
.add-sub:focus { border-color: var(--line-2); }
.add-sub::placeholder { color: var(--faint); }

.note {
  width: 100%;
  min-height: 62px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--bg-2);
  padding: 10px 12px;
  outline: none;
  resize: vertical;
  font-size: 14px;
}
.note:focus { border-color: var(--line-2); }
.note::placeholder { color: var(--faint); }
.saved { font-family: var(--mono); font-size: 11px; color: var(--faint); height: 15px; margin-top: 5px; }

/* ------------------------------------------------------------- community */

.community { border-left: 2px solid var(--line); padding-left: 14px; }
.comm-steps { list-style: none; margin: 0 0 8px; padding: 0; }
.comm-steps li { font-size: 14px; padding: 2px 0 2px 16px; position: relative; }
.comm-steps li:before { content: "–"; position: absolute; left: 0; color: var(--faint); }

/*
 * Saying it worked. Lives inside the credit line beside "N people used this",
 * so it's dressed as part of that sentence rather than as a control — same
 * size, same colour, no chip. The heart and the hover carry the fact that it
 * can be pressed, which is enough for something nobody needs to find in a
 * hurry. As a bordered button it read as a call to action on a page you came
 * to for instructions.
 */
.thanks {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: color 0.15s ease;
}
.thanks:hover:not(:disabled) { color: var(--fg); }
.thanks:disabled { opacity: 0.6; cursor: default; }
.thanks.is-on { color: var(--accent); }
.thanks-mark { font-size: 12px; line-height: 1; transition: color 0.15s ease; }
/* The heart picks up the accent before the words do, so the hover reads as
   "this is the thing you'd press" rather than as a link. */
.thanks:hover:not(:disabled) .thanks-mark,
.thanks.is-on .thanks-mark { color: var(--accent); }

/* Quiet by design: reassurance, not a badge competing with the heading. */
.private-tag {
  margin-left: 9px;
  padding: 2px 7px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  vertical-align: middle;
}

/* Picking mode. The list stops being prose and becomes a form, and it says so:
   the dashes give way to checkboxes and the whole block lifts onto its own
   surface, so there's no moment where a control could be mistaken for state. */
.comm-steps.is-picking {
  margin: 0 -10px 10px;
  padding: 4px 10px;
  border-radius: 8px;
  background: var(--accent-dim);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.comm-steps.is-picking li { padding: 0; }
.comm-steps.is-picking li:before { content: none; }

.comm-pick {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 10px;
  align-items: baseline;
  padding: 7px 4px;
  border-radius: 6px;
  cursor: pointer;
}
.comm-pick:hover { background: rgba(255, 255, 255, 0.03); }
.comm-pick input {
  width: 15px;
  height: 15px;
  accent-color: var(--accent);
  align-self: center;
  cursor: pointer;
}
.comm-pick.is-held { cursor: default; color: var(--faint); }
.comm-pick.is-held:hover { background: none; }
.comm-pick.is-held input { cursor: default; }
.comm-held {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
}
.comm-note { font-size: 13.5px; color: var(--muted); margin: 0 0 8px; }
.comm-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; }
.comm-form { margin-top: 12px; display: grid; gap: 8px; }
.contribs { color: var(--muted); }
.anon-row { display: flex; gap: 9px; align-items: flex-start; font-size: 12.5px; color: var(--muted); cursor: pointer; }
.anon-row input { width: 15px; height: 15px; accent-color: var(--accent); flex: none; margin-top: 1px; }
.comm-edit { font-family: var(--mono); font-size: 13px; }
.quiet-line { color: var(--faint); font-size: 12.5px; margin: 0; }
a.quiet-line:hover { color: var(--fg); }

.hint-block {
  color: var(--muted);
  font-size: 13px;
  line-height: 1.6;
  border-left: 2px solid var(--line-2);
  padding-left: 13px;
  margin: 0 0 22px;
  max-width: 62ch;
}

.prop-head { padding: 14px 4px 8px; }
.diff { font-size: 13.5px; padding: 0 4px 10px; display: grid; gap: 2px; }
.diff .add { color: var(--accent); }
.diff .del { color: var(--muted); text-decoration: line-through; }
.diff .same { color: var(--faint); }

.vote-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 0 4px 16px; }
.vote {
  background: none;
  border: 1px solid var(--line-2);
  border-radius: 6px;
  padding: 5px 11px;
  font-size: 13px;
  color: var(--muted);
  cursor: pointer;
}
.vote:hover { color: var(--fg); border-color: var(--faint); }
.vote.on { color: var(--accent); border-color: var(--accent); }
.vote:disabled { opacity: 0.5; cursor: default; }
.score { font-family: var(--mono); font-weight: 700; font-size: 14px; }

/* Unreviewed work, and it has to look it.
   The accent green means "unlocked, done, yours" everywhere else in the app, so
   using it for proposed lines made an unpublished edit read as an approved one.
   In here the additions are plain text with an amber marker, and the whole
   block sits on its own amber edge — the same colour the review count and the
   outage notice use for "needs attention". */
.pending {
  margin-top: 18px;
  padding: 14px 0 4px 14px;
  border-top: 1px solid var(--line);
  border-left: 2px solid var(--gold);
}
.pending h3 { color: var(--gold); }
.pending .prop { border: 0; border-top: 1px solid var(--line); margin-top: 10px; }
.pending .prop:first-of-type { border-top: 0; }
.pending .prop-head { padding: 10px 0 6px; }
.pending .diff, .pending .vote-row { padding-left: 0; }
.pending .diff .add { color: var(--fg); }
/* The marker carries the colour, not the sentence. */
.pending .diff .add::first-letter { color: var(--gold); font-weight: 700; }

.unreviewed {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
  background: var(--gold-dim);
  box-shadow: inset 0 0 0 1px currentColor;
  border-radius: 999px;
  padding: 2px 8px;
  margin-left: 8px;
}

/* ------------------------------------------------------- the steps editor */

/* Every limit visible while you type. It used to be three raw textareas, so
   the rules only appeared as a server error after pressing Send. */

.ed { display: grid; gap: 20px; }
.ed-section { display: grid; gap: 9px; }
.ed-section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.ed-hint { margin: -4px 0 2px; font-size: 12px; color: var(--faint); line-height: 1.5; }

.ed-count {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.ed-count.is-full { color: var(--gold); }

.ed-warn:not(:empty) {
  display: block;
  margin-top: 5px;
  font-size: 11.5px;
  color: var(--gold);
}

.ed-steps { display: grid; gap: 7px; }
.ed-step { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 10px; align-items: start; }
.ed-num {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--faint);
  padding-top: 10px;
  min-width: 1.4ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.ed-step-main { min-width: 0; }
.ed-step-input {
  width: 100%;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-2);
  border-radius: 7px;
  padding: 8px 11px;
  font-size: 13.5px;
}
.ed-step-input:focus { outline: none; border-color: var(--accent); }
.ed-step-input.is-bad { border-color: var(--gold); }
.ed-step-input::placeholder { color: var(--faint); }

.ed-step-tools { display: flex; gap: 4px; padding-top: 4px; }
.ed-move {
  width: 26px;
  height: 26px;
  padding: 0;
  background: none;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--faint);
  font-size: 12px;
  cursor: pointer;
}
.ed-move:hover:not(:disabled) { color: var(--fg); border-color: var(--faint); }
.ed-move.danger:hover:not(:disabled) { color: var(--danger); border-color: var(--danger); }
.ed-move:disabled { opacity: 0.3; cursor: default; }

.ed-add { justify-self: start; }
.ed-note { width: 100%; resize: vertical; }

.ed-atts { display: grid; gap: 6px; }
.ed-att {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 7px 9px;
  background: var(--bg-2);
}
.ed-att-kind {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  background: var(--accent-dim);
  border-radius: 4px;
  padding: 2px 7px;
}
.ed-att-cap-text { font-size: 12.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.ed-att-add { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) auto; gap: 7px; }
.ed-att-url, .ed-att-cap {
  min-width: 0;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line-2);
  border-radius: 7px;
  padding: 8px 11px;
  font-size: 12.5px;
}
.ed-att-url:focus, .ed-att-cap:focus { outline: none; border-color: var(--accent); }
.ed-att-url::placeholder, .ed-att-cap::placeholder { color: var(--faint); }
.ed-att-url:disabled, .ed-att-cap:disabled { opacity: 0.5; }

.ed .sw { max-width: none; }

@media (max-width: 560px) {
  .ed-att-add { grid-template-columns: minmax(0, 1fr); }
}

/* Each suggestion is a scannable row until you open it. Ten expanded cards is
   a wall — same height, same weight, nothing to tell them apart without
   reading all of them. */
.sugg { border-top: 1px solid var(--line); }
.sugg:first-of-type { border-top: 0; }

.sugg-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: 11px 8px 11px 0;
  cursor: pointer;
  color: inherit;
}
.sugg-head:hover { color: var(--fg); }
.sugg-head:hover .sugg-peek { color: var(--muted); }
.sugg-caret { color: var(--faint); font-size: 10px; transition: transform 0.18s ease; }
.sugg.is-open .sugg-caret { transform: rotate(90deg); color: var(--gold); }

.sugg-main { min-width: 0; }
.sugg-by { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; flex-wrap: wrap; }
.sugg-peek {
  display: block;
  font-size: 12.5px;
  color: var(--faint);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sugg-score {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--faint);
  min-width: 3ch;
  text-align: right;
}
.sugg-score.up { color: var(--accent); }
.sugg-score.down { color: var(--danger); }

.sugg-body { padding-bottom: 6px; }
.sugg-body .prop { border-top: 0; margin-top: 0; }
.sugg-body .prop-head { display: none; } /* the row above is the header */

/* A first draft, not a change to something. No +/− markers, because there is
   nothing to compare it against. */
.draft { padding: 0 4px 10px; }
.draft-steps { margin: 0; padding-left: 20px; display: grid; gap: 6px; font-size: 13.5px; line-height: 1.5; }
.draft-steps li::marker { color: var(--gold); font-family: var(--mono); font-size: 12px; }
.draft-note {
  margin: 12px 0 0;
  padding-left: 12px;
  border-left: 2px solid var(--line-2);
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.55;
}

.ach.is-focus > .ach-row { background: var(--bg-3); box-shadow: inset 2px 0 0 var(--fg); }

/* ---------------------------------------------------------- attachments */

.atts { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; margin: 12px 0 4px; }
.att { margin: 0; }
.att-frame { position: relative; aspect-ratio: 16 / 9; background: var(--bg-3); border-radius: 7px; overflow: hidden; }
.att img, .att iframe { display: block; width: 100%; height: 100%; border: 0; object-fit: cover; }
.att-img img { border-radius: 7px; aspect-ratio: 16 / 9; }
.att-play {
  position: absolute; inset: 0; margin: auto;
  width: 46px; height: 46px; border-radius: 50%; border: 0;
  background: rgba(8, 10, 11, 0.75); color: #fff; font-size: 15px;
  cursor: pointer; display: grid; place-items: center; padding-left: 3px;
}
.att-play:hover { background: rgba(8, 10, 11, 0.9); }
.att-time {
  position: absolute; right: 6px; bottom: 6px;
  background: rgba(8, 10, 11, 0.8); color: #fff;
  font-family: var(--mono); font-size: 10px; padding: 1px 5px; border-radius: 3px;
}
.att figcaption { font-size: 12px; color: var(--muted); margin-top: 5px; line-height: 1.4; }
.att-src {
  display: inline-block; margin-left: 6px;
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--faint); border: 1px solid var(--line); border-radius: 3px; padding: 0 4px;
}
.att-dead {
  display: grid; place-items: center; aspect-ratio: 16 / 9;
  background: var(--bg-3); border-radius: 7px; color: var(--faint);
  font-size: 12px; text-align: center; padding: 10px;
}

/* ------------------------------------------------------------- changelog */

.changelog h2 {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.06em;
  color: var(--accent);
  margin: 28px 0 10px;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--line);
}
.changelog h2:first-child { margin-top: 0; }
.changelog p { color: var(--muted); margin: 0 0 12px; max-width: 64ch; }
.changelog ul { margin: 0; padding-left: 18px; display: grid; gap: 6px; }
.changelog li { color: var(--muted); line-height: 1.6; max-width: 64ch; }
.changelog strong { color: var(--fg); font-weight: 600; }
.changelog code {
  font-family: var(--mono);
  font-size: 12px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 5px;
}

/* ------------------------------------------------------------ responsive */

@media (max-width: 1080px) {
  .stats { grid-auto-flow: row; grid-template-columns: repeat(3, 1fr); }
  .stat { border-left: 1px solid var(--line); border-top: 1px solid var(--line); }
  .stat:nth-child(-n + 3) { border-top: 0; }
  .stat:nth-child(3n + 1) { border-left: 0; }
}

/* ------------------------------------------------- narrow-screen controls */

/* Both control sets are in the DOM; the breakpoint decides which one shows. */
.lib-tools { display: none; gap: 10px; margin-bottom: 14px; }

/* Collections as a swipeable row. A vertical list of five costs more height
   than the games it's filtering. */
/* Wraps rather than scrolls sideways. A horizontal strip only works with a
   thumb — on a narrow desktop window it needs shift+wheel to reach, so it just
   reads as content chopped off at the edge.

   Each chip grows to fill its row, so however many land on a line they finish
   flush with the edge. Left at their natural widths they wrapped ragged, with
   the last one stranded beside a gap — and since that can happen at any width,
   no breakpoint fixes it. */
.chip-scroll {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}
.chip-scroll .chip {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.chip-scroll .chip em { font-style: normal; color: var(--faint); }
.chip-scroll .chip[aria-selected="true"] em { color: var(--accent); }

.tool-row { display: flex; align-items: center; gap: 8px; }

.tool-row .seg { flex: none; grid-template-columns: auto auto; }
.tool-row .seg button { padding: 7px 12px; }
.tool-refresh { flex: none; font-size: 14px; line-height: 1; padding: 8px 11px; }

@media (max-width: 760px) {
  .side { display: none; }
  .lib-tools { display: grid; }
  .shell { grid-template-columns: 1fr; gap: 0; padding-top: 18px; }

  /* Six tall tiles stacked two-wide filled the screen before a single game
     appeared. One swipeable strip instead. */
  /* Fits as many tiles per row as the width allows and wraps the rest. Nothing
     is ever off-screen, at any width, with any pointer. */
  /* Three across, so six tiles make two even rows rather than leaving one
     stranded on a line of its own. */
  .stats {
    grid-auto-flow: row;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
    overflow: visible;
    border: 0;
    border-radius: 0;
    background: none;
  }
  .stat {
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--bg-2);
    padding: 10px 12px;
  }
  /* Compact enough that six tiles cost three short rows, not half the screen. */
  .stat .k { white-space: normal; font-size: 9px; letter-spacing: 0.1em; }
  .stat-v { font-size: 19px; margin-top: 3px; }
  .stat-v small { font-size: 11px; }

  /* Less preamble before the thing you came for. */
  .page-head { padding: 20px 0 14px; }
  .page-head h1 { font-size: 25px; margin-bottom: 6px; }
  .page-head .lede { font-size: 12.5px; }
}

/* Phone-width: three tiles across gets cramped, two stays readable. */
@media (max-width: 520px) {
  .stats { grid-template-columns: repeat(2, 1fr); }

  /* Stop chips getting so wide they look like buttons: at this width two per
     row is the natural minimum, and flex-grow squares off the ends. */
  .chip-scroll .chip { flex-basis: 40%; }
}

@media (max-width: 760px) {
  .top { gap: 12px; padding: 0 16px; }
  .brand-text i { display: none; }
  /* No room beside the brand and the account control at this width. */
  .ticker { display: none; }
  /* The name label doesn't fit; the menu trigger beside it always stays. */
  .top-right .who { display: none; }
  .top-actions { gap: 8px; }
  .who-menu { right: 0; min-width: 190px; }
  main, .foot { padding-left: 16px; padding-right: 16px; }

  .page-head { display: block; }
  /* The stacked two-column stat grid that used to live here is gone — the
     swipeable strip above replaces it. Its leftover border rules would have
     drawn dividers in the wrong places on the strip. */

  .row { grid-template-columns: 88px minmax(0, 1fr) auto; gap: 0 12px; }
  .cover { width: 88px; height: 41px; }
  .row .col-unlocked, .row .chev { display: none; }

  .hero-in { min-height: 150px; padding: 18px; display: block; }
  .hero h1 { font-size: 23px; }
  .hero-pct { text-align: left; margin-top: 12px; }
  .hero-pct b { font-size: 28px; }

  .ach-row { grid-template-columns: 20px 36px minmax(0, 1fr); gap: 0 10px; }
  .a-icon { width: 36px; height: 36px; }
  .a-global, .a-date { grid-column: 3; text-align: left; margin-top: 6px; }
  .a-global .meter { max-width: 120px; }
  .panel-inner { padding-left: 6px; }
  .filter { margin-left: 0; width: 100%; }
}
