/* ============================================================
   ServerDimitri Browser Games: style.css
   Black background, subtle red particle network, browser-window
   game cards. Dev/terminal-flavoured typography.
   ============================================================ */

/* Self-hosted variable fonts (CSP allows 'self' only) */
@font-face {
  font-family: "Space Grotesk";
  src: url("../fonts/space-grotesk.woff2") format("woff2");
  font-weight: 300 700;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("../fonts/jetbrains-mono.woff2") format("woff2");
  font-weight: 100 800;
  font-display: swap;
}

:root {
  --bg: #000000;
  --panel: #0d0d0f;
  --panel-2: #131316;
  --chrome: #1a1a1e;
  --border: #26262b;
  --red: #e5343e;
  --red-soft: rgba(229, 52, 62, 0.35);
  --text: #eaeaea;
  --text-dim: #9a9aa2;
  --text-faint: #63636b;
  --radius: 14px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: "Space Grotesk", "Segoe UI", system-ui, -apple-system, Roboto, Arial, sans-serif;
  min-height: 100vh;
  overflow-x: hidden;
}

/* ---------- Background canvas ---------- */
#net-canvas {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* Faint red square grid over the whole page.

   Normally the canvas draws this itself, because a second fixed full-viewport
   layer is one more surface for the browser to composite on every frame of the
   animation behind it. Reduced motion switches the canvas off entirely, so in
   that case only there is no canvas to draw it and the CSS version stands in. */
@media (prefers-reduced-motion: reduce) {
  body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image:
      linear-gradient(rgba(229, 52, 62, 0.11) 1px, transparent 1px),
      linear-gradient(90deg, rgba(229, 52, 62, 0.11) 1px, transparent 1px);
    background-size: 56px 56px;
  }
}

/* Everything above the canvas */
.hero, main, .site-footer {
  position: relative;
  z-index: 1;
}

/* ---------- Header ---------- */
/* No box: the title sits directly on the particle background */
.hero {
  text-align: center;
  padding: 48px 24px 44px;
}

/* Terminal prompt line above the title */
.hero-prompt {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  margin-bottom: 14px;
}

/* Two-line title: the name in the display face, then the section as a
   monospace command line with a blinking caret — leans into the terminal
   theme and reads far less flat than plain two-tone text. */
.hero-title { line-height: 1.02; }

.hero-title .title-line { display: block; }

.hero-title .title-name {
  font-size: clamp(2.4rem, 6vw, 4.1rem);
  font-weight: 700;
  letter-spacing: -0.03em;
}

.hero-title .title-cmd {
  margin-top: 8px;
  font-family: "JetBrains Mono", monospace;
  font-weight: 700;
  font-size: clamp(1.5rem, 4vw, 2.7rem);
  letter-spacing: -0.01em;
  color: var(--red);
  text-shadow: 0 0 26px rgba(229, 52, 62, 0.45);
}

/* Blinking block cursor after the command */
.hero-title .caret {
  display: inline-block;
  width: 0.6ch;
  height: 1em;
  vertical-align: -0.12em;
  margin-left: 0.14em;
  background: var(--red);
  box-shadow: 0 0 12px rgba(229, 52, 62, 0.9);
  animation: caret-blink 1.05s steps(1) infinite;
}

@keyframes caret-blink { 50% { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .hero-title .caret { animation: none; }
}

/* Typewriter caret on the prompt command while JS types it out (item 10).
   Only present during the .typing window; reduced-motion skips typing
   entirely (see main.js) so this caret never shows there. */
.hero-prompt .prompt-cmd.typing::after {
  content: "";
  display: inline-block;
  width: 0.55ch;
  height: 1em;
  vertical-align: -0.12em;
  margin-left: 2px;
  background: var(--red);
  box-shadow: 0 0 10px rgba(229, 52, 62, 0.8);
  animation: caret-blink 1.05s steps(1) infinite;
}

/* Subtitle styled as a browser URL bar */
.hero-sub {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 18px;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.88rem;
  color: var(--text-dim);
  background: rgba(13, 13, 16, 0.85);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 10px 22px;
}

.hero-sub .lock { font-size: 0.8rem; }

/* Small status line under the URL bar */
.status-line {
  margin-top: 12px;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.78rem;
  color: var(--text-faint);
}

.status-line .ok { color: #28c840; }

/* Live total plays: lift it out of the faint status grey so it reads —
   the number in the site red, "plays" in a brighter grey */
#plays-total { color: var(--text-dim); }
#plays-total .plays-n { color: var(--red); font-weight: 600; }

/* ---------- Watch mode ---------- */
/* A quiet ghost button under the status line, and the class it toggles on
   <body>. Hiding the page with display: none rather than opacity means the
   browser stops laying out and painting all of it, so watching the simulation
   costs less than looking at the page does. */
.watch-btn {
  /* absolute, not fixed: it sits at the top-right of the page and scrolls away
     with it rather than following the viewport down the whole page */
  position: absolute;
  top: 14px;
  right: 16px;
  z-index: 3;                  /* over the canvas and the page content */
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: rgba(8, 8, 9, 0.7);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 14px;
  color: var(--text-faint);
  font-family: "JetBrains Mono", monospace;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}

/* Nothing to click while watching: the hint, Esc and any click all lead out */
body.watching .watch-btn { display: none; }

.watch-btn:hover,
.watch-btn:focus-visible {
  color: var(--red);
  border-color: var(--red-soft);
  box-shadow: 0 0 18px rgba(229, 52, 62, 0.25);
}

body.watching .hero,
body.watching main,
body.watching .site-footer { display: none; }

body.watching { overflow: hidden; }

/* The way out. Fades itself after a few seconds so it stops competing with the
   thing you came to look at, and comes back on any pointer move. */
.watch-hint {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%);
  z-index: 2;
  margin: 0;
  padding: 9px 18px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: rgba(8, 8, 9, 0.72);
  color: var(--text-faint);
  font-family: "JetBrains Mono", monospace;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  transition: opacity 0.6s ease;
}

.watch-hint kbd {
  font: inherit;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
}

.watch-hint.is-faded { opacity: 0; }

/* ---------- Sections ---------- */
main { padding-bottom: 96px; }

.game-section {
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 24px;
}

.game-section + .game-section { margin-top: 68px; }

/* Full-width rule that visually separates the sections
   (main project / side projects / abandoned) */
.section-divider {
  max-width: 1032px;
  margin: 64px auto;
  border: 0;
  height: 1px;
  background: linear-gradient(90deg,
    transparent,
    var(--border) 18%,
    var(--border) 82%,
    transparent);
}

/* A divider sits between two sections, so drop the extra section gap it
   would otherwise stack on top of */
.section-divider + .game-section { margin-top: 0; }

/* Terminal-flavoured section heading with a trailing rule line */
.section-heading {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 26px;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.9rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.section-heading .sh-mark {
  width: 9px;
  height: 9px;
  flex-shrink: 0;
  border-radius: 2px;
  background: var(--red);
  box-shadow: 0 0 10px rgba(229, 52, 62, 0.7);
}

/* Abandoned section: grey, unlit square instead of the glowing red one */
.section-heading .sh-mark-dim {
  background: var(--text-faint);
  box-shadow: none;
}

.section-heading .sh-suffix {
  color: var(--text-faint);
  letter-spacing: 0.06em;
  text-transform: none;
}

.section-heading::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--border), transparent);
}

/* Side projects: three cards across, stated rather than inferred.
   This was `repeat(auto-fit, minmax(N, 1fr))`, which derives the column count
   from the track minimum — so the answer changed with the window and the third
   card kept dropping onto a row of its own, sitting half-width beside a hole.
   Lowering N only moves the width where that happens: at 300px it broke on a
   1080p laptop, at 272px it still broke at a 903px window. Three is a layout
   decision, so it is written down, and so are the two step-downs.

   NOTE: this is a count, not a rule that adapts. A fourth side project will
   wrap to a second row with two empty slots beside it — at that point either
   make this `repeat(2, ...)` or add a fifth card. */
.games-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
}

/* Two across before the cards get too narrow to read. A media query further
   down (max-width: 720px) then takes it to one. */
@media (max-width: 880px) {
  .games-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Featured (main project) card holder */
.games-featured { display: block; }

/* ---------- Game card (browser window) ---------- */
.game-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}

.game-card:hover {
  transform: translateY(-6px);
  border-color: var(--red-soft);
  box-shadow:
    0 12px 32px rgba(0, 0, 0, 0.6),
    0 0 24px rgba(229, 52, 62, 0.12);
}

/* ---------- Card entrance reveal ---------- */
/* JS adds .reveal to each card, then .is-visible when it scrolls into
   view, and removes both once the transition ends (so the settled card
   is fully natural and its hover-lift keeps working). Wrapped in a
   no-preference query, so reduced-motion users see cards immediately.
   No JS -> the class is never added -> cards are visible too. */
@media (prefers-reduced-motion: no-preference) {
  .game-card.reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 0.6s ease, transform 0.6s ease;
    will-change: opacity, transform;
  }
  .game-card.reveal.is-visible {
    opacity: var(--card-opacity, 1);
    transform: translateY(0);
  }
}

/* ---------- Featured (main project) card ---------- */
/* Cinematic: chrome bar on top, a big screenshot fills the card, and the
   title / tagline / Play float over the image on a dark scrim. An ambient
   red glow lifts the whole card so the main game grabs attention. */
.game-card.featured {
  position: relative;
  display: block;
  box-shadow:
    0 0 0 1px rgba(229, 52, 62, 0.10),
    0 22px 60px rgba(0, 0, 0, 0.55),
    0 0 70px rgba(229, 52, 62, 0.15);
}

.game-card.featured:hover {
  transform: translateY(-6px);
  border-color: var(--red-soft);
  box-shadow:
    0 0 0 1px rgba(229, 52, 62, 0.30),
    0 28px 74px rgba(0, 0, 0, 0.6),
    0 0 96px rgba(229, 52, 62, 0.26);
}

/* ---------- Per-game accent colour (border + hover glow) ---------- */
/* Each card gets --accent from games.js (JS sets it inline); it defaults to
   the site red, so a game with no accent looks exactly as before. These
   rules come after the red-hardcoded ones above and override just the
   colour, leaving the transform / geometry untouched. color-mix keeps a
   single hex in games.js while deriving the soft border + ambient glow. */
.game-card { --accent: var(--red); }
.game-card:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  box-shadow:
    0 12px 32px rgba(0, 0, 0, 0.6),
    0 0 24px color-mix(in srgb, var(--accent) 20%, transparent);
}
.game-card.featured {
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--accent) 12%, transparent),
    0 22px 60px rgba(0, 0, 0, 0.55),
    0 0 70px color-mix(in srgb, var(--accent) 16%, transparent);
}
.game-card.featured:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent),
    0 28px 74px rgba(0, 0, 0, 0.6),
    0 0 96px color-mix(in srgb, var(--accent) 26%, transparent);
}

/* ---------- Cursor glow ---------- */
/* main.js writes --mx / --my (pointer position within the card) and adds
   .is-glowing while the pointer is over it. The defaults here mean a card with
   no JS, no mouse or reduced motion simply never lights up. Softer than the
   first version: 8% accent over 220px, down from 13% over 260px. */
.game-card {
  position: relative;          /* anchors the overlay */
  --mx: 50%;
  --my: 50%;
}

/* A pseudo-element, so the glow costs no extra node per card, and it is clipped
   by the card's own overflow: hidden. Never takes the pointer, so it cannot
   steal a click from the Play button underneath it. */
.game-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
  background: radial-gradient(
    220px circle at var(--mx) var(--my),
    color-mix(in srgb, var(--accent) 8%, transparent),
    transparent 70%
  );
}

@media (prefers-reduced-motion: no-preference) {
  .game-card.is-glowing::after { opacity: 1; }
}

/* ---------- Tier status dot (green live / amber side / grey abandoned) ---- */
.status-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}
.status-dot.status-live { background: #28c840; box-shadow: 0 0 8px rgba(40, 200, 64, 0.85); }
.status-dot.status-side { background: #febc2e; box-shadow: 0 0 8px rgba(254, 188, 46, 0.7); }
.status-dot.status-dim  { background: #6b6b72; box-shadow: none; }

/* Testing: up and playable, but not a release. Its own colour rather than the
   side-project amber, so "still being built" is never read as "second-rate",
   and it pulses because green sitting perfectly still is what "live" looks
   like. */
.status-dot.status-test {
  background: #8b6cff;
  box-shadow: 0 0 8px rgba(139, 108, 255, 0.85);
  animation: banner-pulse 1.8s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .status-dot.status-test { animation: none; }
}

/* Wide, tall stage for the screenshot.

   max-width matters more than it looks. min-height and aspect-ratio fight each
   other: once the height is clamped to 380px the browser derives the width
   from the ratio instead, giving 380 x 21/9 = 887px. Any card narrower than
   that (viewports 721px to 934px, so small laptops and half-screen windows)
   got a shot wider than its own card, which overflow: hidden then cropped, and
   the right-anchored plays badge was clipped away entirely. Clamping the width
   costs a slightly taller crop in that band and keeps everything inside.
   Below 721px the media query zeroes min-height and the question goes away. */
.game-card.featured .card-shot {
  aspect-ratio: 21 / 9;
  min-height: 380px;
  max-height: 520px;
  max-width: 100%;
}
.game-card.featured .card-shot img { object-position: center; }
.game-card.featured:hover .card-shot img { transform: scale(1.03); }

/* "main project" badge, top-left over the screenshot */
.card-badge {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(229, 52, 62, 0.16);
  border: 1px solid var(--red-soft);
  color: #ff8a90;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 5px 11px;
  border-radius: 999px;
}

/* Announcement pill (e.g. "Multiplayer releasing soon"), sits just under
   the main-project badge. Green + pulsing dot so it reads as good news. */
.card-banner {
  position: absolute;
  top: 47px;
  left: 12px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: rgba(40, 200, 64, 0.14);
  border: 1px solid rgba(40, 200, 64, 0.4);
  color: #7ff09a;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 5px 11px;
  border-radius: 999px;
}

.card-banner .banner-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #28c840;
  box-shadow: 0 0 8px rgba(40, 200, 64, 0.9);
  animation: banner-pulse 1.8s ease-in-out infinite;
}

@keyframes banner-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.45; transform: scale(0.7); }
}

/* Same pill, testing tone: violet instead of green, because the green one
   announces a release and this one announces a work in progress. */
.card-banner.is-testing {
  background: rgba(139, 108, 255, 0.16);
  border-color: rgba(139, 108, 255, 0.45);
  color: #c3b2ff;
}
.card-banner.is-testing .banner-dot {
  background: #8b6cff;
  box-shadow: 0 0 8px rgba(139, 108, 255, 0.9);
}

@media (prefers-reduced-motion: reduce) {
  .card-banner .banner-dot { animation: none; }
}

/* Text floats over the bottom of the screenshot on a dark gradient scrim.
   Grid stacks the title, blurb and note in the left column while the Play
   button stays pinned to the bottom-right across all three rows. */
.game-card.featured .card-body {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "head btn"
    "desc btn"
    "note btn";
  align-items: start;
  column-gap: 24px;
  row-gap: 10px;
  padding: 88px 30px 26px;
  background: linear-gradient(
    to top,
    rgba(5, 5, 7, 0.94) 0%,
    rgba(5, 5, 7, 0.78) 38%,
    rgba(5, 5, 7, 0.32) 70%,
    transparent 100%
  );
}

.game-card.featured .card-head { grid-area: head; }

/* The hero blurb reads brighter than a side card's: it sits on a photo,
   so it needs the extra contrast and a shadow to stay legible. */
.game-card.featured .card-desc {
  grid-area: desc;
  max-width: 62ch;
  font-size: 0.95rem;
  color: var(--text);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.8);
}

.game-card.featured .card-title {
  font-size: 2.2rem;
  line-height: 1.04;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.6);
}
.game-card.featured .card-tagline { font-size: 0.8rem; }

/* The hero's button is the biggest call to action on the page, so it is a size
   up again. It sits on a photo, hence the heavier drop shadow to keep its edge
   defined over whatever the screenshot happens to be doing behind it. */
.game-card.featured .play-btn {
  grid-area: btn;
  align-self: end;
  flex-shrink: 0;
  margin-top: 0;
  padding: 17px 40px;
  font-size: 1.06rem;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.55);
}

/* The rest state above outranks .play-btn:hover on specificity, so without
   this the hero button was the one button on the page that did not glow. */
.game-card.featured .play-btn:hover,
.game-card.featured .play-btn:focus-visible {
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.14),
    0 6px 22px rgba(0, 0, 0, 0.55),
    0 0 30px rgba(229, 52, 62, 0.8),
    0 0 64px rgba(229, 52, 62, 0.4);
}

/* Browser chrome bar */
.card-chrome {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--chrome);
  border-bottom: 1px solid var(--border);
  padding: 10px 12px;
}

.chrome-dots { display: flex; gap: 6px; flex-shrink: 0; }

.chrome-dots span {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  cursor: default;
  transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.chrome-dots span:nth-child(1) { background: #ff5f57; }
.chrome-dots span:nth-child(2) { background: #febc2e; }
.chrome-dots span:nth-child(3) { background: #28c840; }

/* Dots light up on hover; clicking them does nothing on purpose */
.chrome-dots span:hover { transform: scale(1.2); }
.chrome-dots span:nth-child(1):hover { box-shadow: 0 0 10px 2px rgba(255, 95, 87, 0.85); }
.chrome-dots span:nth-child(2):hover { box-shadow: 0 0 10px 2px rgba(254, 188, 46, 0.85); }
.chrome-dots span:nth-child(3):hover { box-shadow: 0 0 10px 2px rgba(40, 200, 64, 0.85); }

.chrome-url {
  flex: 1;
  min-width: 0;
  background: #0a0a0c;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 14px;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.74rem;
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chrome-url .lock { color: var(--text-dim); margin-right: 6px; }

/* Screenshot */
.card-shot {
  position: relative;
  aspect-ratio: 16 / 10;
  background: var(--panel-2);
  overflow: hidden;
}

.card-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  display: block;
  transition: transform 0.35s ease;
}

.game-card:hover .card-shot img { transform: scale(1.04); }

/* ---------- Screenshot slideshow (crossfade) ---------- */
.card-slides { position: absolute; inset: 0; }

.card-slides .slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.1s ease, transform 0.4s ease;
}

.card-slides .slide.is-active { opacity: 1; }
.card-slides .slide.slide-dead { opacity: 0 !important; }

/* Frame dots, centred along the top of the screenshot */
.slide-dots {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  gap: 7px;
}

.slide-dot {
  width: 7px;
  height: 7px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.32);
  cursor: pointer;
  transition: background 0.25s ease, transform 0.25s ease, box-shadow 0.25s ease;
}

.slide-dot:hover { background: rgba(255, 255, 255, 0.6); }

.slide-dot.is-active {
  background: var(--red);
  box-shadow: 0 0 8px rgba(229, 52, 62, 0.8);
  transform: scale(1.15);
}

/* "plays" counter badge in the corner of the screenshot */
/* Every game with a play shows its count. The base badge is deliberately quiet
   (neutral border, dimmed text) so the leader's .is-top variant is the only one
   that reads as hot — see main.js. */
.plays-badge {
  position: absolute;
  top: 10px;
  right: 10px;
  background: rgba(0, 0, 0, 0.78);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-family: "JetBrains Mono", monospace;
  font-size: 0.72rem;
  font-weight: 400;
  padding: 4px 10px;
  border-radius: 999px;
}

/* Most-played game: the red the badge used to wear unconditionally. */
.plays-badge.is-top {
  border-color: rgba(229, 52, 62, 0.4);
  color: #fff;
}

/* Abandoned section: small cards that keep their size instead of
   stretching to fill the row like the side / main cards do */
#games-abandoned {
  grid-template-columns: repeat(auto-fill, minmax(260px, 320px));
}

/* Abandoned cards read quieter: smaller title and slightly faded
   until hovered, so they clearly sit below the active projects.
   Opacity goes through a variable so the entrance reveal can animate
   to the right settled value (0.72) instead of a hard 1. */
.game-card.is-abandoned { --card-opacity: 0.72; opacity: var(--card-opacity); }
.game-card.is-abandoned:hover { opacity: 1; }
.game-card.is-abandoned .card-title { font-size: 1.1rem; }
.game-card.is-abandoned .card-desc { font-size: 0.84rem; }

/* Small "side project" tag in the corner of a side card's screenshot */
.card-flag {
  position: absolute;
  top: 10px;
  left: 10px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(0, 0, 0, 0.6);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-family: "JetBrains Mono", monospace;
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: 999px;
}

/* Fallback shown when the screenshot image is missing */
.shot-fallback {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
  background:
    radial-gradient(circle at 50% 40%, rgba(229, 52, 62, 0.12), transparent 60%),
    var(--panel-2);
}

/* Card body */
.card-body {
  padding: 20px 20px 22px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
}

/* Groups the tagline + title so the featured card can float them as a unit */
.card-head {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.card-title {
  font-size: 1.3rem;
  font-weight: 700;
}

.card-tagline {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.78rem;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--red);
}

.card-desc {
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--text-dim);
  flex: 1;
}

/* Answers the announcement pill in one quiet line, so a visitor who reads
   "public playtest" learns what that means for them today. The dot ties it
   back to the pill without repeating its wording, and takes the pill's colour
   with it (see .card-note.is-testing below). */
.card-note {
  grid-area: note;
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.76rem;
  letter-spacing: 0.02em;
  color: var(--text-faint);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.8);
}

.card-note::before {
  content: "";
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #28c840;
  box-shadow: 0 0 7px rgba(40, 200, 64, 0.8);
}

/* The dot ties the note to the pill above it, so it has to change with it. */
.card-note.is-testing::before {
  background: #8b6cff;
  box-shadow: 0 0 7px rgba(139, 108, 255, 0.8);
}

/* Play button: solid red at rest, not outlined. It used to be a transparent
   outline that only filled in on hover, which made the one thing on the page
   we want clicked the quietest element on the card. Black text rather than
   white: measured on this red, black is 4.61:1 and white only 4.29:1, so black
   is the one that clears WCAG AA, and it reads as a physical button rather
   than a link. The brighter hover red is 5.37:1, better still. */
.play-btn {
  align-self: flex-start;
  margin-top: 6px;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  background: var(--red);
  border: 1px solid var(--red);
  color: #0a0a0c;
  text-decoration: none;
  font-family: "JetBrains Mono", monospace;
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.01em;
  padding: 13px 28px;
  border-radius: 9px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
  transition: background 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease;
}

/* Under the pointer it brightens and glows rather than changing colour, so the
   button never looks like it switched state. :focus-visible gets the same
   treatment so keyboard users see what a mouse user sees. */
.play-btn:hover,
.play-btn:focus-visible {
  background: #f04651;
  color: #0a0a0c;
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.12),
    0 4px 16px rgba(0, 0, 0, 0.45),
    0 0 26px rgba(229, 52, 62, 0.75),
    0 0 54px rgba(229, 52, 62, 0.35);
  transform: translateY(-2px);
}

.play-btn:active { transform: translateY(0); }

/* ---------- Footer ---------- */
.site-footer {
  border-top: 1px solid var(--border);
  background: rgba(8, 8, 9, 0.85);
  padding: 28px 24px 36px;
  text-align: center;
}

/* About blurb — one quiet line on who makes/hosts the games */
.footer-about {
  max-width: 620px;
  margin: 0 auto 18px;
  font-size: 0.86rem;
  line-height: 1.6;
  color: var(--text-dim);
}

/* Author credit — the prominent line */
.footer-credit {
  font-size: 0.92rem;
  color: var(--text-dim);
  margin-bottom: 8px;
}

/* Contact / feedback line */
.footer-contact {
  font-size: 0.82rem;
  color: var(--text-faint);
  margin-bottom: 16px;
}

.footer-contact a {
  color: var(--red);
  text-decoration: none;
  border-bottom: 1px solid var(--red-soft);
}

.footer-contact a:hover { border-bottom-color: var(--red); }

.footer-credit strong {
  color: var(--text);
  font-weight: 600;
}

/* Copyright — quiet legal line in the terminal font */
.footer-brand {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--text-faint);
  margin-bottom: 16px;
}

/* Disclaimer folded away behind a small toggle */
.disclaimer-fold summary {
  cursor: pointer;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.72rem;
  color: var(--text-faint);
  list-style: none;
}

.disclaimer-fold summary::before { content: "> "; }
.disclaimer-fold summary::-webkit-details-marker { display: none; }
.disclaimer-fold summary:hover { color: var(--text-dim); }
.disclaimer-fold[open] summary { margin-bottom: 10px; }

.disclaimer {
  max-width: 860px;
  margin: 0 auto;
  font-size: 0.68rem;
  line-height: 1.6;
  color: var(--text-faint);
}

/* ---------- Tablet / narrow: taller stage, text stacks ---------- */
@media (max-width: 720px) {
  /* Last step of the side grid: 3 -> 2 (880px) -> 1 here. Two columns on a
     phone-width screen leaves each card too narrow for its screenshot to say
     anything. */
  .games-grid { grid-template-columns: 1fr; }
  .game-card.featured .card-shot {
    aspect-ratio: 4 / 3;         /* taller crop reads better on narrow screens */
    min-height: 0;               /* aspect-ratio drives height; avoids width blow-out */
    max-height: none;
  }
  /* Narrow screens drop the overlay entirely: the full blurb is too tall to
     float over a 4:3 crop without covering the screenshot and the badges, so
     the text sits below the shot in normal flow, like a side card. */
  .game-card.featured .card-body {
    position: static;
    background: none;
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "head"
      "desc"
      "note"
      "btn";
    justify-items: start;
    row-gap: 12px;
    padding: 18px 20px 22px;
  }
  .game-card.featured .card-desc,
  .game-card.featured .card-title,
  .game-card.featured .card-note { text-shadow: none; }
  .game-card.featured .play-btn { align-self: start; }
  .game-card.featured .card-title { font-size: 1.7rem; }
  .game-card.featured .card-desc { font-size: 0.9rem; }
}

/* ---------- Small screens ---------- */
@media (max-width: 480px) {
  .hero { padding: 56px 18px 44px; }
  .hero-sub { font-size: 0.78rem; padding: 9px 16px; }
  .games-grid { gap: 24px; grid-template-columns: 1fr; }
}
