/* ═══════════════════════════════════════════════════════════
   404 — "Exception Invaders"
   Figma: Web 2.0 → 404 (node 1811:91127)

   Page-scoped. Anything the design system already owns is reused
   from brand.css: the nav, the cascade CTA buttons
   (.hero-cta--in-hero .btn-lime / .btn-outline — Figma's
   "Button Animation" component), --font-head / --font-body, and the
   light/dark token set. Only the pieces the system has no primitive
   for live here — including the design's mono, which is Geist Mono
   rather than the brand's --font-mono (Fira Code).
   ═══════════════════════════════════════════════════════════ */

:root {
  /* The design's mono. brand.css's --font-mono is Fira Code, which is the
     right site-wide token but not what this page was drawn in. */
  --nf-mono: 'Geist Mono', 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Figma frame surface is #e5e5e5, one step darker than --ds-background,
     so the game frame's hairline border reads without a card behind it. */
  --nf-bg:      #e5e5e5;
  --nf-ink:     #171717;
  --nf-lede:    #525252;
  --nf-hud:     rgba(23, 23, 23, 0.6);
  --nf-frame:   #000000;
  --nf-veil:    rgba(229, 229, 229, 0.9);
  --nf-kbd-bg:  rgba(0, 0, 0, 0.8);
  --nf-kbd-brd: rgba(233, 235, 223, 0.12);
  --nf-kbd-ink: #f2ff70;

  /* Canvas palette. 404-game.js reads these off .nf-stage with
     getComputedStyle, so the game repaints itself on a theme flip
     instead of hard-coding colours in JS. */
  --nf-play-ink:    #171717;
  --nf-play-dim:    rgba(23, 23, 23, 0.34);
  /* #f2ff70 is 1.4:1 on #e5e5e5 — invisible. The player craft and its
     shots use a darkened lime in light mode and the brand lime in dark. */
  --nf-play-accent: #5f6b00;
  --nf-play-hot:    #b42318;
}
[data-theme="dark"] {
  --nf-bg:      #171717;
  --nf-ink:     #fafafa;
  --nf-lede:    #a3a3a3;
  --nf-hud:     rgba(250, 250, 250, 0.6);
  --nf-frame:   rgba(255, 255, 255, 0.24);
  --nf-veil:    rgba(23, 23, 23, 0.9);

  --nf-play-ink:    #fafafa;
  --nf-play-dim:    rgba(250, 250, 250, 0.32);
  --nf-play-accent: #f2ff70;
  --nf-play-hot:    #ff6b6b;
}

/* brand.css paints html with --ds-card (and #0a0a0a in dark) so the
   overscroll gutter matches the footer. This page has no footer and its
   own surface, so both layers follow --nf-bg instead. */
html[data-theme],
body {
  background: var(--nf-bg);
}

.nf-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Full-bleed shell. The 404 layout is deliberately wider than the
   1400px inner-page container — the game frame runs to a 52px gutter,
   as drawn. */
.nf-wrap {
  width: 100%;
  max-width: 2240px;
  margin: 0 auto;
  padding: 0 clamp(20px, 3vw, 52px);
}

/* ── HERO ───────────────────────────────────────────────── */

.nf-hero {
  padding-top: clamp(44px, 6vw, 92px);
  padding-bottom: clamp(24px, 2.6vw, 44px);
}
.nf-hero-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 40px;
}
.nf-hero-copy {
  max-width: 600px;
  min-width: 0;
}
.nf-title {
  font-family: var(--font-body);
  font-size: clamp(26px, 2.4vw, 36px);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: 0;
  color: var(--nf-ink);
  margin: 0 0 16px;
  overflow-wrap: anywhere;
}
/* The scrambled token decodes to the URL the visitor actually asked for,
   so it is arbitrary text — never let a long path blow out the column. */
.nf-scramble {
  font-family: var(--nf-mono);
  font-size: 0.92em;
}
.nf-lede {
  max-width: 480px;
  font-family: var(--font-body);
  font-size: clamp(16px, 1.2vw, 18px);
  line-height: 1.5;
  color: var(--nf-lede);
  margin: 0;
}
/* .hero-cta ships centred with a 48px top margin; the 404 stacks left
   with the 93px gap drawn between the copy block and the buttons. */
.nf-cta.hero-cta {
  justify-content: flex-start;
  gap: 16px;
  margin: clamp(36px, 5.4vw, 93px) 0 0;
}
/* The in-hero cascade CTAs are authored for the dark homepage hero
   (white stroke, --ds-background fill). On this surface the secondary
   needs the same treatment the light homepage sections use. */
.nf-cta.hero-cta--in-hero .btn-outline {
  border-color: var(--nf-ink);
  color: var(--nf-ink);
  background-color: transparent;
}
.nf-cta.hero-cta--in-hero .btn-outline:hover {
  border-color: var(--nf-ink);
  color: var(--nf-ink);
  background: rgba(0, 0, 0, 0.04);
}
.nf-cta.hero-cta--in-hero .btn-outline .btn-cascade-fill {
  background: #d4d4d4;
}
[data-theme="dark"] .nf-cta.hero-cta--in-hero .btn-outline:hover {
  background: rgba(255, 255, 255, 0.06);
}
[data-theme="dark"] .nf-cta.hero-cta--in-hero .btn-outline .btn-cascade-fill {
  background: rgba(255, 255, 255, 0.2);
}
/* The component drops the primary's border to transparent on hover, and a
   transparent border still shows the element's own background — lime — so the
   filled button wore a 1px lime ring. The cascade cannot cover it either:
   overflow:hidden clips at the padding box, so no negative inset reaches the
   border. Matching the border to the cascade fill merges the two.
   (Transparent is correct on the homepage, whose hero behind it is dark.) */
.nf-cta.hero-cta--in-hero .btn-lime:hover,
.nf-cta.hero-cta--in-hero .btn-lime:focus-visible {
  border-color: #171717;
}
[data-theme="dark"] .nf-cta.hero-cta--in-hero .btn-lime:hover,
[data-theme="dark"] .nf-cta.hero-cta--in-hero .btn-lime:focus-visible {
  border-color: var(--ds-primary-cascade-fill);
}

.nf-code {
  flex: 0 0 auto;
  font-family: var(--font-head);
  font-weight: 400;
  font-size: clamp(84px, 11.4vw, 180px);
  line-height: 1;
  letter-spacing: -0.04em; /* -7.2px at 180px */
  color: var(--nf-ink);
  user-select: none;
}

/* ── GAME: HUD BAR ──────────────────────────────────────── */

.nf-game {
  padding-bottom: clamp(40px, 5vw, 80px);
}
.nf-game-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px 24px;
  padding: 24px 0;
}
.nf-game-stats,
.nf-game-keys {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
}
.nf-game-name,
.nf-stat {
  font-family: var(--nf-mono);
  font-size: 12px;
  line-height: 1.2;
  letter-spacing: 0.12px;
  color: var(--nf-hud);
  white-space: nowrap;
}
.nf-game-name {
  text-transform: uppercase;
}
.nf-key {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.nf-key-label {
  font-family: var(--nf-mono);
  font-size: 12px;
  line-height: 1.2;
  letter-spacing: 0.12px;
  color: var(--nf-hud);
  white-space: nowrap;
}
.nf-kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 2px;
  background: var(--nf-kbd-bg);
  border: 1px solid var(--nf-kbd-brd);
  border-radius: 4px;
  font-family: var(--nf-mono);
  font-size: 10.4px;
  line-height: 1;
  letter-spacing: 0.104px;
  text-transform: uppercase;
  color: var(--nf-kbd-ink);
}
.nf-kbd--wide {
  padding-left: 5px;
  padding-right: 5px;
}
/* Lit while the game actually owns the keyboard, so it is obvious when
   arrow keys will steer the ship instead of scrolling the page. */
.nf-game-keys {
  opacity: 0.55;
  transition: opacity 0.25s ease;
}
.nf-game.is-live .nf-game-keys {
  opacity: 1;
}
.nf-touch-hint {
  display: none;
  font-family: var(--nf-mono);
  font-size: 12px;
  letter-spacing: 0.12px;
  color: var(--nf-hud);
}

/* ── GAME: STAGE ────────────────────────────────────────── */

.nf-stage {
  position: relative;
  height: clamp(340px, 40.4vw, 698px);
  border: 1px solid var(--nf-frame);
  overflow: hidden;
  touch-action: pan-y;
}
.nf-stage:focus-visible {
  outline: 3px solid var(--nf-play-accent);
  outline-offset: 3px;
}
#nf-canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.nf-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 24px;
  text-align: center;
  background: var(--nf-veil);
  transition: opacity 0.2s ease;
}
.nf-overlay[hidden] {
  display: none;
}
/* Attract and resume prompts sit in a pill at the foot of the board so the
   marching fleet stays visible behind them; only a real stop (paused, run
   over) earns the full veil. */
.nf-overlay--quiet {
  background: transparent;
  gap: 8px;
  pointer-events: none;
}
.nf-overlay--quiet .nf-overlay-hint,
.nf-overlay--quiet .nf-overlay-btn {
  pointer-events: auto;
}
.nf-overlay--quiet .nf-overlay-hint {
  padding: 6px 12px;
  border-radius: 4px;
  background: var(--nf-veil);
}
.nf-overlay--quiet .nf-overlay-btn {
  margin-top: 0;
}
.nf-overlay-title[hidden] {
  display: none;
}
.nf-overlay-title {
  font-family: var(--nf-mono);
  font-size: clamp(13px, 1.1vw, 15px);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--nf-ink);
  margin: 0;
}
.nf-overlay-hint {
  font-family: var(--nf-mono);
  font-size: 12px;
  letter-spacing: 0.12px;
  color: var(--nf-hud);
  margin: 0;
  max-width: 64ch;
}
.nf-overlay-btn {
  margin-top: 6px;
  padding: 9px 20px;
  background: var(--nf-kbd-bg);
  border: 1px solid var(--nf-kbd-brd);
  border-radius: 4px;
  font-family: var(--nf-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nf-kbd-ink);
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.nf-overlay-btn:hover {
  background: #000;
  border-color: var(--nf-kbd-ink);
}
.nf-overlay-btn:focus-visible {
  outline: 2px solid var(--nf-play-accent);
  outline-offset: 2px;
}
.nf-noscript {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  text-align: center;
  font-family: var(--nf-mono);
  font-size: 12px;
  color: var(--nf-hud);
}
.nf-noscript a {
  color: var(--nf-ink);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ── RESPONSIVE ─────────────────────────────────────────── */

/* Touch devices get no keycaps — the pointer scheme is the control
   scheme, so say so instead of listing keys nobody can press. */
@media (hover: none) and (pointer: coarse) {
  .nf-game-keys { display: none; }
  .nf-touch-hint { display: block; }
}

@media (max-width: 900px) {
  .nf-hero-inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
  }
  .nf-code {
    order: -1;
    font-size: clamp(72px, 21vw, 132px);
    margin-bottom: 4px;
  }
  /* width, not just max-width: the column is a flex item under
     align-items:flex-start, so it shrink-to-fits its widest child — the 480px
     lede — and capped the CTAs at 480 even where the gutters allowed 846.
     brand.css already stacks these buttons full-width below 900px; they just
     needed a full-width column to fill. */
  .nf-hero-copy {
    width: 100%;
    max-width: 100%;
  }
  .nf-cta.hero-cta {
    width: 100%;
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
  }
  .nf-game-bar { padding: 18px 0; }
  .nf-stage { height: clamp(320px, 62vw, 460px); }
}

@media (max-width: 560px) {
  .nf-game-stats { gap: 12px; }
  .nf-stage { height: 340px; }
}

/* .reveal starts at opacity 0 and waits on brand-animations.js. On an
   error page a script failure must never leave a blank screen. */
.no-js .reveal,
.no-js .reveal-scale {
  opacity: 1 !important;
  transform: none !important;
}
