:root {
  --public-black: #080908;
  --public-white: #ffffff;
  --public-muted: #6f716e;
  --public-line: #d7d8d4;
  --public-soft: #f2f2ef;
  --accent-red: #f04444;
  --accent-yellow: #f3cf3c;
  --accent-green: #36c66a;
  --accent-blue: #4a78f0;
  --accent-magenta: #df42ba;
  --accent-cyan: #24c7c4;
  --accent-orange: #f27a31;
  --accent-pink: #ff2f86;
  --accent-rose: #ff86bd;
  --accent-purple: #a259ff;
  --accent-violet: #6531d6;
  --accent-teal: #0f8f8c;
  --surface-sunset: #f2762c;
  --surface-frost: #eaf1f5;
  --ink-sunset: #2a0d2b;
  --public-pad: clamp(1.25rem, 4.2vw, 5rem);
  --public-max: 96rem;
  --public-header-height: clamp(4.75rem, 6vw, 5.5rem);
}

/* The game's own face, used for the SUPER-TYPE wordmark and nothing else.
   Self-hosted (Lobster, SIL OFL, latin subset) rather than pulled from Google
   Fonts — the rest of the site loads no third-party resource, and the hero is
   the first paint on the home page. */
@font-face {
  font-family: "Lobster";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/lobster-latin-400.woff2") format("woff2");
}

/* The hero is sized to fill the viewport, so at first paint — footer not yet
   parsed — the document does not overflow and no scrollbar exists. The footer
   arrives, the scrollbar appears, and every page shrinks by its width. Nothing
   left-aligned shows it, but the hero's centred release card jumps half a
   scrollbar sideways on every load. Reserving the gutter up front is the fix
   at the source; on overlay-scrollbar platforms it costs nothing. */
html {
  scrollbar-gutter: stable;
}

/* In-page jumps glide rather than cut — the trailer chip on the game page is
   the one that matters, and seeing the page travel is what tells you the
   trailer was already below you rather than somewhere else. Behind a motion
   query because for anyone who has asked for less of it, an unrequested scroll
   animation is exactly the kind they mean. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

.public-site {
  background: var(--public-white);
  color: var(--public-black);
}

.public-site main {
  width: 100%;
  max-width: none;
  margin: 0;
  padding: 0;
}

.public-shell {
  width: min(100%, var(--public-max));
  margin-inline: auto;
  padding-inline: var(--public-pad);
}

/* A compact studio mark and the navigation share one black surface. Colour is
   held back for interaction. */
.public-header {
  --header-height: var(--public-header-height);
  position: relative;
  z-index: 20;
  min-height: var(--header-height);
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: stretch;
  gap: clamp(2rem, 4vw, 5rem);
  padding-inline: var(--public-pad);
  background: var(--public-black);
  color: var(--public-white);
  isolation: isolate;
}

.public-brand {
  display: flex;
  align-items: center;
  color: var(--public-white);
  text-decoration: none;
}

/* The wordmark is the whole visible brand here; "GAMES" belongs in larger
   identity lockups, not in the utility header. The animated copy sits directly
   over the white base so colour can crossfade without changing the mark. */
.public-brand__marks {
  position: relative;
  display: block;
  width: clamp(8.5rem, 11vw, 10.75rem);
}

.public-brand .public-brand__mark {
  width: 100%;
  height: auto;
  background: transparent;
}

.public-brand .public-brand__mark--color {
  --mark-1: var(--accent-red);
  --mark-2: var(--accent-yellow);
  --mark-3: var(--accent-cyan);
  --mark-4: var(--accent-pink);
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity 500ms ease;
}

.public-brand:is(:hover, :focus-visible) .public-brand__mark--color {
  opacity: 1;
  transition-duration: 900ms;
}

.public-brand__mark--color .xzzx-letter {
  animation: xzzx-mark-cycle 8s linear infinite;
}

.public-brand__mark--color .xzzx-letter--2 {
  animation-delay: -2s;
}

.public-brand__mark--color .xzzx-letter--3 {
  animation-delay: -4s;
}

.public-brand__mark--color .xzzx-letter--4 {
  animation-delay: -6s;
}

.public-brand:focus-visible {
  outline: 2px solid var(--accent-red);
  outline-offset: 0.35rem;
}

/* ----------------------------------------------------------------------------
   Mark system

   Every XZZX mark renders four letter groups (.xzzx-letter--1 through --4).
   Colour comes from --mark-1 through --mark-4, which default to currentColor,
   so a mark is monochrome until a palette says otherwise. Palettes live on
   [data-palette] and can sit on the mark itself or on any ancestor.
---------------------------------------------------------------------------- */

.xzzx-mark {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  fill: none;
}

.xzzx-mark .xzzx-letter {
  fill: none;
  stroke-width: var(--mark-weight, 10);
  stroke-linecap: var(--mark-cap, butt);
  stroke-linejoin: var(--mark-join, miter);
  transition: stroke 420ms cubic-bezier(0.76, 0, 0.24, 1);
}

/* Doubled class keeps these ahead of the per-context rules in site.css. */
.xzzx-mark .xzzx-letter.xzzx-letter--1 {
  stroke: var(--mark-1, currentColor);
}

.xzzx-mark .xzzx-letter.xzzx-letter--2 {
  stroke: var(--mark-2, currentColor);
}

.xzzx-mark .xzzx-letter.xzzx-letter--3 {
  stroke: var(--mark-3, currentColor);
}

.xzzx-mark .xzzx-letter.xzzx-letter--4 {
  stroke: var(--mark-4, currentColor);
}

/* Per-construction stroke defaults; each family has its own viewBox scale. */
.xzzx-mark--wordmark,
.xzzx-mark--radial,
.xzzx-mark--radial-v2 {
  --mark-weight: 10;
  --mark-cap: butt;
  --mark-join: miter;
}

.xzzx-mark--grid {
  --mark-weight: 10;
  --mark-cap: square;
  --mark-join: miter;
}

.xzzx-mark--grid-flush {
  --mark-weight: 8;
  --mark-cap: butt;
  --mark-join: miter;
}

.xzzx-mark--helix,
.xzzx-mark--helix-cell {
  --mark-weight: 10;
  --mark-cap: butt;
  --mark-join: round;
}

/* Capsule carries --mark-weight and --mark-cap inline, from the saved preset. */
.xzzx-mark--capsule {
  --mark-join: round;
}

/* ---- Palettes -------------------------------------------------------------
   --mark-surface and --mark-ink are the ground and type colours that go with
   each set, for the placements that paint a background.
--------------------------------------------------------------------------- */

[data-palette="ink"] {
  --mark-surface: var(--public-white);
  --mark-ink: var(--public-black);
  --mark-1: var(--public-black);
  --mark-2: var(--public-black);
  --mark-3: var(--public-black);
  --mark-4: var(--public-black);
}

[data-palette="paper"] {
  --mark-surface: var(--public-black);
  --mark-ink: var(--public-white);
  --mark-1: var(--public-white);
  --mark-2: var(--public-white);
  --mark-3: var(--public-white);
  --mark-4: var(--public-white);
}

[data-palette="signal"] {
  --mark-surface: var(--public-black);
  --mark-ink: var(--public-white);
  --mark-1: var(--accent-red);
  --mark-2: var(--accent-yellow);
  --mark-3: var(--accent-green);
  --mark-4: var(--accent-blue);
}

[data-palette="sunset"] {
  --mark-surface: var(--surface-sunset);
  --mark-ink: var(--ink-sunset);
  --mark-1: var(--accent-pink);
  --mark-2: var(--accent-rose);
  --mark-3: var(--accent-purple);
  --mark-4: var(--accent-violet);
}

[data-palette="frost"] {
  --mark-surface: var(--surface-frost);
  --mark-ink: var(--public-black);
  --mark-1: var(--accent-teal);
  --mark-2: var(--accent-cyan);
  --mark-3: var(--accent-blue);
  --mark-4: var(--accent-violet);
}

/* ---- Motion ---------------------------------------------------------------
   All three are opt-in per mark.

   `cycle` rotates the palette across the letters on a stagger. It steps rather
   than interpolates, so every letter is always showing an actual palette
   colour instead of a muddy value between two of them.

   `blend` is the interpolating version, for when the drift is the point.

   `trace` draws the mark on once. It relies on pathLength="1", so it works on
   any construction regardless of real path length.
--------------------------------------------------------------------------- */

.xzzx-mark--cycle .xzzx-letter,
.xzzx-mark--blend .xzzx-letter {
  animation: xzzx-mark-cycle 7s linear infinite;
}

.xzzx-mark--cycle .xzzx-letter {
  animation-timing-function: step-end;
  transition: none;
}

.xzzx-mark--cycle .xzzx-letter--2,
.xzzx-mark--blend .xzzx-letter--2 {
  animation-delay: -1.75s;
}

.xzzx-mark--cycle .xzzx-letter--3,
.xzzx-mark--blend .xzzx-letter--3 {
  animation-delay: -3.5s;
}

.xzzx-mark--cycle .xzzx-letter--4,
.xzzx-mark--blend .xzzx-letter--4 {
  animation-delay: -5.25s;
}

@keyframes xzzx-mark-cycle {
  0% {
    stroke: var(--mark-1);
  }

  25% {
    stroke: var(--mark-2);
  }

  50% {
    stroke: var(--mark-3);
  }

  75% {
    stroke: var(--mark-4);
  }

  100% {
    stroke: var(--mark-1);
  }
}

.xzzx-mark--trace .xzzx-letter {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: xzzx-mark-trace 1.6s cubic-bezier(0.65, 0, 0.35, 1) forwards;
}

.xzzx-mark--trace .xzzx-letter--2 {
  animation-delay: 0.12s;
}

.xzzx-mark--trace .xzzx-letter--3 {
  animation-delay: 0.24s;
}

.xzzx-mark--trace .xzzx-letter--4 {
  animation-delay: 0.36s;
}

@keyframes xzzx-mark-trace {
  to {
    stroke-dashoffset: 0;
  }
}

.public-footer__mark {
  display: block;
  width: 100%;
  height: auto;
  fill: none;
}

.public-nav {
  display: flex;
  align-items: stretch;
  justify-content: flex-end;
  gap: clamp(0.1rem, 0.45vw, 0.45rem);
}

.public-nav a,
.public-menu a,
.public-footer a {
  font-size: 0.69rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-decoration: none;
  text-transform: uppercase;
}

.public-nav a {
  position: relative;
  z-index: 0;
  display: flex;
  align-items: center;
  overflow: hidden;
  padding-inline: clamp(0.75rem, 1.35vw, 1.35rem);
  color: #a8aaa6;
  font-size: clamp(0.68rem, 0.72vw, 0.78rem);
  transition: color 280ms cubic-bezier(0.76, 0, 0.24, 1);
}

.public-nav a::after {
  position: absolute;
  z-index: -1;
  right: 0.45rem;
  bottom: 0;
  left: 0.45rem;
  height: 0.4rem;
  background: var(--nav-accent);
  content: "";
  transform: scaleY(0);
  transform-origin: center bottom;
  transition: transform 280ms cubic-bezier(0.76, 0, 0.24, 1);
}

.public-nav a span {
  transition: transform 280ms cubic-bezier(0.76, 0, 0.24, 1);
}

.public-nav a:hover,
.public-nav a:focus-visible,
.public-nav a[aria-current="page"] {
  color: var(--public-white);
}

.public-nav a:hover::after,
.public-nav a:focus-visible::after,
.public-nav a[aria-current="page"]::after {
  transform: scaleY(1);
}

.public-nav a:hover span,
.public-nav a:focus-visible span,
.public-nav a[aria-current="page"] span {
  transform: translateY(-2px);
}

.public-nav a:focus-visible {
  outline: 2px solid var(--nav-accent);
  outline-offset: -2px;
}

.public-nav a:nth-child(1),
.public-menu a:nth-child(1) {
  --nav-accent: var(--accent-red);
}

.public-nav a:nth-child(2),
.public-menu a:nth-child(2) {
  --nav-accent: var(--accent-yellow);
}

.public-nav a:nth-child(3),
.public-menu a:nth-child(3) {
  --nav-accent: var(--accent-cyan);
}

.public-nav a:nth-child(4),
.public-menu a:nth-child(4) {
  --nav-accent: var(--accent-pink);
}

.public-nav a:nth-child(5),
.public-menu a:nth-child(5) {
  --nav-accent: var(--accent-green);
}

.public-nav a:nth-child(6),
.public-menu a:nth-child(6) {
  --nav-accent: var(--accent-purple);
}

/* ---- Held-back destinations ------------------------------------------------
   An item marked disabled in navigation.json keeps its slot so the navigation
   holds its shape, but renders as inert text. It mirrors the link box exactly
   — same padding, same metrics — and only steps back in colour, so the bar
   reads as one row rather than as links with gaps between them.
--------------------------------------------------------------------------- */

.public-nav .nav-disabled {
  display: flex;
  align-items: center;
  padding-inline: clamp(0.75rem, 1.35vw, 1.35rem);
  color: #55574f;
  font-size: clamp(0.68rem, 0.72vw, 0.78rem);
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  cursor: default;
}

.public-menu .nav-disabled {
  padding-block: 0.85rem;
  padding-left: 1rem;
  border-bottom: 1px solid #343633;
  color: #55574f;
  font-size: 0.69rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  cursor: default;
}

.public-footer__nav .nav-disabled {
  color: #b0b2ad;
  font-size: 0.69rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  cursor: default;
}

.public-menu {
  display: none;
}

.game-index-card__content > span,
.section-page__description,
.game-intro__spec dt,
.public-footer {
  font-family: "Courier New", Courier, monospace;
}

.game-hero {
  --hero-extension: 6.75rem;
  position: relative;
  height: min(
    61.75rem,
    calc(100svh - var(--public-header-height) + var(--hero-extension))
  );
  min-height: calc(36rem + var(--hero-extension));
  overflow: hidden;
  background: var(--public-black);
  isolation: isolate;
}

.game-hero__collage {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: repeat(8, 1fr);
  background: var(--public-black);
}

/* Each tile is a window onto one game screenshot, blown up with nearest
   neighbour so the source pixels stay square. Grid placement, crop and stagger
   are written inline — at build time for the served cut, then again by
   xzzx-hero.js when it re-cuts the collage on load.

   The hero rests in black and white. Grayscale sits on each tile rather than
   the collage so the canvas layer on top is filtered along with its tile — the
   whole collage still turns over as one, on one timing. */
.hero-media {
  position: relative;
  overflow: hidden;
  background-color: #000;
  background-repeat: no-repeat;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
  filter: grayscale(1) contrast(1.08);
  transition: filter 620ms cubic-bezier(0.33, 1, 0.68, 1);
}

/* The live palette-cycling layer, sized in source pixels and scaled up by the
   same integer factor as the background it covers. */
.hero-media__cycle {
  position: absolute;
  top: 0;
  left: 0;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}

/* The wash answers to the whole lockup rectangle, which carries
   game-hero__wake — anywhere along the bottom of the frame wakes the hero,
   including the gap between the wordmark and the date card. Deliberately wider
   than the click targets. Keyboard focus on either link gets the same. */
@media (hover: hover) {
  .game-hero:has(.game-hero__wake:hover) .hero-media,
  .game-hero:has(.game-hero__wake a:focus-visible) .hero-media {
    filter: grayscale(0) contrast(1);
  }
}

/* The touch path. There is no hover to key off, so xzzx-hero.js sets
   data-hero-awake when the hero scrolls into view and the wash follows it —
   one attribute, so the colour and the palette cycle cannot disagree. */
.game-hero[data-hero-awake] .hero-media {
  filter: grayscale(0) contrast(1);
}

/* The title reads by difference against the collage, so its legibility is
   really a question of how even the collage is underneath it. This settles the
   lower band without touching the blend, the colour or the type itself. Raise
   --hero-scrim toward 1 for more contrast, drop it to 0 to remove it.

   It hangs off the collage rather than the hero so that it paints inside the
   collage's own layer. The lockup below can then clear it on tree order alone
   and needs no z-index — which it must not have, or it would become the
   backdrop its blended title has to invert against. */
.game-hero__collage::after {
  --hero-scrim: 0.55;
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: 52%;
  content: "";
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, var(--hero-scrim)),
    rgba(0, 0, 0, calc(var(--hero-scrim) * 0.5)) 42%,
    rgba(0, 0, 0, 0)
  );
  pointer-events: none;
}

/* Wordmark and release card sit in one block at the foot of the frame — the
   title left where it has always been, the card centred under it.

   Layout only: no z-index and no blend of its own. Either would make this the
   stacking context its children blend inside, and difference against an empty
   backdrop is just white text. */
.game-hero__lockup {
  position: absolute;
  right: var(--public-pad);
  bottom: clamp(1.5rem, 4vw, 4rem);
  left: var(--public-pad);
  color: #fff;
}

/* The title is the game's wordmark, so it is set in the game's face rather
   than the site sans. Script type wants different metrics than the caps
   lockup it replaces: the tight tracking would run the letterforms together,
   and the descenders in "Super-Type" need leading that 0.84 does not leave. */
.game-hero h1 {
  margin: 0;
  font-family: "Lobster", "Brush Script MT", cursive;
  font-size: clamp(3.5rem, 10.5vw, 10.5rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.01em;
}

/* The date rides a small white card centred under the wordmark, black on
   white — the one solid, fixed thing in a hero that re-cuts itself on every
   load, and the only element here that carries its own contrast rather than
   borrowing it from the scrim. Impact ships with Windows and macOS both, so
   the face costs no download; the stack below it narrows on anything else
   rather than falling back to a normal grotesque. */
.game-hero__status {
  display: block;
  width: fit-content;
  margin: clamp(0.9rem, 1.8vw, 1.5rem) auto 0;
  padding: 0.5em 0.95em;
  background: var(--public-white);
  color: var(--public-black);
  text-decoration: none;
  font-family: Impact, "Haettenschweiler", "Arial Narrow", "Arial Bold",
    sans-serif;
  font-size: clamp(0.8rem, 1.2vw, 1.05rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

/* The link is the text and nothing more — the heading spans the full width of
   the lockup, and a click target that wide would swallow the hero. It is also
   what the two cuts of the word are stacked inside, so it shrinks to them. */
.game-hero h1 a {
  position: relative;
  display: inline-block;
  color: inherit;
  text-decoration: none;
}

/* The wordmark rests as flat white and inverts against the collage on hover,
   in step with the wash that takes the tiles from grayscale into colour.
   mix-blend-mode is a discrete property and cannot be transitioned, so the
   change is carried by two identical cuts of the word crossfading: the plain
   one out, the inverting one in. The midpoint of that crossfade is a flat
   mid-grey whatever the art behind it — the word dips through grey on its way
   into the picture, which is the effect, not a fault of it. */
.game-hero__word {
  display: block;
  transition: opacity 620ms cubic-bezier(0.33, 1, 0.68, 1);
}

/* Laid over the plain cut, not beside it: same box, same metrics, so the two
   register exactly. No z-index anywhere up the tree, or this would blend
   against its own group instead of the collage. */
.game-hero__word--invert {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  opacity: 0;
  mix-blend-mode: difference;
}

/* The wordmark inverts on the same trigger as the wash, so waking the hero from
   the date card carries the word with it rather than leaving the two disagreeing
   about whether the hero is awake. */
.game-hero:has(.game-hero__wake:hover) .game-hero__word--invert,
.game-hero:has(.game-hero__wake a:focus-visible) .game-hero__word--invert {
  opacity: 1;
}

.game-hero:has(.game-hero__wake:hover) .game-hero__word:not(.game-hero__word--invert),
.game-hero:has(.game-hero__wake a:focus-visible) .game-hero__word:not(.game-hero__word--invert) {
  opacity: 0;
}

.game-hero h1 a:focus-visible {
  outline: 0.04em solid currentColor;
  outline-offset: 0.06em;
}

/* The card carries its own contrast, so its ring sits outside the white rather
   than on it — currentColor here is the black of the text. */
.game-hero__status:focus-visible {
  outline: 2px solid var(--public-white);
  outline-offset: 2px;
}

@keyframes block-cursor-blink {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0;
  }
}

/* Match the home hero's balance at 80% browser zoom while the surrounding site
   stays at its native scale. The extra vertical field reveals more of the
   collage at the same integer pixel treatment rather than enlarging the art. */
@media (min-width: 721px) {
  .game-hero {
    height: min(
      49rem,
      calc(100svh - var(--public-header-height))
    );
    min-height: calc(28.8rem + var(--hero-extension));
  }

  .game-hero__lockup {
    bottom: clamp(1.2rem, 4vw, 3.2rem);
  }

  .game-hero h1 {
    font-size: clamp(2.8rem, 10.5vw, 8.4rem);
  }
}

.studio-position {
  position: relative;
  isolation: isolate;
  background: var(--public-white);
  color: var(--public-black);
}

/* One small signal at the handoff from the game to the studio introduction. */
.studio-position::before {
  position: absolute;
  z-index: 1;
  right: 0;
  left: 0;
  height: 0.3rem;
  background: linear-gradient(
    to right,
    var(--accent-red) 0 25%,
    var(--accent-yellow) 25% 50%,
    var(--accent-cyan) 50% 75%,
    var(--accent-pink) 75% 100%
  );
  content: "";
  pointer-events: none;
}

.studio-position::before {
  top: 0;
}

.studio-position__inner {
  display: grid;
  grid-template-columns: minmax(8rem, 0.34fr) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 7rem);
  padding-top: clamp(3.25rem, 5vw, 5rem);
  padding-bottom: clamp(3.5rem, 6vw, 6rem);
}

.studio-position__content {
  display: contents;
  min-width: 0;
}

/* This is a heading, not an eyebrow: keep it readable and keep all type in
   this introduction black. It runs a step above the section heads because it
   introduces the studio, not a feed. */
.studio-position .studio-position__subheading {
  margin: 0;
  color: var(--public-black);
  font-size: clamp(2rem, 3.6vw, 3.4rem);
  font-weight: 500;
  line-height: 0.95;
  letter-spacing: -0.045em;
}

/* The one colour concession in the introduction — the same red as the caption
   marker upstream, closing the word like a full stop the copy doesn't carry. */
.studio-position .studio-position__subheading::after {
  color: var(--accent-red);
  content: ".";
}

.studio-position__body {
  width: min(100%, 61rem);
}

.studio-position__body p {
  margin: 0;
  color: var(--public-black);
  font-size: clamp(1.35rem, 2.25vw, 2.1rem);
  line-height: 1.3;
  letter-spacing: -0.03em;
  text-wrap: pretty;
}

.studio-position__body p + p {
  max-width: 52rem;
  margin-top: clamp(1.5rem, 2.5vw, 2.5rem);
  color: var(--public-black);
  font-size: clamp(1rem, 1.35vw, 1.2rem);
  line-height: 1.55;
  letter-spacing: -0.015em;
}

/* Only while the gutter column exists: the same hairline the cards use stands
   the introduction off from "Welcome". */
@media (min-width: 721px) {
  .studio-position__body {
    border-left: 1px solid var(--public-line);
    padding-left: clamp(2rem, 3.5vw, 3.5rem);
  }
}

/* The handoff strip draws itself in as the section arrives. Browsers without
   scroll-driven animations, and readers who opt out of motion, keep the
   static strip. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .studio-position {
      view-timeline: --studio-position-enter block;
    }

    .studio-position::before {
      animation: studio-strip-wipe linear both;
      animation-timeline: --studio-position-enter;
      animation-range: entry 0% cover 35%;
    }
  }
}

@keyframes studio-strip-wipe {
  from {
    clip-path: inset(0 100% 0 0);
  }

  to {
    clip-path: inset(0 0 0 0);
  }
}

.home-updates,
.home-feature {
  padding-block: clamp(3.75rem, 6vw, 6rem);
  border-top: 1px solid var(--public-line);
}

.public-section-head {
  display: flex;
  justify-content: space-between;
  align-items: start;
  gap: 2rem;
  margin-bottom: clamp(1.75rem, 3vw, 3rem);
}

.public-section-head h2 {
  margin: 0;
  font-size: clamp(1.75rem, 3vw, 2.75rem);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.045em;
}

.line-link {
  padding-bottom: 0.35rem;
  border-bottom: 1px solid;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-decoration: none;
  text-transform: uppercase;
}

.updates-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(17rem, 0.75fr);
  border-top: 1px solid var(--public-black);
}

.latest-update {
  min-height: 26rem;
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(17rem, 0.75fr);
  border-top: 1px solid var(--public-black);
  border-bottom: 1px solid var(--public-line);
  color: inherit;
  text-decoration: none;
}

.latest-update__media {
  min-height: 26rem;
}

.latest-update__content {
  display: flex;
  flex-direction: column;
  padding: clamp(1.5rem, 3vw, 3rem);
  border-left: 1px solid var(--public-line);
}

.latest-update__content h3 {
  max-width: 16ch;
  margin: auto 0 1.5rem;
  font-size: clamp(2rem, 3.8vw, 3.8rem);
  font-weight: 500;
  line-height: 0.94;
  letter-spacing: -0.06em;
}

.update-card {
  min-height: 13.5rem;
  position: relative;
  display: flex;
  flex-direction: column;
  padding: clamp(1.25rem, 2.8vw, 2.5rem);
  border-right: 1px solid var(--public-line);
  border-bottom: 1px solid var(--public-line);
  color: inherit;
  text-decoration: none;
}

.update-card--lead {
  min-height: 34rem;
  grid-row: span 2;
  padding: 0;
}

.update-card__media {
  min-height: 21rem;
  margin-bottom: 0;
}

.update-card--lead .update-card__meta,
.update-card--lead h3,
.update-card--lead .update-card__arrow {
  margin-right: clamp(1.25rem, 2.8vw, 2.5rem);
  margin-left: clamp(1.25rem, 2.8vw, 2.5rem);
}

.update-card--lead .update-card__meta {
  margin-top: 1.5rem;
}

.update-card__meta,
.feature-card__meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1.5rem;
  font-size: clamp(1.05rem, 1.6vw, 1.45rem);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.025em;
}

/* Lead term carries the ink; the trailing one (date / series) stays secondary. */
.update-card__meta > :first-child,
.feature-card__meta > :first-child {
  color: var(--public-black);
}

.update-card__meta > :last-child,
.feature-card__meta > :last-child {
  color: var(--public-muted);
}

.update-card h3 {
  max-width: 18ch;
  margin: 1.25rem 0 2rem;
  font-size: clamp(1.45rem, 2.8vw, 2.65rem);
  font-weight: 500;
  line-height: 1.04;
  letter-spacing: -0.045em;
}

.update-card__arrow,
.feature-card__arrow {
  margin-top: auto;
  align-self: flex-end;
  font-size: 1.4rem;
}

.media-placeholder {
  position: relative;
  overflow: hidden;
  background:
    linear-gradient(135deg, transparent 0 48%, rgba(255, 255, 255, 0.75) 48% 49%, transparent 49%),
    repeating-linear-gradient(90deg, #121212 0 23px, #1e1e1e 23px 24px);
  color: rgba(255, 255, 255, 0.62);
}

.media-placeholder > span {
  position: absolute;
  top: 1rem;
  left: 1rem;
  font-family: "Courier New", Courier, monospace;
  font-size: 0.65rem;
  letter-spacing: 0.09em;
}

.feature-card {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(19rem, 0.75fr);
  min-height: 26rem;
  border: 1px solid var(--public-black);
  color: inherit;
  text-decoration: none;
}

.feature-card__content {
  display: flex;
  flex-direction: column;
  padding: clamp(1.5rem, 3vw, 3rem);
}

.feature-card__content h3,
.feature-card__content h2 {
  max-width: 13ch;
  margin: auto 0 1.5rem;
  font-size: clamp(2rem, 4vw, 4rem);
  font-weight: 500;
  line-height: 0.92;
  letter-spacing: -0.06em;
}

.home-newsletter {
  background: var(--public-black);
  color: var(--public-white);
}

.home-newsletter__inner {
  min-height: 33rem;
  display: grid;
  grid-template-columns: minmax(9rem, 0.35fr) minmax(0, 1fr);
  gap: clamp(2rem, 8vw, 10rem);
  align-items: center;
  padding-block: clamp(5rem, 10vw, 10rem);
}

.home-newsletter h2 {
  max-width: 15ch;
  margin: 0 0 3rem;
  font-size: clamp(2.75rem, 6vw, 6.5rem);
  font-weight: 500;
  line-height: 0.94;
  letter-spacing: -0.06em;
}

.newsletter-form {
  max-width: 46rem;
  display: grid;
  grid-template-columns: 1fr auto;
  border-bottom: 1px solid rgba(255, 255, 255, 0.7);
}

.newsletter-form input,
.newsletter-form button {
  min-height: 4rem;
  border: 0;
  border-radius: 0;
  font: inherit;
}

.newsletter-form input {
  min-width: 0;
  padding: 0;
  background: transparent;
  color: white;
  outline: none;
}

.newsletter-form input::placeholder {
  color: rgba(255, 255, 255, 0.45);
}

.newsletter-form button {
  padding: 0 1.5rem;
  background: white;
  color: black;
  cursor: pointer;
  font-size: 0.69rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.public-footer {
  min-height: 17rem;
  display: grid;
  grid-template-columns: minmax(11rem, 0.8fr) 1fr minmax(11rem, 0.8fr);
  gap: clamp(2rem, 5vw, 6rem);
  align-items: start;
  padding: clamp(3rem, 6vw, 6rem) var(--public-pad);
  border-top: 1px solid var(--public-line);
  background: var(--public-white);
  color: var(--public-muted);
  font-size: 0.65rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.public-footer__brand {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.public-footer__mark {
  width: 7rem;
  color: var(--public-black);
}

.public-footer__nav {
  display: grid;
  grid-template-columns: repeat(2, minmax(7rem, 1fr));
  gap: 1rem 2rem;
}

.public-footer__contact {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: 1rem;
}

/* The address is the one live link in the footer, so it takes the ink while
   the surrounding labels stay muted. */
.public-footer__contact a,
.contact-placeholder a {
  color: var(--public-black);
  text-decoration: none;
  border-bottom: 1px solid var(--public-line);
  transition: border-color 220ms ease;
  /* An address is one unbreakable token, and the contact block sets it large.
     Without this it defines the page's minimum width on a narrow viewport. */
  overflow-wrap: anywhere;
}

.public-footer__contact a:is(:hover, :focus-visible),
.contact-placeholder a:is(:hover, :focus-visible) {
  border-bottom-color: var(--accent-red);
}

.section-page__header {
  /* The masthead sizes to its content: title, introduction, mark, no reserved
     air. Compactness comes from here and from the mark widths below. */
  display: grid;
  grid-template-columns: minmax(8rem, 0.25fr) minmax(0, 1fr);
  grid-template-rows: auto auto;
  gap: 1.1rem 5rem;
  padding-block: clamp(1.75rem, 2.75vw, 2.75rem);
}

.section-page__header h1 {
  margin: 0;
  font-size: clamp(2rem, 4.5vw, 4.25rem);
  font-weight: 500;
  line-height: 0.85;
  letter-spacing: -0.06em;
}

/* The page introduction sits directly under the title, in the site's mono
   metadata voice — real copy now, held muted so the title keeps the ink. */
.section-page__description {
  grid-column: 2;
  max-width: 42rem;
  margin: 0;
  color: var(--public-muted);
  font-size: 0.8rem;
  line-height: 1.7;
  letter-spacing: 0.05em;
}

.section-page__content {
  padding-bottom: clamp(6rem, 12vw, 12rem);
}

/* ----------------------------------------------------------------------------
   Section masthead marks

   Each section gets its own construction, palette and placement, set in
   _data/pages.json. Placements deliberately land the mark somewhere different
   on every page:

     lead   left of the heading, level with it            (Games)
     trail  right of the heading, level with it           (Design & Play)
     flank  runs bracketing the heading, off-page both ways  (Updates)
     below  centred under a centred heading               (Studio)
     trio   three marks in a row above the heading        (Contact)
---------------------------------------------------------------------------- */

.section-mark {
  display: block;
  /* Grid items default to min-width:auto, and an inline SVG contributes its
     viewBox width as intrinsic size. Without this the mark can widen the
     column instead of scaling down into it. */
  min-width: 0;
  width: 100%;
}

.section-mark--lead {
  width: min(100%, 5rem);
}

/* Below 721px the trail mark sits in normal flow above the title; uncapped it
   would fill the whole viewport width. */
.section-mark--trail {
  width: min(100%, 15rem);
}

.section-mark--below {
  width: min(100%, 13rem);
}

/* Studio centres the whole block — heading, mark and description together. */
.section-page--below .section-page__header {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto auto;
  justify-items: center;
  text-align: center;
}

.section-page--below h1 {
  grid-area: 1 / 1;
}

.section-page--below .section-mark--below {
  grid-area: 2 / 1;
}

.section-page--below .section-page__description {
  grid-area: 3 / 1;
}

/* Contact centres the same way, with the row of three sitting above. */
.section-page--trio .section-page__header {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto auto;
  justify-items: center;
  text-align: center;
}

.section-page--trio .section-mark--trio {
  grid-area: 1 / 1;
  justify-self: stretch;
}

.section-page--trio h1 {
  grid-area: 2 / 1;
}

.section-page--trio .section-page__description {
  grid-area: 3 / 1;
}

/* Updates: two runs of small cells converging on the heading. The tiles butt
   with no gap, and each run is longer than its column so it always leaves the
   page. In both runs :first-child is the cell nearest the title. */
.section-mark--flank {
  display: flex;
  align-items: center;
  gap: 0;
  color: var(--public-black);
}

.section-mark--flank > * {
  flex: 0 0 auto;
  width: 2.25rem;
}

/* row-reverse packs the left run rightward from the heading, which both keeps
   :first-child adjacent to the title and sends the overflow off-page left. */
.section-mark--flank-left {
  flex-direction: row-reverse;
  justify-content: flex-start;
}

.section-mark--flank-left > :first-child {
  color: var(--accent-blue);
}

.section-mark--flank-right > :first-child {
  color: var(--accent-red);
}

/* Contact: the three spread across the full masthead width, held a short inset
   off both margins rather than sized to fill. */
.section-mark--trio {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  width: 100%;
  padding-inline: clamp(1.5rem, 4vw, 4rem);
}

.section-mark--trio > * {
  flex: 0 1 clamp(6rem, 13%, 9.5rem);
  min-width: 0;
}

/* Left to right: full ink, then two steps back into the page. Each mark's
   letters fall back to currentColor, so one colour drives all four. */
.section-mark--trio > :nth-child(1) {
  color: var(--public-black);
}

.section-mark--trio > :nth-child(2) {
  color: #a2a49f;
}

.section-mark--trio > :nth-child(3) {
  color: #d7d9d4;
}

/* Updates runs its mark past the page edge; clip so nothing scrolls sideways. */
.section-page--flank .section-page__masthead {
  overflow: hidden;
}

/* Opt-in painted masthead, driven by the mark config rather than a placement. */
.section-page--surface .section-page__masthead {
  background: var(--mark-surface);
  color: var(--mark-ink);
}

.section-page--surface .section-page__description {
  color: var(--mark-ink);
  opacity: 0.58;
}

/* Explicit grid placement only applies while the header is still two columns;
   below 720px it collapses to one and the marks fall back into normal flow. */
@media (min-width: 721px) {
  /* These blocks kept their left gutter when the eyebrow labels came out, so
     the content column stays where it was. Without an explicit column the sole
     remaining child would auto-place into the empty gutter. */
  .home-newsletter__inner > div {
    grid-column: 2;
  }

  /* Games: the mark leads the title from the shell's left edge — an oversized
     index glyph on the title's line, not a resident of a phantom gutter. Title
     and introduction share one left axis beside it. The mark column is a fixed
     track: an auto track would take the SVG's 300px default intrinsic width. */
  .section-page--lead .section-page__header {
    grid-template-columns: 5rem minmax(0, 1fr);
    column-gap: clamp(1.25rem, 2vw, 2rem);
    align-items: center;
  }

  .section-page--lead .section-mark--lead {
    grid-area: 1 / 1;
  }

  .section-page--lead h1 {
    grid-area: 1 / 2;
  }

  .section-page--lead .section-page__description {
    grid-area: 2 / 2;
  }

  /* Design & Play: title and introduction hold the shell's left edge — level
     with the content below — while the mark anchors the right edge of the same
     line, the way the home section heads pair a heading with a trailing link. */
  .section-page--trail .section-page__header {
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: clamp(1.5rem, 2.5vw, 2.5rem);
    align-items: center;
  }

  .section-page--trail h1 {
    grid-area: 1 / 1;
  }

  .section-page--trail .section-page__description {
    grid-area: 2 / 1;
  }

  .section-page--trail .section-mark--trail {
    grid-area: 1 / 2;
    justify-self: end;
    width: min(100%, 11rem);
  }

  /* Updates: the heading claims only its own width so the two runs can
     bracket it on the same line. */
  .section-page--flank .section-page__header {
    grid-template-columns: minmax(8rem, 0.25fr) auto minmax(0, 1fr);
    column-gap: clamp(1.5rem, 2.5vw, 2.5rem);
  }

  .section-page--flank h1 {
    grid-area: 1 / 2;
  }

  .section-page--flank .section-page__description {
    grid-area: 2 / 2;
  }

  /* The runs bracket the heading: gutter column left, open column right. */
  .section-page--flank .section-mark--flank-left {
    grid-area: 1 / 1;
    align-self: center;
  }

  .section-page--flank .section-mark--flank-right {
    grid-area: 1 / 3;
    align-self: center;
  }

}

.index-list {
  border-top: 1px solid var(--public-black);
}

.index-row {
  min-height: 8rem;
  display: grid;
  grid-template-columns: 10rem 1fr 10rem;
  gap: 2rem;
  align-items: center;
  border-bottom: 1px solid var(--public-line);
  color: inherit;
  text-decoration: none;
}

.index-row h2 {
  margin: 0;
  font-size: clamp(1.5rem, 3vw, 3rem);
  font-weight: 500;
  letter-spacing: -0.04em;
}

/* Planned entries: same rhythm as live index rows, muted so they read as
   slots rather than links. */
.index-list--upcoming {
  margin-top: clamp(2.5rem, 5vw, 4.5rem);
}

.index-row--upcoming {
  color: var(--public-muted);
}

.game-index-list {
  display: grid;
  gap: clamp(1.5rem, 3vw, 3rem);
}

/* The card carries the game page's own intro layout — frame left, identity
   right — inside a border, so a list entry reads the way that title's page
   opens. Padding sits on the card rather than the columns; the unannounced
   slot below pads again in its own content block. */
.game-index-card {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: start;
  gap: clamp(2rem, 4vw, 4.5rem);
  padding: clamp(1.5rem, 3vw, 3rem);
  border: 1px solid var(--public-black);
  color: inherit;
  text-decoration: none;
}

/* Placeholder slot for the next title: same footprint as a real card, dashed
   and muted so it reads as a slot rather than an entry. It keeps the fixed
   height a real card no longer needs, having nothing of its own to fill it. */
.game-index-card--unannounced {
  min-height: 35rem;
  padding: 0;
  border-style: dashed;
  border-color: var(--public-line);
  color: var(--public-muted);
}

.game-index-card--unannounced .game-index-card__media {
  opacity: 0.3;
}

.game-index-card__content {
  display: flex;
  flex-direction: column;
  padding: clamp(1.5rem, 4vw, 4rem);
}

.game-index-card__content h2 {
  margin: auto 0;
  font-size: clamp(3rem, 6vw, 6rem);
  font-weight: 500;
  line-height: 0.9;
  letter-spacing: -0.06em;
}

/* ----------------------------------------------------------------------------
   Game page

   Wordmark, then the game running down the left against the copy on the right,
   then the trailer under both. The art keeps the nearest-neighbour treatment the
   home collage gives it, so a 256x240 frame — still, animated, or cycling its
   palette under a canvas — scales up with its source pixels square.
---------------------------------------------------------------------------- */

/* Full-bleed, flush under the black header, with the game's own face set black
   over its skyline.

   The source frame is 256x240 and the band shows the bottom 74 rows of it, so
   the ratio is 256/74 and `cover` against a band this wide scales by width and
   crops off the top — `bottom` anchoring is what guarantees the city is in
   shot at every size. No min- or max-height: the clamps would break the tie
   between the band's height and the art's scale. Pure scale, which is also
   what makes it full-width in the first place.

   The art is the game's own frame with one palette entry changed: the yellow
   blast is repainted in the field's magenta so it drops out of the sky and
   leaves clean ground for the wordmark. Nothing else is touched — the skyline
   keeps its own chartreuse. The background-color is that same field colour, so
   nothing flashes white underneath while the PNG is in flight; it is a custom
   property because the tool at /scratch/banner-colors/ can move the sky, and
   the colour showing through the gap has to move with it or the gap becomes
   the one moment the old sky is visible.

   `overflow: hidden` is what crops the window canvas. That canvas is the whole
   240-row frame pinned to the bottom edge, so the band clips it to the same 74
   rows the image is cropped to and the two land on each other exactly. */
.game-banner {
  position: relative;
  overflow: hidden;
  aspect-ratio: 256 / 74;
  background-color: var(--banner-sky, #ff00b4);
}

/* `cover` with a bottom anchor is the crop: the band is wider than it is tall
   against a 256x240 frame, so the frame scales by width and loses its top. */
.game-banner__art {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center bottom;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}

/* Both overlays are the frame at its own pixel size, scaled by the same width
   and pinned to the same edge, so they land on the image exactly and on each
   other exactly. Height is left to the ratio and the top runs out of the band,
   which is what `overflow` on the banner is for.

   __paint is the recoloured frame and is opaque: present only when the palette
   tool has changed something, and covering the <img> completely when it is.
   __canvas is the lights and is transparent everywhere it is not lighting a
   window, so whatever is underneath — the image, or the repaint over it — is
   what shows through and what is left when the lights stop. Neither carries a
   z-index: they are placed in this order by the script and document order is
   what keeps the lights on top. Only ever cover a working image. */
.game-banner__paint,
.game-banner__canvas {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: auto;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}

/* Centred across, and 7% of the band above centre down it — which sits it in
   the sky rather than across the skyline the way dead centre did. Black holds
   on both things it lands on: 17:1 against the chartreuse buildings and 5.9:1
   against the magenta field. Taking the blast out of the sky is what clears the
   space above it.

   A share of the band rather than a flat offset, for the same reason the shadow
   below is in em: the band is 74 rows of a frame scaled by width, so it runs
   from about 555px tall at 1920 down to about 93px on a small phone, and a flat
   lift that looks right on a desktop is a third of the band on the phone and
   puts the wordmark hard against the top edge. 7% is 29px at 1440 and holds its
   place all the way down. */
.game-banner__wordmark {
  position: absolute;
  z-index: 1;
  top: 43%;
  left: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
  max-width: 92%;
  /* Both colours are overridable from /scratch/banner-colors/, and both keep
     their real value in the fallback rather than in a property the tool would
     have to set: a banner with no config carries no custom properties at all,
     so these two lines stay the one place the defaults are written. The tool
     can put alpha on either — the frame underneath is opaque, so translucent
     type composites against the art rather than against nothing. */
  color: var(--banner-wordmark, var(--public-black));
  /* A hard white cut of the same word, down and to the left — no blur, so it
     reads as a second copy offset behind the black rather than as a glow. In
     em, so the offset grows with the type instead of thinning out as the
     banner scales up. */
  text-shadow: -0.05em 0.05em 0 var(--banner-wordmark-shadow, var(--public-white));
  font-family: "Lobster", "Brush Script MT", cursive;
  font-size: clamp(2rem, 9vw, 9rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.01em;
  text-align: center;
  white-space: nowrap;
}

/* The chips close the trailer panel, ranged left under the spec sheet rather
   than centred under the banner — they belong to the argument the panel makes,
   not to the lockup. No page padding of their own: the shell around the band
   sets the measure. Wraps to a stack rather than crushing when the panel is
   too narrow to seat both, which is what it does before the columns collapse. */
.game-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: clamp(1.75rem, 3vw, 2.25rem);
}

/* Black on white in a full pill with a two-pixel edge — the one round shape on
   a site that is otherwise square, and heavy enough to carry the weight of
   being what the page asks for. The label keeps the small caps the section
   links already use, so it still reads as site furniture rather than as a
   dropped-in store widget.

   Sized to seat two side by side in half a shell: this ran to 2.25rem of side
   padding as a single centred chip, which is wider than the pair fits in. */
.game-link {
  /* The height of the icon slot, not of the art in it. Both chips reserve this
     much so they stand the same height apart as well as side by side — sharing
     a row, the flex container's stretch hid the difference. */
  --game-link-icon: 1.45rem;
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  padding: 1.05rem 1.7rem;
  border: 2px solid var(--public-black);
  border-radius: 999px;
  background: var(--public-white);
  color: var(--public-black);
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  line-height: 1;
  text-decoration: none;
  text-transform: uppercase;
  transition: background-color 200ms ease, color 200ms ease;
}

.game-link:hover,
.game-link:focus-visible {
  background: var(--public-black);
  color: var(--public-white);
}

/* Sized to the label's cap height rather than to a round number, so the mark
   sits on the type instead of beside it. */
.game-link .steam-mark {
  flex: none;
  width: var(--game-link-icon);
  height: var(--game-link-icon);
}

/* Valve's blue would be the only colour on this page, so the mark is flattened
   to the chip's own two tones: body in the label's ink, cutout in the ground
   behind it. Both follow the chip over on hover, so the logo inverts with the
   rest of it rather than punching a white hole in the black. */
.game-link .steam-mark__body {
  fill: currentColor;
}

.game-link .steam-mark__detail {
  fill: var(--public-white);
}

.game-link:hover .steam-mark__detail,
.game-link:focus-visible .steam-mark__detail {
  fill: var(--public-black);
}

/* The play triangle on the trailer chip. Smaller than the Steam mark beside it
   and set to the label's own cap height rather than to the mark's box: a solid
   triangle at the logo's size would out-weigh it, since the logo is mostly
   negative space and this is not.

   It reserves the taller slot even so. preserveAspectRatio keeps the triangle
   at 0.72 x 0.84 and centres it in the box, so this changes what the chip
   measures without changing what is drawn. */
.game-link__glyph {
  flex: none;
  width: 0.72rem;
  height: var(--game-link-icon);
}

/* Screen left, the heading and two text blocks right. Centre the complete
   text column against the screen, keeping both on the same row. */
.game-about__inner {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: clamp(2rem, 4vw, 4.5rem);
  padding-block: clamp(2rem, 4vw, 4rem);
}

/* The frame is the containing block for the cycling canvas, so it is positioned
   even when nothing ever goes over it. Height comes from the shot's own aspect
   ratio: nothing crops it. */
.game-about__screen {
  position: relative;
  margin: 0;
  background: var(--public-black);
}

.game-about__screen img {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}

/* The cycle paints into a canvas laid exactly over the still at the still's own
   pixel size, then scaled by the same rule. The image underneath is what shows
   if the script never runs, so this can only ever cover a working screenshot. */
.screen-cycle__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}

.game-about__copy {
  display: grid;
  gap: clamp(1.75rem, 2.5vw, 2.5rem);
}

.game-about__block h3 {
  margin: 0 0 clamp(1.25rem, 2vw, 1.75rem);
  font-size: clamp(1.65rem, 2.5vw, 2.4rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.04em;
  text-wrap: balance;
}

.game-about__block p {
  margin: 0;
  max-width: 56ch;
  color: #3f4448;
  font-size: clamp(1rem, 1.35vw, 1.28rem);
  line-height: 1.55;
  letter-spacing: -0.015em;
  text-wrap: pretty;
}

/* The copy carries its own emphasis inline, and the site has one weight above
   body to give it. */
.game-about__copy strong {
  font-weight: 600;
}

/* Not currently rendered. The four short claims under the copy were never
   written past `01` to `04`, and the numbered features grid does that job now;
   the styles are kept, so a bullet list here again is markup only.

   A plain list: black marker, no rules between the items. Nothing here counts
   or positions, so the list is whatever length superType.json gives it. */
.game-points {
  padding-block: clamp(2rem, 4vw, 3.5rem) 0;
}

.game-points__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.game-points__list li {
  position: relative;
  padding-left: 1.6rem;
  font-size: clamp(1rem, 1.15vw, 1.12rem);
  line-height: 1.5;
  letter-spacing: -0.01em;
  text-wrap: pretty;
}

.game-points__list li + li {
  margin-top: 0.7rem;
}

/* Square, not round: the marker borrows its shape from the collage pixels. */
.game-points__list li::before {
  position: absolute;
  top: 0.62em;
  left: 0;
  width: 0.45rem;
  height: 0.45rem;
  background: var(--public-black);
  content: "";
}

/* The trailer opens the page, directly under the wordmark, and is the one band
   on it that is not on white.

   Frost rather than a grey: `--public-soft` beside true white reads as white
   that has gone off, where a cool tint reads as a second surface someone chose.
   It is the site's own alternate ground — the frost palette paints section
   marks in it — so the band is not a colour introduced for this page. It also
   suits what stands on it: the game is magenta and chartreuse, and both sit
   further from a cool ground than from a warm one.

   Full-bleed by default. Nothing constrains a section on this site, so the
   background runs edge to edge and the shell inside it holds the content to the
   same measure as every other section. That is the whole point of the band —
   a tint that stopped at the text column would read as a card. */
.game-trailer {
  padding-block: clamp(3rem, 6vw, 5.5rem);
  background: var(--surface-frost);
  /* /games/super-type/#trailer lands here from off the page, and the chip in
     the panel points at the section it is standing in. Either way the band's
     own top padding is scrolled past on the way, so this puts it back. */
  scroll-margin-top: 2rem;
}

/* Video left, the case for it right. Weighted to the video rather than split
   evenly: at an even 1fr the picture would come out narrower than the 37.5rem
   it held when it was centred alone, and the panel beside it does not need the
   width — it is one paragraph, three rows of spec and two chips. 1.15fr holds
   the video at about the size it was.

   Centred against each other, so the shorter of the two sits at the middle of
   the other rather than at its top. */
.game-trailer__inner {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  align-items: center;
  gap: clamp(2rem, 4vw, 4.5rem);
}

/* Mono metadata, the same voice the feature indices and the spec labels speak
   in: the band is named rather than titled, so the video stays the largest
   thing in it. */
.game-trailer__kicker {
  margin: 0 0 1.25rem;
  color: var(--public-muted);
  font-family: "Courier New", Courier, monospace;
  font-size: 0.65rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* The page's opening paragraph, set a step above the body copy further down —
   it is the caption to the trailer and the first prose anyone reads. */
.game-trailer__lead {
  max-width: 54ch;
  margin: 0;
  font-size: clamp(1.05rem, 1.25vw, 1.18rem);
  line-height: 1.55;
  letter-spacing: -0.015em;
  text-wrap: pretty;
}

.game-trailer__lead strong {
  font-weight: 600;
}

/* The borrowed spec sheet keeps its hairlines, which are drawn for white. On
   frost they are close enough in value to read as a lighter rule rather than as
   a line that has lost its ground, so they are left alone. */
.game-trailer__panel .game-intro__spec {
  margin-top: clamp(1.5rem, 2.5vw, 2rem);
}

/* The source is a 1280x720 file with the game pillarboxed inside it: at 3x, a
   256x240 screen is 768 wide and full height, centred, with 256 of black either
   side. `cover` against a 10/9 box scales the frame to the box height and lets
   the rest run past the edges, so 800 source pixels survive — the 768 of game
   plus 16 of black either side, which is the margin that keeps this reading as
   a screen rather than as something trimmed too close. Cropped, not fitted: no
   axis is scaled independently of the other, so nothing is squashed.

   It costs the two shots that are genuinely 16:9 — the live-action hand and the
   end card — 240px a side. Both are centre-framed and survive it; the poster is
   a gameplay frame and crops with the video, since object-fit governs it too. */
.game-trailer__player {
  display: block;
  width: 100%;
  aspect-ratio: 10 / 9;
  background: var(--public-black);
  object-fit: cover;
}

.game-intro {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: start;
  gap: clamp(2rem, 4vw, 4.5rem);
  padding-block: clamp(2.5rem, 5vw, 5rem);
}

/* Height comes from the frame's own aspect ratio — nothing crops it, so a GIF
   dropped in at any size keeps its whole picture. */
.game-intro__media {
  margin: 0;
  background: var(--public-black);
}

.game-intro__media img {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}

/* The title sizes to its column now, not to the viewport: it is a heading
   beside a picture, not a full-bleed display line. It is an h1 on the game
   page and an h2 in the games index, which reuses this block. */
.game-intro__body :is(h1, h2) {
  margin: 0;
  font-size: clamp(2.5rem, 5vw, 4.5rem);
  font-weight: 500;
  line-height: 0.9;
  letter-spacing: -0.06em;
}

/* The tagline keeps the voice it had as a full-width band: mono metadata, a red
   cell marking the line live, and a block cursor — the game is a typing game
   and the line reads as something being typed. */
.game-intro__tagline {
  margin: 1.25rem 0 0;
  font-family: "Courier New", Courier, monospace;
  font-size: clamp(0.74rem, 1vw, 0.86rem);
  line-height: 1.7;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.game-intro__tagline::before {
  display: inline-block;
  width: 0.6em;
  height: 0.6em;
  margin-right: 0.9em;
  background: var(--accent-red);
  content: "";
}

.game-intro__tagline::after {
  display: inline-block;
  width: 0.55em;
  height: 1em;
  margin-left: 0.45em;
  background: currentColor;
  content: "";
  vertical-align: text-bottom;
}

.game-intro__spec {
  margin: clamp(1.75rem, 3vw, 2.5rem) 0 0;
}

.game-intro__spec > div {
  display: grid;
  grid-template-columns: 7rem 1fr;
  padding-block: 0.85rem;
  border-top: 1px solid var(--public-line);
}

.game-intro__spec dt {
  color: var(--public-muted);
  font-size: 0.65rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.game-intro__spec dd {
  margin: 0;
}

/* Lead copy, not a display line: the summary runs long enough to need body
   line-height and a capped measure rather than display tracking. */
.game-intro__summary {
  max-width: 54ch;
  margin: clamp(1.75rem, 3vw, 2.5rem) 0 0;
  font-size: clamp(1.05rem, 1.25vw, 1.2rem);
  line-height: 1.55;
  letter-spacing: -0.015em;
  text-wrap: pretty;
}

/* A dark band separates the practical controls from the white game overview. */
.game-controls {
  padding-block: clamp(3.5rem, 6vw, 6rem);
  background: var(--public-black);
  color: var(--public-white);
}

.game-controls__heading {
  margin: 0;
  font-size: clamp(1.65rem, 2.5vw, 2.4rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.04em;
  text-align: center;
}

.game-controls__list {
  width: min(100%, 54rem);
  margin: clamp(2rem, 4vw, 3.25rem) auto 0;
  padding-left: 1.4em;
  font-size: clamp(1rem, 1.35vw, 1.28rem);
  line-height: 1.55;
  letter-spacing: -0.015em;
}

.game-controls__list:empty {
  display: none;
}

.game-controls__list li + li {
  margin-top: 0.75em;
}

.game-controls__list li::marker {
  color: var(--accent-red);
}

/* Square rather than round: the game is a grid of square keys drawn on a
   256-pixel field, and a circle is the one shape nothing on this page is. */
.game-controls__list {
  list-style-type: square;
}

/* The modifiers group. Sized between the section heading and the body so it
   reads as a division inside the list rather than a second section. */
.game-controls__subheading {
  width: min(100%, 54rem);
  margin: clamp(1.75rem, 3vw, 2.5rem) auto 0.75em;
  font-size: clamp(1.05rem, 1.5vw, 1.28rem);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.02em;
}

/* A run of keys, set in the accent so the four ranges can be picked out of the
   sentence at a glance. The brackets are in the text rather than added here:
   they group the range and should survive being copied out. */
.key-range {
  color: var(--accent-red);
  font-family: "Courier New", Courier, monospace;
  font-size: 0.92em;
  white-space: nowrap;
}

/* ----------------------------------------------------------------------------
   The aiming bar

   A window onto the game's field at the game's own resolution, so the canvas
   is 256 pixels across whatever it is shown at and every rule here has to
   leave that scaling alone. Narrower than the bullets above it: at the list's
   full column the 2:1 crop comes out over four hundred pixels tall, which
   stops reading as a bar.
---------------------------------------------------------------------------- */

.control-sim {
  width: min(100%, 44rem);
  margin: clamp(2rem, 4vw, 3.25rem) auto 0;
}

.control-sim[hidden] {
  display: none;
}

.control-sim__screen {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid rgb(255 255 255 / 0.22);
  background: var(--public-black);
  /* The canvas is drawn in game pixels and blown up here, so the upscale has
     to be a hard one. crisp-edges first for the browsers that only know it. */
  image-rendering: crisp-edges;
  image-rendering: pixelated;
  cursor: crosshair;
}

/* The bar only listens while it holds the keyboard, so the edge is doing real
   work: it is the difference between a picture and an instrument. Kept for
   :focus rather than :focus-visible alone because reaching it with a mouse is
   what arms it, and that needs saying too.

   It is the hairline itself that thickens and lights up, rather than a separate
   ring standing off it. The extra 2px arrives as an outline flush against the
   border rather than as border-width, because a border that grows eats into the
   content box: the canvas is sized by its own aspect ratio, so widening the
   frame would rescale the picture and nudge the page below it every time
   someone clicked in. An outline is drawn outside the layout and costs nothing.
   Border and outline are the same white, so the two read as one 3px edge. */
.control-sim__screen:focus {
  border-color: var(--public-white);
  outline: 2px solid var(--public-white);
  outline-offset: 0;
}

/* Nothing follows the bar. The section's own bottom padding ends it, so there
   is no trailing margin here to add to that. */

/* Scoped to the card that actually holds art: this rule sits after
   .media-placeholder, and an unscoped background here would paint over the
   dashed placeholder the unannounced card still uses. */
.game-index-card__media:has(img) {
  overflow: hidden;
  background: var(--public-black);
}

.game-index-card__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}

/* ----------------------------------------------------------------------------
   Coming soon

   The holding state for a section with nothing in it yet. It sits on the same
   hairline as an index list and speaks in the same mono metadata voice, so an
   empty section reads as deliberate rather than unfinished.
---------------------------------------------------------------------------- */

.coming-soon {
  display: flex;
  flex-direction: column;
  align-items: start;
  padding-block: clamp(2.5rem, 5vw, 4.5rem);
  border-top: 1px solid var(--public-black);
  border-bottom: 1px solid var(--public-line);
}

.coming-soon__eyebrow {
  display: flex;
  align-items: center;
  gap: 0.9em;
  margin: 0 0 clamp(1.25rem, 2.5vw, 2rem);
  color: var(--public-muted);
  font-family: "Courier New", Courier, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

/* The block cursor the game page's tagline ends on, held to the same beat. */
.coming-soon__eyebrow::after {
  width: 0.55em;
  height: 1em;
  background: currentColor;
  animation: block-cursor-blink 1.1s step-end infinite;
  content: "";
}

.coming-soon__heading {
  max-width: 18ch;
  margin: 0;
  font-size: clamp(2rem, 4vw, 3.6rem);
  font-weight: 500;
  line-height: 0.94;
  letter-spacing: -0.055em;
  text-wrap: balance;
}

.coming-soon__body {
  max-width: 52ch;
  margin: clamp(1.25rem, 2vw, 1.75rem) 0 0;
  color: var(--public-muted);
  font-size: clamp(0.95rem, 1.2vw, 1.1rem);
  line-height: 1.6;
  text-wrap: pretty;
}

.coming-soon__action {
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
  color: inherit;
}

/* On the black newsletter panel the muted greys would fall out; the holding
   line borrows the panel's own contrast instead. */
.home-newsletter__body {
  max-width: 46rem;
  margin: 0 0 clamp(2rem, 3vw, 3rem);
  color: rgba(255, 255, 255, 0.62);
  font-size: clamp(0.95rem, 1.2vw, 1.1rem);
  line-height: 1.6;
  text-wrap: pretty;
}

.newsletter-pending {
  display: flex;
  align-items: center;
  gap: 0.9em;
  margin: 0;
  padding-block: 1rem;
  border-top: 1px solid rgba(255, 255, 255, 0.35);
  color: rgba(255, 255, 255, 0.75);
  font-family: "Courier New", Courier, monospace;
  font-size: 0.75rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.newsletter-pending::before {
  width: 0.6em;
  height: 0.6em;
  background: var(--accent-yellow);
  content: "";
}

.contact-placeholder {
  min-height: 18rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding-block: 2rem;
  border-top: 1px solid var(--public-black);
  border-bottom: 1px solid var(--public-line);
  font-size: clamp(1.5rem, 4vw, 4rem);
  letter-spacing: -0.04em;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (max-width: 920px) {
  .public-nav {
    display: none;
  }

  .public-header {
    grid-template-columns: 1fr auto;
  }

  .public-menu {
    position: relative;
    display: block;
    align-self: stretch;
    min-width: 4rem;
  }

  .public-menu summary {
    position: relative;
    z-index: 4;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    cursor: pointer;
    color: var(--public-white);
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.09em;
    list-style: none;
    text-transform: uppercase;
  }

  .public-menu summary::-webkit-details-marker {
    display: none;
  }

  .public-menu nav {
    position: absolute;
    top: 100%;
    right: calc(var(--public-pad) * -1);
    width: min(22rem, 100vw);
    display: flex;
    flex-direction: column;
    padding: 1rem var(--public-pad) 1.5rem;
    /* Literal, like the border below it: the open panel needs an edge against
       the header whether or not a nav item is lit. */
    border-top: 2px solid #343633;
    border-bottom: 1px solid #343633;
    background: var(--public-black);
    box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.2);
  }

  .public-menu a {
    position: relative;
    padding-block: 0.85rem;
    padding-left: 1rem;
    border-bottom: 1px solid #343633;
    color: #a8aaa6;
    transition: color 180ms ease;
  }

  .public-menu a::before {
    position: absolute;
    top: 0.65rem;
    bottom: 0.65rem;
    left: 0;
    width: 0.3rem;
    background: var(--nav-accent);
    content: "";
    transform: scaleY(0);
    transition: transform 180ms ease;
  }

  .public-menu a:hover,
  .public-menu a:focus-visible,
  .public-menu a[aria-current="page"] {
    color: var(--public-white);
  }

  .public-menu a:hover::before,
  .public-menu a:focus-visible::before,
  .public-menu a[aria-current="page"]::before {
    transform: scaleY(1);
  }

  .feature-card {
    grid-template-columns: 1fr;
  }

  .feature-card__media,
  .game-index-card__media {
    min-height: 24rem;
  }

  /* Stacked earlier than the page's other two-ups, and capped rather than run
     to the shell. The panel carries a paragraph, a spec sheet and two chips, so
     it runs out of room to seat them before a picture-and-copy pair would; and
     a video given the whole shell at this width is taller than the viewport.
     38rem is about the width the trailer held when it was centred on its own,
     so narrow screens get the size it always had. */
  .game-trailer__inner {
    grid-template-columns: min(100%, 38rem);
    justify-content: center;
    gap: clamp(1.75rem, 4vw, 2.5rem);
  }

  .public-footer {
    grid-template-columns: 1fr 1fr;
  }

  .public-footer__contact {
    grid-column: 2;
  }
}

@media (max-width: 720px) {
  /* The desktop extension is about a third of a phone screen, and mobile was
     the one breakpoint adding it to `height` as well as the floor — so the hero
     overflowed the viewport before it had done anything. A sliver still reads
     as continuing past the fold. Both the overhang and the floor are mobile's
     own here; change these without touching desktop. */
  .game-hero {
    --hero-extension: 2.5rem;
    min-height: calc(26rem + var(--hero-extension));
    height: calc(
      100svh - var(--public-header-height) + var(--hero-extension)
    );
  }

  .studio-position__inner,
  .home-newsletter__inner,
  .section-page__header,
  .game-intro,
  .game-about__inner,
  .game-index-card {
    grid-template-columns: 1fr;
    gap: 3rem;
  }

  .studio-position__content {
    display: block;
  }

  .studio-position .studio-position__subheading {
    margin-bottom: 2rem;
  }

  .updates-layout,
  .latest-update {
    grid-template-columns: 1fr;
  }

  .latest-update__media {
    min-height: 20rem;
  }

  .latest-update__content {
    min-height: 16rem;
    border-top: 1px solid var(--public-line);
    border-left: 0;
  }

  .update-card--lead {
    min-height: 30rem;
    grid-row: auto;
  }

  .update-card__media {
    min-height: 18rem;
  }

  .public-section-head {
    min-height: auto;
    align-items: end;
  }

  .feature-card {
    min-height: 34rem;
  }

  .feature-card__media {
    min-height: 19rem;
  }

  .home-newsletter__inner {
    min-height: 30rem;
  }

  .public-footer {
    grid-template-columns: 1fr;
  }

  .public-footer__contact {
    grid-column: auto;
  }

  .section-page__header {
    grid-template-rows: auto;
    gap: 1.5rem;
  }

  .section-page__description {
    grid-column: auto;
  }

  .index-row {
    grid-template-columns: 1fr auto;
    gap: 0.75rem;
    padding-block: 1.5rem;
  }

  .index-row h2 {
    grid-column: 1 / -1;
    grid-row: 2;
  }

}

@media (max-width: 430px) {
  .public-section-head {
    align-items: flex-start;
    flex-direction: column;
  }

  .newsletter-form {
    grid-template-columns: 1fr;
    border: 0;
  }

  .newsletter-form input {
    border-bottom: 1px solid rgba(255, 255, 255, 0.7);
  }

  .newsletter-form button {
    margin-top: 1rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-media,
  .game-hero__word,
  .public-nav a::after,
  .public-nav a span,
  .public-menu a,
  .public-menu a::before,
  .public-brand__mark--color,
  .xzzx-mark .xzzx-letter {
    transition: none;
  }

  .xzzx-mark--cycle .xzzx-letter,
  .xzzx-mark--blend .xzzx-letter,
  .xzzx-mark--trace .xzzx-letter,
  .public-brand__mark--color .xzzx-letter {
    animation: none;
  }

  /* The cursor stays; it just stops blinking. */
  .coming-soon__eyebrow::after {
    animation: none;
  }

  /* Without the draw animation the trace mark would stay invisible. */
  .xzzx-mark--trace .xzzx-letter {
    stroke-dashoffset: 0;
  }
}
