/* Dalix Games — the platform's stylesheet.
 *
 * Only platform pages use this. Games bring their own everything; the platform
 * never injects styling into a game beyond the SDK's floating back button.
 *
 * The look is an arcade cabinet lit from the front rather than a dark room:
 * white panels, near-black outlines, one teal accent, and pixel type doing all
 * the labelling. Everything is square on purpose — a pixel grid has no curves,
 * so a border-radius anywhere reads as a different site pasted in.
 */

/* -------------------------------------------------------------- fonts --- */
/* Self-hosted: one origin is an invariant here, and a webfont CDN would be a
 * second one on every page. Files, subsets and unicode-ranges are Google
 * Fonts'; see static/fonts/README.md and static/fonts/OFL.txt.
 *
 * A glyph missing from a pixel face — most arrows, most punctuation Silkscreen
 * never drew — falls through the stack to the body font on its own. That is why
 * every pixel stack below ends in a real text font rather than in `monospace`. */

@font-face {
  font-family: "Press Start 2P"; font-style: normal; font-weight: 400; font-display: swap;
  src: url(/static/fonts/press-start-2p-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Press Start 2P"; font-style: normal; font-weight: 400; font-display: swap;
  src: url(/static/fonts/press-start-2p-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Silkscreen"; font-style: normal; font-weight: 400; font-display: swap;
  src: url(/static/fonts/silkscreen-400-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Silkscreen"; font-style: normal; font-weight: 400; font-display: swap;
  src: url(/static/fonts/silkscreen-400-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Silkscreen"; font-style: normal; font-weight: 700; font-display: swap;
  src: url(/static/fonts/silkscreen-700-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Silkscreen"; font-style: normal; font-weight: 700; font-display: swap;
  src: url(/static/fonts/silkscreen-700-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ------------------------------------------------------------- tokens --- */

:root {
  color-scheme: light;

  /* Ink carries a teal cast all the way down, so no grey on the page ever
     reads blue or brown next to the accent. */
  --ink: #0e2422;
  --ink-2: #3f5d5a;
  --ink-3: #5a7a77;     /* 4.6:1 on white — the floor for the small meta text */
  --edge: #123c39;      /* the 2px sprite outline every solid object gets */
  --line: #cbdedb;
  --line-2: #a9c6c2;

  --bg: #eef4f3;
  --bg-2: #e2ecea;
  --panel: #ffffff;
  --panel-2: #e8f2f0;
  --grid: rgba(18, 60, 57, .055);

  /* One accent, four jobs. `--accent` is bright enough to be a fill and too
     bright to be text; `--accent-deep` is the one that goes on white. White on
     `--accent` is 2.5:1, so nothing here ever does that — ink goes on teal. */
  --accent: #14b8a6;
  --accent-hi: #0d9488;
  --accent-bright: #2ad4c0;
  --accent-deep: #0b6a63;   /* 6.4:1 on white */
  --accent-lo: #d3f4ef;
  --accent-sh: #a5e7de;   /* the sprite shadow under display type */
  --accent-ink: #04231f;    /* 6.8:1 on --accent */

  --gold: #e8a317;
  --gold-deep: #8a5d00;
  --silver: #aab8b6;
  --bronze: #cd7f32;
  --good: #0f7a52;
  --warn: #8a5a00;
  --bad: #c62b3f;
  --bad-lo: #fdeaec;

  --radius: 0;
  --shadow-c: #c3d9d5;
  --shadow: 4px 4px 0 var(--shadow-c);
  --shadow-lift: 7px 7px 0 var(--shadow-c);
  --shadow-sm: 3px 3px 0 var(--shadow-c);

  --font-display: "Press Start 2P", "Silkscreen", ui-monospace, "Courier New", monospace;
  --font-ui: "Silkscreen", "Press Start 2P", ui-monospace, "Courier New", monospace;
  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  /* Press Start 2P ships one weight and Silkscreen ships two. Left to itself a
     browser smears a fake bold over a bitmap face the moment something asks for
     a weight it hasn't got, which just doubles every stem. Ask only for the
     weights that exist. */
  font-synthesis: none;
  color: var(--ink);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.6;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  -webkit-font-smoothing: antialiased;
  /* Level-editor graph paper under a teal glow. The grid is 1px at 5% — enough
     that the page has a texture to sit on, not enough to compete with a table. */
  background:
    radial-gradient(900px 420px at 50% -180px, rgba(20, 184, 166, .22), transparent 70%),
    linear-gradient(to right, var(--grid) 0 1px, transparent 1px 100%) 0 0 / 32px 32px,
    linear-gradient(to bottom, var(--grid) 0 1px, transparent 1px 100%) 0 0 / 32px 32px,
    var(--bg);
}

/* ---------------------------------------------------------- typography --- */

h1, h2, h3, h4 { margin: 0 0 .6em; font-weight: 400; }

h1 {
  font-family: var(--font-display);
  font-size: clamp(16px, 3.4vw, 25px);
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink);
  /* A player's own display name lands in an <h1> on the hub, and a pixel face
     has no hyphenation to fall back on. Break the word rather than the layout. */
  overflow-wrap: anywhere;
}
h2 { font-family: var(--font-ui); font-weight: 700; font-size: 19px; letter-spacing: .06em; text-transform: uppercase; }
h3 { font-family: var(--font-ui); font-weight: 700; font-size: 17px; letter-spacing: .04em; }
h4 { font-family: var(--font-ui); font-weight: 700; font-size: 15px; letter-spacing: .03em; }

p { margin: 0 0 1em; }
small { color: var(--ink-3); font-size: 12.5px; }
strong, b { font-weight: 700; }

a { color: var(--accent-deep); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 2px; }

/* One square ring everywhere. An `outline` is the only focus indicator that
   cannot be clipped by an ancestor's overflow, which matters here because half
   the focusable things live inside a panel that scrolls sideways. */
:focus-visible { outline: 3px solid var(--accent-deep); outline-offset: 2px; }

::selection { background: var(--accent); color: var(--accent-ink); }

.wrap { max-width: 1040px; margin: 0 auto; padding: 30px 20px 64px; }
/* `0 auto` still centres it: a column flex item with a max-width leaves free
   space on both sides for the auto margins to split. `flex-basis: auto` rather
   than 0 so a long page is still laid out by its content. */
main.wrap { flex: 1 0 auto; }
.narrow { max-width: 470px; }
/* A form on its own is the only thing on the page; it reads better sat down a
   little from the bar rather than hard against it. */
.narrow h1 { margin-top: 26px; }

/* ------------------------------------------------------------- header --- */

.topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: 16px;
  padding: 11px 20px;
  background: rgba(255, 255, 255, .92);
  backdrop-filter: blur(10px);
  border-bottom: 2px solid var(--edge);
}
/* The cabinet's trim: a 4px teal checker hanging under the bar. It is the one
   piece of pure decoration on the page, and it is what makes the header read as
   a marquee rather than as a web app's chrome. */
.topbar::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -4px; height: 4px;
  background: repeating-linear-gradient(90deg, var(--accent) 0 4px, var(--accent-lo) 4px 8px);
}

.brand {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--font-display); font-size: 14px; font-weight: 400;
  letter-spacing: 0; color: var(--ink);
}
.brand:hover { text-decoration: none; color: var(--accent-deep); }
.brand .mark { display: block; flex: 0 0 auto; }
.brand:hover .mark { transform: translateY(-1px); }

.topbar nav { margin-left: auto; display: flex; align-items: center; gap: 3px; }
.topbar nav a, .topbar nav button {
  display: inline-flex; align-items: center;
  color: var(--ink-2); padding: 8px 10px;
  font: 700 13px/1 var(--font-ui); letter-spacing: .05em; text-transform: uppercase;
  background: none; border: 2px solid transparent; cursor: pointer;
}
.topbar nav a:hover, .topbar nav button:hover {
  background: var(--accent-lo); border-color: var(--accent); color: var(--ink);
  text-decoration: none;
}
.topbar nav a.on { color: var(--ink); background: var(--panel-2); border-color: var(--line-2); }

.nav-burger {
  display: none; margin-left: auto; padding: 7px;
  border: 2px solid var(--edge); background: var(--panel); color: var(--ink);
  box-shadow: var(--shadow-sm); cursor: pointer; line-height: 0;
}
.nav-burger:hover { background: var(--accent-lo); }
.nav-burger[aria-expanded="true"] { background: var(--accent); box-shadow: none; transform: translate(3px, 3px); }

.badge {
  display: inline-block; min-width: 18px; padding: 0 5px;
  background: var(--accent); color: var(--accent-ink);
  border: 2px solid var(--edge);
  font: 700 11px/16px var(--font-ui); letter-spacing: .04em;
  text-align: center; margin-left: 7px;
}

/* -------------------------------------------------------------- panels --- */

.panel {
  background: var(--panel);
  border: 2px solid var(--edge);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 20px;
  box-shadow: var(--shadow);
}
.panel > :last-child { margin-bottom: 0; }

/* The dashed rule is a background rather than a border so the row can keep
   using `margin-left: auto` on `.right` without a pseudo-element fighting it
   for the end of the flex line. */
.panel-title {
  display: flex; align-items: baseline; gap: 10px;
  margin: 0 0 15px; padding-bottom: 11px;
  font: 700 13px/1.5 var(--font-ui);
  text-transform: uppercase; letter-spacing: .1em; color: var(--accent-deep);
  background: repeating-linear-gradient(90deg, var(--line-2) 0 4px, transparent 4px 8px)
              bottom left / 100% 2px no-repeat;
}
.panel-title::before {
  content: ""; flex: 0 0 auto; align-self: center;
  width: 8px; height: 8px; background: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-lo);
}
.panel-title .right {
  margin-left: auto; font-weight: 400; text-transform: none; letter-spacing: .02em;
  font-family: var(--font); font-size: 13px;
}

/* ---------------------------------------------------------- game cards --- */

.games { display: grid; gap: 18px; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }

.game-card {
  position: relative; display: flex; flex-direction: column; overflow: hidden;
  background: var(--panel); border: 2px solid var(--edge);
  border-radius: var(--radius); padding: 20px 18px 16px;
  color: var(--ink); box-shadow: var(--shadow);
  transition: transform .12s steps(3, end), box-shadow .12s steps(3, end);
}
/* The marquee stripe. `--game-accent` comes off the game's own game.toml, so
   a hundred games stay distinguishable while the chrome around them stays teal. */
.game-card::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 6px;
  background: var(--game-accent, var(--accent));
}
.game-card::after {
  content: ""; position: absolute; top: 6px; left: 0; right: 0; height: 4px;
  background: repeating-linear-gradient(90deg,
    var(--game-accent, var(--accent)) 0 4px, transparent 4px 8px);
  opacity: .45;
}
.game-card:hover { transform: translate(-3px, -3px); box-shadow: var(--shadow-lift); text-decoration: none; }
.game-card:focus-within { transform: translate(-3px, -3px); box-shadow: var(--shadow-lift); }

.game-card-play { display: block; color: var(--ink); }
.game-card-play:hover { text-decoration: none; }

/* The logo sits beside the title rather than above it: a hub holding a hundred
   games is a list to scan, and a picture on the left edge of every row is what
   makes it scannable. The text column is the flex item that shrinks, so a long
   title wraps instead of squeezing the logo. */
.game-head { display: flex; gap: 13px; align-items: flex-start; }
.game-head-text { min-width: 0; }
.game-logo {
  flex: 0 0 auto; width: 48px; height: 48px; object-fit: contain;
  /* Every logo gets the same 2px outline every solid object on this site gets,
     so a hundred logos drawn by a hundred different hands still line up as one
     shelf. Scaling is left alone: `image-rendering: pixelated` would sharpen a
     game that ships true 16px art and alias every game that ships anything
     larger, and there is no telling which a game is from here. */
  border: 2px solid var(--edge); background: var(--panel);
}

.game-card h3 {
  margin: 4px 0 6px; font-size: 17px; letter-spacing: .03em;
  text-transform: uppercase; overflow-wrap: anywhere;
}
.game-card:hover h3 { color: var(--accent-deep); }
.game-card p { color: var(--ink-2); font-size: 14px; margin: 0; }

/* Pushed to the bottom edge so cards of different tagline lengths still line
   their footers up across a row. */
.game-card .stat {
  display: block; margin-top: auto; padding-top: 13px;
  font: 700 11px/1 var(--font-ui); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-3);
  background: repeating-linear-gradient(90deg, var(--line) 0 4px, transparent 4px 8px)
              top left / 100% 2px no-repeat;
}
.game-card .stat:hover { color: var(--accent-deep); text-decoration: none; }

/* Somebody is waiting on this player. Only ever drawn when the number is
   non-zero, so it can afford to be the loudest thing on the card — and it
   sits above the leaderboard line, which pushes itself to the bottom edge. */
.game-card .stat.your-turn { margin-top: auto; color: var(--game-accent, var(--accent-deep)); }
.game-card .stat.your-turn + .stat { margin-top: 0; }
.game-card .stat b { color: var(--ink); font-variant-numeric: tabular-nums; }

/* -------------------------------------------------------------- forms --- */

label {
  display: block; margin-bottom: 7px;
  font: 700 11px/1.4 var(--font-ui); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-2);
}
.field { margin-bottom: 16px; }
.field small { display: block; margin-top: 6px; font-size: 12px; }

input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=file]):not([type=range]):not([type=color]):not([type=image]):not([type=hidden]),
select, textarea {
  width: 100%; padding: 10px 12px;
  background: var(--bg-2); color: var(--ink);
  border: 2px solid var(--line-2); border-radius: var(--radius);
  font-family: inherit; font-size: 15px;
  /* A recessed slot: the top inner shadow is the whole reason a square input
     still reads as something you type into. */
  box-shadow: inset 0 2px 0 rgba(18, 60, 57, .07);
}
textarea { min-height: 96px; resize: vertical; }
input:focus, select:focus, textarea:focus {
  outline: 3px solid var(--accent-deep); outline-offset: 1px;
  border-color: var(--accent-hi); background: var(--panel);
}
select[multiple] { min-height: 190px; }
input[type=checkbox], input[type=radio] { accent-color: var(--accent-hi); width: auto; }
::placeholder { color: var(--ink-3); opacity: 1; }

/* The arcade button: outlined, offset, and it travels the full offset when
   pressed so the shadow disappears under it. That travel is the affordance —
   nothing else on the page moves 3px on :active. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 11px 17px; border: 2px solid var(--edge); border-radius: var(--radius);
  background: var(--panel); color: var(--ink);
  font: 700 13px/1.2 var(--font-ui); letter-spacing: .07em; text-transform: uppercase;
  cursor: pointer; box-shadow: var(--shadow-sm);
  transition: transform .08s steps(2, end), box-shadow .08s steps(2, end);
}
.btn:hover { background: var(--accent-lo); text-decoration: none; }
.btn:active { transform: translate(3px, 3px); box-shadow: none; }
.btn.primary { background: var(--accent); border-color: var(--edge); color: var(--accent-ink); }
.btn.primary:hover { background: var(--accent-bright); color: var(--accent-ink); }
.btn.danger { background: var(--bad-lo); color: var(--bad); }
.btn.danger:hover { background: var(--bad); color: #fff; }
.btn.small { padding: 7px 11px; font-size: 11px; letter-spacing: .06em; }
.btn[disabled] { opacity: .45; cursor: not-allowed; box-shadow: none; }

/* Bottom-aligned, so a `.shrink` button next to a control sits level with it
   rather than level with the control's label. */
.row { display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end; margin-bottom: 16px; }
/* But a field with helper text under it is taller than its neighbours, and
   bottom-aligning it rode its input up out of line with theirs — visible on the
   account page, where Username has a hint and Display name does not. Stretching
   the field instead lets its contents flow from the top, so every input in the
   row starts on the same line whatever hangs below it. The margin moves to the
   row for the same reason: on the field it pushed the button 16px low. */
.row > .field { align-self: stretch; margin-bottom: 0; }
/* min-width:0 matters more than it looks. A flex item defaults to min-width:auto,
   so a single unbreakable string — a run's JSON payload, a long email — sets a
   min-content width the item refuses to shrink below, and the whole page ends up
   scrolling sideways on a phone. */
.row > * { flex: 1 1 180px; min-width: 0; }
.row > .shrink { flex: 0 0 auto; }
/* A column that wants more room before it wraps. A class rather than an
   inline style, because an inline style outranks the narrow-screen rules
   below and the column could never be told to stack. */
.row > .col-wide { flex: 1 1 320px; }

/* ------------------------------------------------------------- tables --- */

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 10px; border-bottom: 2px solid var(--line); }
th {
  font: 700 11px/1.4 var(--font-ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent-deep); border-bottom-color: var(--line-2);
}
tbody tr:last-child td { border-bottom: 0; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.num { font: 700 14px/1.5 var(--font-ui); letter-spacing: .02em; }
tbody tr:hover td { background: var(--accent-lo); }

/* A panel that slides. The two `local` gradients paint the panel colour over
   each edge and scroll away with the content; the two fixed shadows sit
   underneath and are only uncovered once there is something scrolled past that
   side. So the shadow appears exactly when there is more to see, and nothing
   has to measure anything. */
.scroll-x {
  overflow-x: auto; max-width: 100%;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  background:
    linear-gradient(to right, var(--panel) 40%, rgba(255, 255, 255, 0)) left / 26px 100% no-repeat local,
    linear-gradient(to left, var(--panel) 40%, rgba(255, 255, 255, 0)) right / 26px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(18, 60, 57, .18), rgba(18, 60, 57, 0)) left / 13px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(18, 60, 57, .18), rgba(18, 60, 57, 0)) right / 13px 100% no-repeat;
}
/* Tables stay readable rather than crushing to one word per line: the panel
   scrolls, the page does not. Done per cell rather than `min-width: max-content`
   on the table, because that sizes the table to its widest cell's *unwrapped*
   content and so overrode the run chips' own cap — they were pushed onto one
   line and off the edge instead of wrapping. */
.scroll-x th, .scroll-x td { white-space: nowrap; }
.scroll-x .run-payload { white-space: normal; }

/* A run's reported numbers. Named `runstat` and not `stat`: `.game-card .stat`
   already exists on the hub, and a bare `.stat` here would leak this chip's
   background, border and padding onto it — the more specific card rule only
   overrides the properties it happens to declare.

   The cap stops a long payload sprawling across a wide screen; on a narrow one
   the cell takes what is left after the nowrap columns and the chips wrap into
   it, which is why the cap is lifted there. */
.runstats { display: flex; flex-wrap: wrap; gap: 5px; max-width: 340px; }
.runstat {
  display: inline-flex; align-items: baseline; gap: 5px; white-space: nowrap;
  padding: 3px 7px;
  background: var(--bg-2); border: 2px solid var(--line-2);
  font: 400 11px/1.4 var(--font-ui); letter-spacing: .04em; color: var(--ink-3);
  text-transform: uppercase;
}
.runstat b { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------- leaderboards --- */

.rank {
  width: 4ch; color: var(--ink-3);
  font: 700 13px/1.5 var(--font-ui); font-variant-numeric: tabular-nums;
}
/* The podium. The class comes off the row's own rank in partials/board.html,
   because a leaderboard's whole job is to make the top of it worth looking at. */
th.rank { color: var(--accent-deep); }
.rank.top { color: var(--ink); }
.rank.top::before {
  content: ""; display: inline-block; vertical-align: 1px;
  width: 8px; height: 8px; margin-right: 6px; background: var(--line-2);
}
.rank.top-1::before { background: var(--gold); }
.rank.top-2::before { background: var(--silver); }
.rank.top-3::before { background: var(--bronze); }
tr.you { background: var(--accent-lo); }
tr.you td { color: var(--ink); font-weight: 700; }
tr.you td:first-child { box-shadow: inset 4px 0 0 var(--accent); }
tr.you:hover td { background: var(--accent-lo); }

/* ---------------------------------------------------------- challenge --- */
/* The rotating daily challenge — hoppy calls its one Pecking Order. It shares
   the leaderboard's table and podium colours on purpose: it is a board, and a
   second visual language for the same object would only ask the reader to
   learn which one they were looking at. What it adds is the clock, the prize
   table and yesterday's result, which an all-time board has no equivalent of. */

.challenge .panel-title .right { color: var(--accent-deep); }
.countdown {
  font: 700 11px/1.4 var(--font-ui); letter-spacing: .06em;
  text-transform: uppercase; font-variant-numeric: tabular-nums;
}
.challenge-lede { color: var(--ink-2); font-size: 14px; margin: 0 0 15px; max-width: 60ch; }
.challenge-you { margin: 12px 0 0; font-size: 14px; color: var(--ink-2); }

.prizes { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; padding: 0; margin: 0 0 17px; }
.prize {
  display: flex; align-items: baseline; gap: 7px;
  padding: 7px 11px; background: var(--panel-2); border: 2px solid var(--line-2);
  font-size: 13px; color: var(--ink-2);
}
.prize b { color: var(--ink); font-variant-numeric: tabular-nums; }
.prize .place {
  font: 700 10px/1.4 var(--font-ui); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-3);
}
/* The same three metals the podium uses, as the border rather than the fill,
   so a prize chip reads as related to rank 1-2-3 without shouting over it. */
.prize-1 { border-color: var(--gold); }
.prize-2 { border-color: var(--silver); }
.prize-3 { border-color: var(--bronze); }

.challenge-last {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
  margin-top: 17px; padding-top: 15px; border-top: 2px solid var(--line);
  font-size: 13.5px; color: var(--ink-2);
}
.challenge-last .section-label {
  font: 700 10px/1.4 var(--font-ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3);
}
.last-win b.rank { display: inline; width: auto; margin-right: 2px; }
.challenge-last .right-ish { margin-left: auto; color: var(--ink-3); }
td.soft { color: var(--ink-3); font-weight: 400; }
.pill.daily {
  margin-top: 9px; color: var(--accent-deep);
  border-color: var(--accent-sh); background: var(--accent-lo);
}

/* --------------------------------------------------------------- misc --- */

.flash {
  display: flex; align-items: flex-start; gap: 11px;
  padding: 12px 15px; margin-bottom: 16px;
  border: 2px solid var(--edge); box-shadow: var(--shadow-sm);
  font-size: 14px; background: var(--panel);
}
.flash::before {
  content: ""; flex: 0 0 auto; width: 10px; height: 10px; margin-top: 6px;
  background: currentColor;
}
.flash.info { background: var(--accent-lo); color: var(--accent-deep); }
.flash.warn { background: #fdf3dc; color: var(--warn); }
.flash.ok { background: #e4f6ec; color: var(--good); }
.flash.bad, .flash.error { background: var(--bad-lo); color: var(--bad); }

.note {
  display: inline-block; padding: 11px 14px;
  background: var(--bg-2); border: 2px dashed var(--line-2);
  color: var(--ink-2); font-size: 14px;
}
.error {
  padding: 10px 13px; margin-bottom: 14px;
  background: var(--bad-lo); border: 2px solid var(--bad);
  color: var(--bad); font-size: 14px; font-weight: 600;
}
.empty {
  padding: 30px 16px; text-align: center;
  color: var(--ink-3); font: 400 13px/1.9 var(--font-ui); letter-spacing: .06em;
}
.empty a { font-weight: 700; }

.pill {
  display: inline-block; padding: 3px 8px; vertical-align: 1px;
  font: 700 10px/1.4 var(--font-ui); letter-spacing: .08em; text-transform: uppercase;
  background: var(--panel-2); color: var(--ink-2); border: 2px solid var(--line-2);
}
.pill.ok { color: var(--good); border-color: #a8d8c1; background: #e4f6ec; }
.pill.bad { color: var(--bad); border-color: #f0b5bd; background: var(--bad-lo); }
.pill.warn { color: var(--warn); border-color: #e0c67f; background: #fdf3dc; }

.ann { padding: 15px 0; border-bottom: 2px solid var(--line); display: flex; gap: 13px; }
.ann:last-child { border-bottom: 0; }
.ann:first-child { padding-top: 2px; }
.ann .marker {
  width: 9px; height: 9px; margin-top: 8px; flex: 0 0 auto;
  background: var(--line-2);
}
.ann.unread .marker { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-lo); }
.ann h4 { margin: 0 0 5px; font-size: 14px; text-transform: uppercase; }
.ann p { margin: 0; color: var(--ink-2); font-size: 14px; white-space: pre-wrap; }
.ann .meta {
  margin-top: 7px; display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  font: 400 11px/1.6 var(--font-ui); letter-spacing: .05em; color: var(--ink-3);
  text-transform: uppercase;
}

.tabs {
  display: flex; gap: 4px; margin-bottom: 20px; flex-wrap: wrap;
  border-bottom: 2px solid var(--edge);
}
.tabs a {
  padding: 10px 14px; margin-bottom: -2px;
  color: var(--ink-2); border: 2px solid transparent; border-bottom-width: 0;
  font: 700 12px/1.3 var(--font-ui); letter-spacing: .06em; text-transform: uppercase;
}
.tabs a:hover { color: var(--ink); background: var(--panel-2); text-decoration: none; }
/* The active tab joins the rule it sits on: its own bottom edge is the panel
   colour painted over the 2px line, which is what makes it read as in front. */
.tabs a.on {
  color: var(--ink); background: var(--panel);
  border-color: var(--edge); border-bottom: 2px solid var(--panel);
}

.kv { display: grid; grid-template-columns: max-content 1fr; gap: 8px 18px; font-size: 14px; }
.kv dt { font: 700 11px/1.7 var(--font-ui); letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3); }
.kv dd { margin: 0; font-variant-numeric: tabular-nums; }

code, .mono { font-family: var(--mono); font-size: .9em; }
code { background: var(--bg-2); border: 1px solid var(--line); padding: 1px 5px; color: var(--accent-deep); }

/* ---------------------------------------------------------------- hero --- */

.hero { position: relative; padding: 28px 0 38px; }
/* The cabinet trim again, in miniature, above the title. Repeating one motif at
   three sizes — 4px under the header, 4px over the footer, 8px here — is what
   ties the pages together without another colour or another border. */
.hero::before {
  content: ""; display: block; width: 96px; height: 8px; margin-bottom: 22px;
  background: repeating-linear-gradient(90deg, var(--accent) 0 8px, var(--accent-lo) 8px 16px);
}
.hero h1 {
  font-size: clamp(17px, 3.8vw, 30px); margin-bottom: 18px;
  /* The offset shadow a sprite gets: no blur, one direction, accent-coloured.
     It is the same 4px the panels are offset by, so the whole page is lit from
     the same corner. */
  text-shadow: 4px 4px 0 var(--accent-sh);
}
.hero p { color: var(--ink-2); font-size: 16.5px; max-width: 54ch; }
.hero .btn { margin-right: 4px; }
/* A game's own page gets the logo big, above the title, where the hero's trim
   bar would otherwise be the only thing at the top. Offset shadow in the accent,
   matching the h1's text-shadow, so both are lit from the same corner. */
.game-hero-logo {
  display: block; width: 88px; height: 88px; object-fit: contain;
  margin-bottom: 20px;
  border: 2px solid var(--edge); background: var(--panel);
  box-shadow: 4px 4px 0 var(--accent-sh);
}

/* -------------------------------------------------------------- footer --- */

footer {
  padding: 24px 20px;
  border-top: 2px solid var(--edge);
  background: repeating-linear-gradient(90deg, var(--accent-lo) 0 4px, transparent 4px 8px)
              top left / 100% 4px no-repeat,
              var(--panel);
  color: var(--ink-3);
  font: 400 12px/1.8 var(--font-ui); letter-spacing: .05em; text-transform: uppercase;
}
footer .wrap { padding-bottom: 0; display: flex; gap: 18px; flex-wrap: wrap; align-items: center; }
footer .sep { margin-left: auto; }

/* ---------------------------------------------------------- narrow --- */
/* The admin portal is the reason this section exists: it is the one part of
   the site that is mostly wide tables, and it gets read on a phone as often as
   not. Nothing below changes the desktop layout — it only stops a phone having
   to scroll the whole page sideways to read a table cell. */

@media (max-width: 720px) {
  /* The page title sat flush against the sticky bar, and the footer floated a
     long way below the last panel. */
  .wrap { padding: 20px 14px 32px; }
  footer { padding: 18px 14px; }
  footer .sep { margin-left: 0; }

  /* The nav becomes a menu. Five links crowding a 390px bar is what pushed the
     brand onto two lines; behind a button, the bar is a brand and a control. */
  .topbar { flex-wrap: wrap; gap: 8px 12px; padding: 10px 14px; }
  .brand { white-space: nowrap; font-size: 12px; }
  .nav-burger { display: block; }

  /* Opened straight off `aria-expanded`, so there is no second piece of state
     to fall out of step with what a screen reader is told. The <noscript> block
     in _base.html covers a page that cannot flip it. */
  #sitenav { display: none; }
  .nav-burger[aria-expanded="true"] ~ #sitenav {
    display: flex; flex-basis: 100%; flex-direction: column; align-items: stretch;
    gap: 2px; margin-left: 0; padding-top: 10px; border-top: 2px solid var(--line);
  }
  #sitenav a, #sitenav button { padding: 11px 8px; font-size: 13px; text-align: left; }
  #sitenav form { display: block; }
  #sitenav form button { width: 100%; justify-content: flex-start; }

  /* Admin sections wrapped onto two rows and pushed the page down. One row
     that scrolls keeps the current tab where the reader left it. */
  .tabs { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .tabs::-webkit-scrollbar { display: none; }
  .tabs a { flex: 0 0 auto; padding: 10px 11px; }

  /* Side-by-side panels stack rather than fighting over 40px each. */
  .row > *, .row > .col-wide { flex: 1 1 100%; }

  .panel { padding: 15px; margin-bottom: 16px; }
  .hero { padding: 14px 0 24px; }
  h1 { margin-bottom: 14px; }
  .tabs { margin-bottom: 16px; }
  th, td { padding: 9px 8px; }

  /* A form control under 16px makes iOS Safari zoom the page on focus, and it
     never zooms back out. */
  input, select, textarea { font-size: 16px; }

  /* Chips wrap into whatever the cell has left rather than being capped, so
     the numbers use the width available instead of running off the edge. */
  .runstats { max-width: none; }
}

/* The stepped transitions above are a deliberate look, not a nicety — but a
   3px jump is still motion, and a card that leaps under the pointer is exactly
   what this setting is for. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
  }
  .game-card:hover, .game-card:focus-within { transform: none; }
}
