/* PUNBIT Loud (Direction 2b) — flat colour, Archivo, 16px corners.
 *
 * The design sheet's three standing rules, which most of this file is in
 * service of:
 *   · nothing casts a shadow — depth comes from a colour field, not a bevel;
 *   · every issue owns one colour, and keeps it on every surface it appears on;
 *   · text on a colour field is solid, never faded.
 *
 * Tokens: site/tokens.css. Markup: tools/site_pages.py.
 */
@import url("tokens.css");

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("vendor/archivo-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: "Archivo";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("vendor/archivo-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;
}

* { box-sizing: border-box; }
html, body { margin: 0; overflow-x: clip; }
body {
  background: var(--page);
  color: var(--text);
  font: 400 var(--text-md)/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
}
body.modal-open { overflow: hidden; }
img { max-width: 100%; }

.wrap {
  width: 100%;
  max-width: var(--wrap);
  min-width: 0;
  margin: 0 auto;
  padding-inline: clamp(16px, 4vw, 32px);
}
a { color: var(--link); }
a:hover { color: var(--link-hover); }

/* ================================================================
   Level colour — one per issue, picked from the slug in site_pages.py
   so it is the same colour on the front page, in the archive and at
   the top of the issue itself.
   ================================================================ */
.tile-blue   { --tile: var(--blue);   --on-tile: #ffffff; }
.tile-yellow { --tile: var(--yellow); --on-tile: #1d1d1f; }
.tile-green  { --tile: var(--green);  --on-tile: #1d1d1f; }
.tile-red    { --tile: var(--red);    --on-tile: #ffffff; }
.tile-purple { --tile: var(--purple); --on-tile: #ffffff; }
/* Not in the issue rotation -- it is the masthead's own colour, and an issue
   wearing it merged the lead band into the bar above it. Kept as the player's
   fallback for a card that carries no colour of its own. */
.tile-ink    { --tile: var(--bar);    --on-tile: #ffffff; }

/* ================================================================
   Masthead — a dark bar across the top, the wordmark left, sections
   and the subscribe pill right.
   ================================================================ */
.masthead { background: var(--bar); color: var(--on-bar); }
.masthead-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  min-height: 52px;
  flex-wrap: wrap;
  padding-block: var(--space-2xs);
}
.logo {
  margin: 0;
  min-width: 0;
  font: 700 1.1875rem/1 var(--font);
  letter-spacing: -0.025em;
  color: var(--on-bar);
}
.logo a { color: inherit; text-decoration: none; }
/* The wordmark's cursor. It was named .blink and only ever coloured; a
   terminal cursor that does not blink is a hyphen. step-end so it snaps on
   and off rather than fading -- this is a 1-bit machine. */
.blink { color: var(--yellow); animation: blink var(--dur-blink) step-end infinite; }
@keyframes blink { 50% { opacity: 0; } }

.masthead-nav {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2.4vw, 26px);
  flex-wrap: wrap;
  font: 500 0.8125rem/1 var(--font);
}
.masthead-nav a { color: var(--on-bar-muted); text-decoration: none; white-space: nowrap; }
.masthead-nav a[aria-current="page"] { color: var(--on-bar); }
.masthead-nav a:hover { color: var(--on-bar); }
.masthead-sub {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 8px 14px;
  border-radius: var(--r);
  background: var(--yellow);
  font-weight: 600;
}
.masthead-nav a.masthead-sub,
.masthead-nav a.masthead-sub:hover { color: #1d1d1f; }

.theme-toggle {
  flex: 0 0 auto;
  min-height: 34px;
  padding: 8px 14px;
  border: 1px solid var(--on-bar-muted);
  border-radius: var(--r);
  background: transparent;
  color: var(--on-bar);
  font: 600 0.75rem/1 var(--font);
  letter-spacing: 0.08em;
  cursor: pointer;
  white-space: nowrap;
}
.theme-toggle:hover { background: var(--bar-2); }

/* ================================================================
   Buttons and badges — 48px tall, 16px corners, no shadow.
   ================================================================ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: 48px;
  padding: 14px 22px;
  border: 0;
  border-radius: var(--r);
  background: var(--bar);
  color: #ffffff;
  font: 600 var(--text-sm)/1 var(--font);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dur-short) var(--ease-out),
    color var(--dur-short) var(--ease-out);
}
.btn:hover { background: var(--bar-2); color: #ffffff; }
.btn-ghost {
  background: transparent;
  color: var(--text);
  box-shadow: inset 0 0 0 2px var(--text);
}
.btn-ghost:hover { background: var(--text); color: var(--page); }
.btn-sm { min-height: 44px; padding: 12px 16px; font-size: var(--text-xs); }
.btn:disabled,
.btn[aria-disabled="true"] {
  background: var(--disabled);
  color: var(--disabled-text);
  box-shadow: none;
  cursor: not-allowed;
}
.btn.is-loading,
.btn[data-state="loading"] { opacity: 0.7; cursor: wait; }
.btn[data-state="error"] { background: var(--red); color: #ffffff; }
.btn[data-state="success"]::before { content: "\2713"; }

/* On a colour field the button inverts into the field's own two colours. */
.on-tile .btn { background: var(--on-tile); color: var(--tile); }
.on-tile .btn:hover { background: var(--tile); color: var(--on-tile); box-shadow: inset 0 0 0 2px var(--on-tile); }
.on-tile .btn-ghost { background: transparent; color: var(--on-tile); box-shadow: inset 0 0 0 2px var(--on-tile); }
.on-tile .btn-ghost:hover { background: var(--on-tile); color: var(--tile); }

/* ================================================================
   Front page — lead, meta strip, the honest band, the shelves.
   ================================================================ */
.lead { background: var(--tile, var(--page)); color: var(--on-tile, var(--text)); }
.lead-empty { --tile: var(--page); --on-tile: var(--text); }
/* No media column with nothing published — the copy is measured instead, so
   the hero still starts on the same left edge as every band under it. */
.lead-empty .lead-inner { grid-template-columns: minmax(0, 1fr); }
.lead-empty .lead-copy { max-width: 44rem; }
.lead-empty .lead-dek { max-width: 46ch; color: var(--text-2); }
.lead-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-lg);
  align-items: center;
  padding-block: clamp(28px, 5vw, 56px);
}
.lead-inner > * { min-width: 0; }
.lead-copy { order: -1; }
.lead-kicker {
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  margin: 0 0 var(--space-sm);
  padding: 7px 12px;
  border-radius: var(--r);
  background: var(--on-tile, var(--red));
  color: var(--tile, #ffffff);
  font: 600 var(--text-micro)/1 var(--font);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.lead-headline {
  margin: 0 0 var(--space-sm);
  font: 700 var(--text-display)/0.99 var(--font);
  letter-spacing: var(--track-display);
  overflow-wrap: anywhere;
}
/* The sheet gives the lead-with-a-story 68px and the empty page 80px: with a
   screenshot beside it the headline has a narrower column to live in. */
.lead:not(.lead-empty) .lead-headline { font-size: clamp(2.25rem, 5.2vw, 4.25rem); }
.lead-headline a { color: inherit; text-decoration: none; }
.lead-headline a:hover { text-decoration: underline; text-underline-offset: 4px; }
.lead-dek {
  margin: 0 0 var(--space-md);
  max-width: 48ch;
  font: 400 var(--text-lead)/1.5 var(--font);
}
.lead-game {
  margin: 0 0 var(--space-2xs);
  font: 600 var(--text-micro)/1.3 var(--font);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
}
.lead-media {
  border-radius: var(--r);
  background: var(--bar);
  padding: var(--shot-inset);
}
.lead-media img,
.article-media img,
.issue-card-media img {
  width: 100%;
  display: block;
  height: auto;
  aspect-ratio: 256 / 240;
  object-fit: cover;
  image-rendering: pixelated;
  border-radius: var(--r-shot);
  background: var(--screen);
  margin-inline: auto;
}
/* NES art only scales cleanly by whole numbers. At 1.72x nearest-neighbour
   draws some source pixels two screen pixels wide and others one, and the
   grid visibly wobbles. Where a whole multiple fits its column we pin it
   (see the 72rem/36rem blocks and #screen); below that the phone's own 2-3x
   device pixels hide the difference and filling the width matters more. */

.meta-strip {
  background: var(--band);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-lg);
  padding-block: var(--space-sm);
  font: 500 0.8125rem/1.4 var(--font);
  color: var(--text-muted);
}
.meta-row b { font-weight: 500; color: var(--text); }
.meta-row a { color: var(--text); }

.honest-band { background: var(--band); border-bottom: 1px solid var(--rule); }
.honest-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
  padding-block: var(--space-md);
}
.honest-lede {
  margin: 0;
  font: 700 var(--text-card)/1.2 var(--font);
  letter-spacing: var(--track-card);
}
.honest-note {
  margin: 0;
  max-width: 56ch;
  font: 400 var(--text-sm)/1.55 var(--font);
  color: var(--text-muted);
}

.latest, .subscribe, .steps-section { padding-block: clamp(28px, 4vw, 44px); }
.shelf-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}
.shelf-head .section-title { margin: 0; }
.shelf-more { font: 600 var(--text-xs)/1 var(--font); text-decoration: none; }
.section-title {
  margin: 0 0 var(--space-md);
  font: 700 var(--text-section)/1.1 var(--font);
  letter-spacing: var(--track-section);
  color: var(--text);
}
.section-label {
  margin: 0 0 var(--space-sm);
  font: 600 var(--text-micro)/1 var(--font);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.shelf-count { color: var(--text-muted); font-weight: 500; }
.shelf-note { margin: 0 0 var(--space-md); color: var(--text-muted); font-size: var(--text-sm); }

.strip-section { padding-block: clamp(20px, 3vw, 32px) 0; }
.test-prints-strip {
  margin: 0;
  border-radius: var(--r);
  background: var(--band);
  border-left: 6px solid var(--red);
  padding: 18px 20px;
  font: 400 var(--text-sm)/1.6 var(--font);
  color: var(--text-2);
}
.test-prints-strip a { color: var(--link); text-decoration: none; font-weight: 600; }

/* ---- the four steps, on the front page and About ---- */
.steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-sm);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr));
}
.step {
  border-radius: var(--r);
  border: 1px solid transparent;   /* so step 1's edge costs no height */
  background: var(--band);
  color: var(--text);
  padding: 22px;
  display: flex;
  flex-direction: column;
}
.step-1 {
  background: var(--card-ink);
  border-color: var(--card-ink-edge);
  color: var(--on-card-ink);
}
.step-1 .step-num { color: var(--yellow); }
.step-1 .step-body { color: var(--on-bar-2); }
.step-2 .step-num { color: var(--blue); }
.step-3 .step-num { color: var(--green-num); }
.step-4 .step-num { color: var(--red); }
.step-num {
  margin: 0 0 var(--space-sm);
  font: 700 2.5rem/1 var(--font);
  letter-spacing: var(--track-section);
}
.step-title {
  margin: 0 0 var(--space-2xs);
  font: 600 1.125rem/1.2 var(--font);
  letter-spacing: -0.015em;
}
.step-body { margin: 0; font: 400 var(--text-xs)/1.55 var(--font); color: var(--text-muted); }

/* ---- subscribe ---- */
.subscribe-box {
  border-radius: var(--r);
  border: 1px solid var(--card-ink-edge);
  background: var(--card-ink);
  color: var(--on-card-ink);
  padding: var(--space-lg);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-lg);
  flex-wrap: wrap;
}
.subscribe-box .section-title { color: var(--on-bar); margin: 0 0 var(--space-2xs); }
.subscribe-box .shelf-note { color: var(--on-bar-2); margin: 0; }
.sub-actions { display: flex; align-items: center; gap: var(--space-2xs); flex-wrap: wrap; }
.sub-follow { display: inline-flex; gap: var(--space-2xs); flex-wrap: wrap; }
.subscribe-box .btn { background: var(--yellow); color: #1d1d1f; }
.subscribe-box .btn:hover { background: #ffffff; color: #1d1d1f; }
.subscribe-box .btn-ghost {
  background: transparent;
  color: var(--on-bar);
  box-shadow: inset 0 0 0 1px var(--on-bar-muted);
}
.subscribe-box .btn-ghost:hover { background: var(--bar-2); color: var(--on-bar); }

/* ================================================================
   Issue tiles — the screenshot sits on the level colour with a 14px
   inset, the tile is clipped at 16px, the screenshot at 8px.
   ================================================================ */
.archive-grid {
  display: grid;
  gap: var(--space-sm);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
}
/* The front page's shelf is not the archive. With one or two issues an
   auto-fill grid leaves three empty tracks and the page reads as broken, so
   the shelf is only as wide as it has cards to fill -- site_pages.py sends
   the count as --cols. */
.shelf-grid { max-width: min(100%, calc(var(--cols, 4) * 340px)); }
.issue-card {
  display: flex;
  flex-direction: column;
  border-radius: var(--r);
  overflow: hidden;
  background: var(--tile);
  color: var(--on-tile);
  transition: transform var(--dur-short) var(--ease-out);
}
/* A lift, not a shadow -- the sheet's no-shadow rule is about depth being a
   colour field, and it still is: nothing appears under the card. */
.issue-card:hover { transform: translateY(-3px); }
.issue-card[hidden] { display: none; }
.issue-card-media { display: block; padding: var(--shot-inset) var(--shot-inset) 0; }
.issue-card-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
  padding: 14px 16px 18px;
}
.issue-card-kicker {
  margin: 0;
  font: 600 0.71875rem/1.4 var(--font);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
}
.issue-card-headline {
  margin: 0;
  font: 700 var(--text-tile)/1.1 var(--font);
  letter-spacing: var(--track-section);
  overflow-wrap: anywhere;
}
.issue-card-headline a { color: inherit; text-decoration: none; }
.issue-card-headline a:hover { text-decoration: underline; text-underline-offset: 3px; }
.issue-card-game { margin: 0; font: 400 var(--text-xs)/1.45 var(--font); }
.issue-card-row { display: flex; gap: var(--space-2xs); margin-top: auto; padding-top: 12px; }
.issue-card-row .btn { flex: 1; }

/* ================================================================
   Archive
   ================================================================ */
.page-head { padding-block: clamp(24px, 4vw, 40px) var(--space-md); }
.page-title {
  margin: 0 0 var(--space-2xs);
  font: 700 var(--text-h1)/1 var(--font);
  letter-spacing: var(--track-display);
}
.page-sub { margin: 0; font: 400 var(--text-lead)/1.5 var(--font); color: var(--text-muted); }
.archive-filters {
  display: flex;
  gap: var(--space-2xs);
  flex-wrap: wrap;
  align-items: center;
  margin: var(--space-md) 0 var(--space-lg);
}
.chip {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 10px 16px;
  border: 1px solid transparent;   /* so the selected chip's edge costs no height */
  border-radius: var(--r);
  background: var(--band);
  color: var(--text);
  font: 600 0.84375rem/1 var(--font);
  text-decoration: none;
  white-space: nowrap;
}
.chip:hover { background: var(--rule); color: var(--text); }
.chip-all {
  background: var(--card-ink);
  border-color: var(--card-ink-edge);
  color: var(--on-card-ink);
}
.chip-all:hover { background: var(--bar-2); color: var(--on-card-ink); }
.archive-search {
  flex: 1;
  min-width: 220px;
  min-height: 40px;
  padding: 11px 14px;
  border: 2px solid var(--text);
  border-radius: var(--r);
  background: var(--page);
  color: var(--text);
  font: 400 var(--text-xs)/1 var(--font);
}
.archive-search::placeholder { color: var(--text-muted); }
.archive-section { padding-block: 0 clamp(28px, 4vw, 40px); }
.season-actions { display: flex; gap: var(--space-2xs); flex-wrap: wrap; }
.season-box {
  border-radius: var(--r);
  background: var(--band);
  padding: 24px 28px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}
.season-box .shelf-note { margin: 0; max-width: 60ch; }
.season-title {
  margin: 0 0 6px;
  font: 700 var(--text-card)/1.15 var(--font);
  letter-spacing: var(--track-card);
}

/* ================================================================
   About
   ================================================================ */
.about-grid {
  display: grid;
  gap: var(--space-lg);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  padding-block: clamp(28px, 4vw, 40px);
  border-top: 1px solid var(--rule);
}
.about-section h2 {
  margin: 0 0 var(--space-xs);
  font: 700 1.5rem/1.15 var(--font);
  letter-spacing: var(--track-card);
}
.about-section p {
  margin: 0;
  font: 400 var(--text-md)/1.65 var(--font);
  color: var(--text-2);
}

/* ================================================================
   Small print — Terms, Privacy, Cookie Notice.
   One measured column, not About's auto-fit grid: these are read top
   to bottom rather than scanned across, and prose set to the full
   wrap is prose nobody finishes.
   ================================================================ */
.legal-updated {
  margin: var(--space-sm) 0 0;
  font: 500 var(--text-xs)/1.5 var(--font);
  color: var(--text-muted);
}
.legal {
  padding-block: clamp(28px, 4vw, 40px);
  border-top: 1px solid var(--rule);
}
.legal-section + .legal-section { margin-top: var(--space-lg); }
.legal-section h2 {
  margin: 0 0 var(--space-xs);
  max-width: var(--measure);
  font: 700 1.375rem/1.2 var(--font);
  letter-spacing: var(--track-card);
}
.legal p,
.legal ul { max-width: var(--measure); }
.legal p {
  margin: 0 0 0.9em;
  font: 400 var(--text-md)/1.7 var(--font);
  color: var(--text-2);
}
.legal p:last-child { margin-bottom: 0; }
.legal ul { margin: 0 0 0.9em; padding-left: 1.15em; }
.legal li {
  margin: 0 0 0.5em;
  font: 400 var(--text-md)/1.7 var(--font);
  color: var(--text-2);
}
.legal li:last-child { margin-bottom: 0; }
/* The storage keys are quoted verbatim so a reader can go and look for
   them, which only works if they read as keys and not as prose. */
/* Tight horizontal padding on purpose: at 0.35em the band pushed the
   following comma a full space away and the prose read as mistyped. */
.legal code {
  border-radius: 4px;
  background: var(--band);
  padding: 0.1em 0.2em;
  font: 400 0.9em/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text);
}

/* ================================================================
   Issue page — a coloured header band, then the story.
   ================================================================ */
.article-head { background: var(--tile, var(--bar)); color: var(--on-tile, var(--on-bar)); }
.article-head-inner { padding-block: clamp(24px, 4vw, 36px); }
.article-kicker {
  margin: 0 0 var(--space-xs);
  font: 600 var(--text-micro)/1 var(--font);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.article-headline {
  margin: 0 0 var(--space-xs);
  max-width: 34ch;
  font: 700 var(--text-h1)/1.02 var(--font);
  letter-spacing: -0.035em;
  overflow-wrap: anywhere;
}
.article-game-line { margin: 0; font: 600 1.125rem/1.3 var(--font); letter-spacing: -0.015em; }
.article-game-line span { font-weight: 400; }

/* The prose is measured, not the column — so the story starts on the same
   left edge as the coloured band above it instead of floating in the middle. */
.article { padding-block: var(--space-lg) var(--space-xl); }
.article-dek,
.article-byline,
.issue-source,
.article-body p,
.article-note,
.article-game { max-width: var(--measure); }
/* 512 + the sheet's 14px inset on both sides. */
.article-media { max-width: 540px; }
@media (min-width: 36rem) { .article-media img { width: 512px; } }
.crumbs-bar { background: var(--band); border-bottom: 1px solid var(--rule); }
.crumbs { margin: 0; padding-block: 11px; font-size: var(--text-xs); color: var(--text-muted); }
.crumbs a { color: var(--link); }
.crumbs span { color: var(--text); }
.article-dek {
  margin: 0 0 var(--space-md);
  font: 400 var(--text-lead)/1.5 var(--font);
  color: var(--text-2);
}
.article-media { margin: 0 0 var(--space-md); border-radius: var(--r); background: var(--bar); padding: var(--shot-inset); }
.article-actions { display: flex; gap: var(--space-2xs); flex-wrap: wrap; margin: var(--space-md) 0; }
.article-byline {
  margin: 0;
  padding-top: 10px;
  border-top: 1px solid var(--rule);
  font: 500 var(--text-xs)/1.5 var(--font);
  color: var(--text-muted);
}
/* On a colour field the byline keeps the field's own ink — the sheet's rule
   is that type on colour is solid, so it is not faded to make it secondary.
   It also loses its rule: the field is already the separation. */
.lead .article-byline { border-top: 0; padding-top: 0; color: inherit; }
.lead .article-byline a { color: inherit; }
.issue-source { margin: var(--space-2xs) 0 0; font: 400 var(--text-xs)/1.55 var(--font); color: var(--text-muted); }
.article-body p { margin: 0 0 1.1em; font: 400 var(--text-body)/1.65 var(--font); color: var(--text-2); }
.article-note {
  margin: var(--space-md) 0 0;
  border-radius: var(--r);
  border-left: 6px solid var(--red);
  background: var(--band);
  padding: 18px 20px;
  font: 400 var(--text-sm)/1.6 var(--font);
  color: var(--text-2);
}
.article-game {
  margin: var(--space-md) 0 0;
  padding-top: var(--space-sm);
  border-top: 1px solid var(--rule);
  font: 400 var(--text-xs)/1.6 var(--font);
  color: var(--text-muted);
}
.article-game strong { color: var(--text); font-weight: 600; }

/* Where an issue lets you out: the week before it, the week after it, and the
   archive. */
.issue-foot {
  margin-top: var(--space-xl);
  padding-top: var(--space-md);
  border-top: 1px solid var(--rule);
}
/* Two halves, so a lone neighbour still sits on its own side of the page
   instead of stretching across it. */
.issue-nav { display: grid; gap: var(--space-2xs); }
@media (min-width: 40rem) {
  .issue-nav { grid-template-columns: 1fr 1fr; }
  .issue-nav-link.is-next { grid-column: 2; }
}
.issue-nav-link {
  display: flex;
  flex-direction: column;
  gap: 5px;
  border-radius: var(--r);
  background: var(--band);
  color: var(--text);
  padding: 16px 18px;
  text-decoration: none;
  transition: background-color var(--dur-short) var(--ease-out);
}
.issue-nav-link:hover { background: var(--rule); color: var(--text); }
.issue-nav-link.is-next { text-align: right; }
.issue-nav-dir {
  font: 600 var(--text-micro)/1 var(--font);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}
.issue-nav-name {
  font: 700 1.0625rem/1.25 var(--font);
  letter-spacing: var(--track-card);
  overflow-wrap: anywhere;
}
.issue-nav-all { margin: var(--space-sm) 0 0; font: 600 var(--text-xs)/1 var(--font); }
.issue-nav-all a { text-decoration: none; }

/* ================================================================
   Player
   ================================================================ */
/* The player is the issue's tile at full size: app.js puts the same
   `on-tile tile-<colour>` classes on the dialog that the card and the article
   band carry, so the level colour does not vanish at the moment you enter the
   level. The dark surfaces inside it (the screen's inset, the pad, the board)
   are fixed rather than themed -- they have to stay dark on a yellow tile as
   much as on a purple one. */
.modal {
  --m-ink: #1d1d1f;
  --m-ink-2: #2c2c2e;
  --m-ink-3: #3a3a3c;
  position: fixed;
  inset: 0;
  z-index: 50;
  display: none;
  justify-items: center;
  /* `safe` so a box taller than the viewport is scrolled to from its top
     rather than centred with its head cut off and unreachable. */
  align-items: safe center;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: clamp(8px, 3vw, 24px);
  border: 0;
  /* The dialog scrolls, not the box: a scrollbar inside the box would eat ~15
     unpredictable pixels off the screen's width and put the whole-number
     scaling back where it started. */
  overflow: auto;
  background: transparent;
}
.modal[open] { display: grid; }
.modal::backdrop { background: var(--backdrop); }
.modal-box {
  position: relative;
  /* 768 (3x) + the 14px inset both sides + 20px of box padding both sides. */
  width: min(836px, 100%);
  border-radius: var(--r);
  background: var(--tile, var(--m-ink));
  color: var(--on-tile, #ffffff);
  padding: 20px;
}
.modal[open] .modal-box { animation: box-in var(--dur-short) var(--ease-out); }
@keyframes box-in { from { opacity: 0; transform: translateY(10px); } }
.modal-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-xs);
}
.modal-head > div { min-width: 0; }
.modal-head h3 {
  margin: 0;
  min-width: 0;
  font: 600 var(--text-md)/1.3 var(--font);
  overflow-wrap: anywhere;
}
.m-sub {
  margin: var(--space-2xs) 0 0;
  font: 400 var(--text-xs)/1.5 var(--font);
  color: var(--on-tile, #98989d);
  overflow-wrap: anywhere;
}
.icon-btn {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: var(--r-round);
  background: var(--m-ink-2);
  color: #ffffff;
  font-size: 15px;
  cursor: pointer;
}
.icon-btn:hover { background: var(--m-ink-3); }

/* The same dark inset the sheet gives every screenshot, around the live one. */
.screen-wrap {
  display: grid;
  justify-items: center;
  margin: var(--space-sm) 0;
  border-radius: var(--r);
  background: var(--m-ink);
  padding: var(--shot-inset);
}
.screen-inset {
  position: relative;
  width: 100%;
  max-width: 100%;
  line-height: 0;
  border-radius: var(--r-shot);
  overflow: hidden;
  background: var(--screen);
}
:root[data-theme="dark"] .screen-inset::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(0deg, var(--scanline) 0 1px, transparent 1px 3px);
}
/* The one staged moment on the site: the cartridge coming on. */
.screen-inset.is-booting { animation: power-on var(--dur-boot) var(--ease-out); }
@keyframes power-on {
  0%   { clip-path: inset(50% 0); filter: brightness(2.6); }
  55%  { clip-path: inset(0);     filter: brightness(1.7); }
  100% { clip-path: inset(0);     filter: brightness(1); }
}
#screen {
  width: 100%;
  display: block;
  aspect-ratio: 256 / 240;
  image-rendering: pixelated;
}
/* Whole multiples only, and only where one fits the window in both directions
   -- a 3x screen is 768x720, so it needs the height as much as the width. The
   width goes on the inset, not the canvas: .screen-wrap centres its child, so
   a canvas sized in % of an auto-width parent would collapse to 256. */
@media (min-width: 40rem) and (min-height: 46rem) { .screen-inset { width: 512px; } }
@media (min-width: 56rem) and (min-height: 62rem) { .screen-inset { width: 768px; } }

/* The win panel is a state, not an identity, so it does not take the level
   colour -- it would collide with it on the green and yellow tiles. It is the
   dark card the board already is, with the numbers in the masthead's yellow. */
#m-result {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
  margin: 0 0 var(--space-sm);
  border-radius: var(--r);
  background: var(--m-ink);
  color: #ffffff;
  padding: 18px;
}
#m-result[hidden] { display: none; }
.m-result-headline { margin: 0; font: 600 var(--text-xs)/1.4 var(--font); color: #c7c7cc; }
.m-result-stats {
  margin: 0;
  font: 700 2rem/1 var(--font);
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
  color: var(--yellow);
}
.m-result-caption { margin: 0; font-size: var(--text-xs); }
.m-result-rank { margin: 0; font: 600 var(--text-xs)/1.3 var(--font); color: #00d69b; }
#m-result .btn { margin-top: var(--space-2xs); background: #ffffff; color: #1d1d1f; }
#m-result .btn:hover { background: var(--yellow); color: #1d1d1f; }
#m-result .btn-ghost { background: transparent; color: #ffffff; box-shadow: inset 0 0 0 2px #ffffff; }
#m-result .btn-ghost:hover { background: #ffffff; color: #1d1d1f; }

#m-board:empty { display: none; }
#m-board {
  border-radius: var(--r);
  background: var(--m-ink);
  color: #ffffff;
  padding: 18px;
  margin: 0 0 var(--space-sm);
}
.m-board-title {
  margin: 0 0 var(--space-xs);
  font: 600 0.6875rem/1 var(--font);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: #98989d;
}
.m-board-list { list-style: none; margin: 0; padding: 0; }
.m-board-list li {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 9px 0;
  border-bottom: 1px solid var(--m-ink-3);
  font: 500 var(--text-sm)/1.4 var(--font);
}
.m-board-list li:last-child { border-bottom: 0; }
.m-board-rank { width: 2.2em; color: var(--yellow); font-weight: 700; }
.m-board-score { flex: 1; font-variant-numeric: tabular-nums; }
.m-board-time { color: #98989d; font-variant-numeric: tabular-nums; }

.pad {
  display: none;
  grid-template-columns: 132px minmax(0, 1fr);
  grid-template-areas: "dpad ab" "dpad se";
  align-items: center;
  gap: 10px;
  margin-bottom: var(--space-xs);
}
@media (pointer: coarse) { .pad { display: grid; } }
.dpad { grid-area: dpad; position: relative; width: 132px; height: 132px; }
.pb {
  min-width: 44px;
  min-height: 44px;
  border: 0;
  border-radius: var(--r-shot);
  background: var(--m-ink-2);
  color: #ffffff;
  font: 600 0.9375rem/1 var(--font);
  cursor: pointer;
  user-select: none;
  touch-action: none;
}
.pb.held { background: var(--yellow); color: #1d1d1f; }
.dpad .pb { position: absolute; width: 44px; height: 44px; }
.dpad .up { top: 0; left: 44px; }
.dpad .down { bottom: 0; left: 44px; }
.dpad .left { top: 44px; left: 0; }
.dpad .right { top: 44px; right: 0; }
.ab { grid-area: ab; display: flex; gap: 12px; justify-content: flex-end; }
.ab .pb { width: 60px; height: 60px; border-radius: var(--r-round); font-size: 1.0625rem; }
.ab .a { background: var(--red); }
.ab .b { background: var(--blue); }
.se { grid-area: se; display: flex; gap: 10px; justify-content: flex-end; }
.se .pb { padding: 12px 16px; border-radius: var(--r); font-size: 0.78125rem; }

.modal-foot { display: flex; justify-content: space-between; align-items: center; gap: var(--space-xs); flex-wrap: wrap; }
/* Type on a colour field is solid, so the legend is not greyed down here --
   it steps back by size and by sitting opposite the actions instead. */
.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px var(--space-sm);
  color: var(--on-tile, #98989d);
  font-size: var(--text-micro);
}
kbd {
  border-radius: var(--r-shot);
  background: var(--m-ink-2);
  padding: 3px 7px;
  font: 500 0.6875rem/1 var(--font-mono);
  color: #ffffff;
}
.actions { display: flex; flex-wrap: wrap; gap: var(--space-2xs); }
/* Same inversion as .on-tile .btn, restated with a fallback for the moment
   before app.js has put a level colour on the dialog. */
.modal .btn { background: var(--on-tile, #ffffff); color: var(--tile, #1d1d1f); }
.modal .btn:hover {
  background: var(--tile, #1d1d1f);
  color: var(--on-tile, #ffffff);
  box-shadow: inset 0 0 0 2px var(--on-tile, #ffffff);
}
.modal .btn-ghost {
  background: transparent;
  color: var(--on-tile, #ffffff);
  box-shadow: inset 0 0 0 2px var(--on-tile, #ffffff);
}
.modal .btn-ghost:hover { background: var(--on-tile, #ffffff); color: var(--tile, #1d1d1f); }
.modal .btn:disabled { background: var(--m-ink-2); color: #98989d; box-shadow: none; }

/* ================================================================
   Footer + interaction
   ================================================================ */
.site-foot { margin-top: var(--space-lg); border-top: 1px solid var(--rule); }
.site-foot .wrap {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
  padding-block: var(--space-md) var(--space-lg);
}
.site-foot p { margin: 0; font: 400 0.78125rem/1.6 var(--font); color: var(--text-muted); }
/* The small print keeps the footer's muted weight rather than the link
   colour: it has to be findable, not the loudest thing down here. */
.foot-legal a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.foot-legal a:hover { color: var(--link-hover); }

:where(a, button, input, [tabindex]):focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
  box-shadow: 0 0 0 3px var(--focus-inner);
}
.on-tile :where(a, button, input):focus-visible,
.modal :where(a, button, input):focus-visible {
  outline-color: var(--yellow);
  box-shadow: 0 0 0 3px #1d1d1f;
}
.icon-btn:disabled,
.pb:disabled,
.theme-toggle:disabled { opacity: 0.55; cursor: not-allowed; }

/* Narrow: the wordmark and the toggle share the top line and the sections
   wrap onto their own, rather than each landing on a row of its own. */
@media (max-width: 39.99rem) {
  .masthead-row { row-gap: 0; }
  .theme-toggle { order: 2; }
  .masthead-nav { order: 3; flex-basis: 100%; gap: 14px; padding-bottom: 10px; }
  /* Stacked buttons of different widths read as a ragged edge, not a pair. */
  .article-actions { flex-direction: column; align-items: stretch; }
}

@media (min-width: 40rem) {
  .lead-inner { padding-block: clamp(36px, 5vw, 56px); }
}

@media (min-width: 56rem) {
  .lead-inner {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: var(--space-xl);
  }
  .lead-copy { order: 0; }
  .pad { grid-template-columns: 132px minmax(0, 1fr) auto; grid-template-areas: "dpad ab se"; }
  .se { flex-direction: column; }
}

@media (min-width: 72rem) {
  /* .wrap is at its 1120 max from here, so the media column can be pinned to
     the exact width of a 2x screen inside its inset: 512 + 14 + 14. */
  .lead-inner { grid-template-columns: minmax(0, 1fr) 540px; }
  .lead-media img { width: 512px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  /* Without its transition the card lift is a jump, which is the thing this
     query is asked to prevent -- so it does not move at all. */
  .issue-card:hover { transform: none; }
}
