/* Rise of the Machines — riseofthemachines.fun
   One page, one stylesheet, no framework and no external requests.

   The palette is lifted straight off the game's HUD so the site and the thing
   it is selling do not look like two different products: amber title, cyan
   button rims, shard yellow, the green the wave counter uses, the red the
   lives counter uses. */

/* ------------------------------------------------------------------ font */
/* Shipped with the site rather than pulled from a font CDN: the game already
   licenses VT323, the file is 146 KB, and a third-party request on every page
   load to save that is a poor trade. */
@font-face {
  font-family: "VT323";
  src: url("../assets/fonts/VT323-Regular.ttf") format("truetype");
  font-weight: 400;
  font-display: swap;
}

/* ---------------------------------------------------------------- tokens */
:root {
  --ink:       #05070c;
  --panel:     #0b1018;
  --panel-2:   #121a29;
  --line:      #22304a;
  --line-hot:  #33507d;

  --amber:     #ffa62b;
  --amber-2:   #ffc861;
  --cyan:      #5fd8ff;
  --green:     #7ef29d;
  --red:       #ff6b6b;
  --shard:     #ffd23f;
  --violet:    #b98bff;

  --text:      #cbd5e6;
  --dim:       #8290a8;

  --display: "VT323", ui-monospace, "Cascadia Mono", Consolas, monospace;
  --body: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --wrap: 1180px;
  --r: 10px;
}

/* ----------------------------------------------------------------- reset */
*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; scroll-padding-top: 74px; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ink);
  color: var(--text);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }
a { color: var(--cyan); }

.wrap { width: min(var(--wrap), calc(100% - 48px)); margin-inline: auto; }

.skip {
  position: absolute; left: -999px; top: 0; z-index: 100;
  background: var(--amber); color: #000; padding: 10px 16px; font-weight: 700;
}
.skip:focus { left: 8px; top: 8px; }

:focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; }

/* -------------------------------------------------------------- sprites */
/* Every walk cycle in the game is four frames and every attack is six, at a
   uniform frame size — so these can be driven entirely from the sheet width
   with no per-character bookkeeping. `image-rendering: pixelated` is doing
   real work: without it a browser smooths the art on upscale and the whole
   thing goes to mush. */
.sprite {
  background-repeat: no-repeat;
  image-rendering: pixelated;
  flex: none;
}

.sprite[class*="mech-"] { width: 112px; height: 112px; background-size: 448px 112px; }
.sprite[class*="foe-"]  { width: 88px;  height: 88px;  background-size: 352px 88px; }
.sprite[class*="boss-"] { width: 152px; height: 152px; background-size: 608px 152px; }

.walk[class*="mech-"] { animation: stride-mech .66s steps(4) infinite; }
.walk[class*="foe-"]  { animation: stride-foe .58s steps(4) infinite; }
.walk[class*="boss-"] { animation: stride-boss .82s steps(4) infinite; }

@keyframes stride-mech { to { background-position-x: -448px; } }
@keyframes stride-foe  { to { background-position-x: -352px; } }
@keyframes stride-boss { to { background-position-x: -608px; } }

.mech-artillery { background-image: url("../assets/mechs/Artillery_walk.png"); }
.mech-brute     { background-image: url("../assets/mechs/Brute_walk.png"); }
.mech-scout     { background-image: url("../assets/mechs/Scout_walk.png"); }
.mech-sniper    { background-image: url("../assets/mechs/Sniper_walk.png"); }

.foe-drone      { background-image: url("../assets/foes/Drone_walk.png"); }
.foe-turret     { background-image: url("../assets/foes/Turret_walk.png"); }
.foe-elite      { background-image: url("../assets/foes/Elite_walk.png"); }
.foe-phantom    { background-image: url("../assets/foes/Phantom_walk.png"); }
.foe-overseer   { background-image: url("../assets/foes/Overseer_walk.png"); }
.foe-terminator { background-image: url("../assets/foes/Terminator_walk.png"); }

.boss-Queen         { background-image: url("../assets/bosses/Queen_walk.png"); }
.boss-Colossus      { background-image: url("../assets/bosses/Colossus_walk.png"); }
.boss-Hacker        { background-image: url("../assets/bosses/Hacker_walk.png"); }
.boss-OverseerPrime { background-image: url("../assets/bosses/OverseerPrime_walk.png"); }
.boss-TurretLord    { background-image: url("../assets/bosses/TurretLord_walk.png"); }
.boss-FlameWarden   { background-image: url("../assets/bosses/FlameWarden_walk.png"); }
.boss-IceTitan      { background-image: url("../assets/bosses/IceTitan_walk.png"); }
.boss-Overlord      { background-image: url("../assets/bosses/Overlord_walk.png"); }
.boss-VoidWalker    { background-image: url("../assets/bosses/VoidWalker_walk.png"); }
.boss-NexusEcho     { background-image: url("../assets/bosses/NexusEcho_walk.png"); }

/* Hover a mech card and it swings instead of walking. Six frames, so both the
   sheet width and the step count change with the image. */
.mech:hover .sprite, .mech:focus-within .sprite {
  background-size: 672px 112px;
  animation: swing .54s steps(6) infinite;
}
@keyframes swing { to { background-position-x: -672px; } }
.mech:hover [class*="mech-artillery"] { background-image: url("../assets/mechs/Artillery_attack.png"); }
.mech:hover [class*="mech-brute"]     { background-image: url("../assets/mechs/Brute_attack.png"); }
.mech:hover [class*="mech-scout"]     { background-image: url("../assets/mechs/Scout_attack.png"); }
.mech:hover [class*="mech-sniper"]    { background-image: url("../assets/mechs/Sniper_attack.png"); }

/* ---------------------------------------------------------------- topbar */
.topbar {
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; padding: 12px clamp(16px, 4vw, 40px);
  background: rgba(5, 7, 12, .72);
  backdrop-filter: blur(9px);
  border-bottom: 1px solid transparent;
  transition: border-color .25s, background .25s;
}
.topbar.stuck { border-bottom-color: var(--line); background: rgba(5, 7, 12, .93); }

.brand {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--display); font-size: 22px; letter-spacing: .13em;
  color: var(--amber); text-decoration: none; white-space: nowrap;
}
.brand img { image-rendering: pixelated; }

.topbar nav { display: flex; align-items: center; gap: clamp(10px, 2vw, 26px); }
.topbar nav a {
  font-family: var(--display); font-size: 20px; letter-spacing: .09em;
  color: var(--dim); text-decoration: none; transition: color .18s;
}
.topbar nav a:hover { color: var(--cyan); }
.topbar-cta {
  color: var(--ink) !important; background: var(--amber);
  padding: 3px 14px; border-radius: 6px;
}
.topbar-cta:hover { background: var(--amber-2); }

/* The playable build sits beside the download in the bar, in the cyan this
   site already uses to mean "you can touch this". Amber stays the download, so
   the two primary actions never read as the same button twice. */
.topbar-play {
  color: var(--ink) !important; background: var(--cyan);
  padding: 3px 14px; border-radius: 6px;
  box-shadow: 0 0 18px -4px var(--cyan);
}
.topbar-play:hover { background: #9ce8ff; }
/* The nav sheds links as the bar narrows, least useful first, rather than
   wrapping or pushing the download button off the edge. Adding two sections
   was enough to overflow this at 820px, which is a very ordinary tablet. */
@media (max-width: 1180px) {
  .topbar nav a[href="#screens"], .topbar nav a[href="#arsenal"] { display: none; }
}
@media (max-width: 980px) {
  .topbar nav a[href="#hives"] { display: none; }
}
@media (max-width: 860px) {
  .topbar nav a[href="#echoes"] { display: none; }
}
@media (max-width: 780px) {
  .topbar nav a[href="#mechs"] { display: none; }
}
/* The name used to vanish below 720px, leaving a bare icon and no way to tell
   what the site was. It shrinks instead; the nav is what gives up the room. */
@media (max-width: 720px) {
  .brand { font-size: 19px; letter-spacing: .1em; gap: 7px; }
  .brand img { width: 24px; height: 24px; }
  .topbar nav a:not(.topbar-cta) { font-size: 18px; }
  .topbar { gap: 10px; padding-inline: 12px; }
}
@media (max-width: 430px) {
  .brand { font-size: 16px; letter-spacing: .04em; }
  .brand img { width: 20px; height: 20px; }
  .topbar-cta { padding: 3px 10px; font-size: 17px; }
}
/* Six links do not fit a phone. Rather than collapse to nothing but the
   download button — which leaves a visitor no way to reach the playable bit
   without scrolling the whole page — keep the one link that is also an action. */
@media (max-width: 560px) {
  /* Down to the two actions: play it, or take it. */
  .topbar nav a:not(.topbar-cta):not(.topbar-play) { display: none; }
}
@media (max-width: 380px) {
  .topbar-play, .topbar-cta { padding: 3px 9px; font-size: 16px; }
}

/* ------------------------------------------------------------------ hero */
.hero {
  position: relative;
  min-height: min(100svh, 900px);
  display: grid; place-items: center;
  padding: 120px 0 150px;
  overflow: hidden;
}
.hero-sky { position: absolute; inset: 0; width: 100%; height: 100%; }
.hero-grad {
  position: absolute; inset: 0;
  background:
    radial-gradient(120% 75% at 50% 8%, rgba(255,140,40,.13), transparent 60%),
    linear-gradient(180deg, transparent 55%, var(--ink) 97%);
  pointer-events: none;
}
.hero-inner { position: relative; text-align: center; padding-inline: 20px; }

.emblem {
  width: clamp(84px, 13vw, 132px); height: auto;
  margin: 0 auto 14px;
  filter: drop-shadow(0 10px 26px rgba(255, 140, 40, .35));
}

.eyebrow {
  font-family: var(--display); font-size: 21px; letter-spacing: .34em;
  color: var(--dim); text-transform: uppercase; margin: 0 0 10px;
}

/* Per-letter spans so the title can flicker on like a bank of tubes coming up
   to voltage, one at a time. */
.title {
  font-family: var(--display);
  font-size: clamp(42px, 8.8vw, 118px);
  line-height: .95;
  /* Two spacings stack here and it is easy to over-do: `letter-spacing`
     applies between the per-letter spans *and* on either side of each word
     gap. Keeping the tracking small lets the word gap alone do the work. */
  letter-spacing: .012em;
  margin: 0;
  color: var(--amber);
  text-shadow: 0 0 24px rgba(255,140,40,.42), 0 4px 0 rgba(120,45,0,.55);
  cursor: pointer;
  user-select: none;
}
/* Words are the unit and letters live inside them. Every letter used to be a
   bare inline-block, so a narrow viewport was free to break between any two of
   them and MACHINES came apart mid-word. `nowrap` on the word leaves a space
   as the only legal break, which is where a reader expects one. */
.title .word { display: inline-block; white-space: nowrap; }
.title .word + .word { margin-left: .26em; }
.title .word span { display: inline-block; animation: flick .5s backwards; }
@keyframes flick {
  0%   { opacity: 0; transform: translateY(-6px); }
  40%  { opacity: 1; }
  55%  { opacity: .25; }
  70%  { opacity: 1; }
  85%  { opacity: .5; }
  100% { opacity: 1; transform: none; }
}
.title.shake { animation: shake .4s; }
@keyframes shake {
  25% { transform: translate(-4px, 2px) rotate(-.5deg); }
  50% { transform: translate(5px, -2px) rotate(.4deg); }
  75% { transform: translate(-3px, -1px); }
}

.tagline {
  font-family: var(--display);
  font-size: clamp(19px, 2.5vw, 29px);
  letter-spacing: .17em;
  color: var(--cyan);
  margin: 14px 0 0;
}
.lede {
  max-width: 62ch; margin: 20px auto 0;
  color: var(--text); opacity: .88;
}

.cta { display: flex; flex-wrap: wrap; gap: 14px; justify-content: center; margin-top: 32px; }
.hint { color: var(--dim); font-size: 14.5px; margin-top: 16px; }
.hint.centered, .sub.centered, .h2.centered { text-align: center; }

/* Sprites walking along the bottom edge of the hero, at different speeds and
   depths. Purely decorative, and the first thing that tells a visitor this is
   a game and not a landing page template. */
.hero-parade {
  position: absolute; left: 0; right: 0; bottom: 0; height: 175px;
  pointer-events: none;
}
/* Two things were wrong with this line of machines.

   `transform: scale()` works from the centre of the box, so shrinking a sprite
   lifted its feet by half the height it lost — the small ones were walking
   along in mid-air above the ones at full size. `transform-origin` at the
   bottom keeps every pair of feet on the same ground line.

   And they all crossed in the same time regardless of size, so the small ones
   read as small rather than as far away. Distance now sets the pace: the
   nearest cross in about 21 seconds, the furthest take more than twice that.
   Scale rides along inside the same transform as the travel, so the two cannot
   fight each other the way a `left` animation and a `transform` would. */
.hero-parade .sprite {
  position: absolute; left: -180px;
  transform-origin: 50% 100%;
  will-change: transform;
  animation-name: stride-mech, march;
  animation-duration: .66s, 24s;
  animation-timing-function: steps(4), linear;
  animation-iteration-count: infinite, infinite;
}
.hero-parade [class*="foe-"] { animation-name: stride-foe, march; }
/* A dozen of them now, spread across the loop with negative delays so the line
   is already full when the page opens rather than trickling in from the left.
   Scale and opacity stand in for depth: the small faint ones read as further
   back, which stops twelve identical-sized sprites looking like a conga line. */
/* Lane 1 — far. Small, faint, high, slow, and behind everything. */
.hero-parade .sprite:nth-child(-n+4) {
  --s: .56; bottom: 46px; z-index: 1; opacity: .38;
  animation-duration: .78s, 44s;
}
.hero-parade .sprite:nth-child(1) { animation-delay: 0s, 0s; }
.hero-parade .sprite:nth-child(2) { animation-delay: 0s, -11s; }
.hero-parade .sprite:nth-child(3) { animation-delay: 0s, -22s; }
.hero-parade .sprite:nth-child(4) { animation-delay: 0s, -33s; }

/* Lane 2 — middle. */
.hero-parade .sprite:nth-child(n+5):nth-child(-n+8) {
  --s: .76; bottom: 25px; z-index: 5; opacity: .62;
  animation-duration: .66s, 31s;
}
.hero-parade .sprite:nth-child(5) { animation-delay: 0s, 0s; }
.hero-parade .sprite:nth-child(6) { animation-delay: 0s, -7.75s; }
.hero-parade .sprite:nth-child(7) { animation-delay: 0s, -15.5s; }
.hero-parade .sprite:nth-child(8) { animation-delay: 0s, -23.25s; }

/* Lane 3 — near. Full size, and it passes in front of the other two. */
.hero-parade .sprite:nth-child(n+9) {
  --s: 1; bottom: 2px; z-index: 9; opacity: .95;
  animation-duration: .58s, 21s;
}
.hero-parade .sprite:nth-child(9)  { animation-delay: 0s, 0s; }
.hero-parade .sprite:nth-child(10) { animation-delay: 0s, -5.25s; }
.hero-parade .sprite:nth-child(11) { animation-delay: 0s, -10.5s; }
.hero-parade .sprite:nth-child(12) { animation-delay: 0s, -15.75s; }
@keyframes march {
  from { transform: translateX(0) scale(var(--s, 1)); }
  to   { transform: translateX(calc(100vw + 360px)) scale(var(--s, 1)); }
}

/* ---------------------------------------------------------------- buttons */
.btn {
  display: inline-flex; flex-direction: column; align-items: center;
  font-family: var(--display); font-size: 25px; letter-spacing: .13em;
  padding: 13px 30px; border-radius: var(--r);
  border: 2px solid var(--line-hot);
  background: var(--panel-2); color: var(--text);
  text-decoration: none; cursor: pointer;
  transition: transform .14s, border-color .2s, box-shadow .2s, background .2s;
}
.btn small {
  font-family: var(--body); font-size: 12px; letter-spacing: .06em;
  color: var(--dim); text-transform: none; margin-top: 2px;
}
.btn:hover {
  transform: translateY(-2px);
  border-color: var(--cyan);
  box-shadow: 0 0 0 1px rgba(95,216,255,.25), 0 10px 30px -12px rgba(95,216,255,.5);
}
.btn-primary {
  background: linear-gradient(180deg, var(--amber-2), var(--amber));
  border-color: #ffdb9a; color: #1a0d00;
}
.btn-primary small { color: rgba(26,13,0,.66); }
.btn-primary:hover {
  border-color: #fff2d6;
  box-shadow: 0 0 0 1px rgba(255,200,97,.4), 0 14px 34px -12px rgba(255,166,43,.75);
}
/* The browser build gets the cyan treatment the top bar uses, so the pair of
   hero buttons read as two different offers rather than one loud and one
   quiet. */
.btn-play {
  background: linear-gradient(180deg, #7ee2ff, var(--cyan));
  border-color: #b6efff; color: #04222d;
}
.btn-play small { color: rgba(4, 34, 45, .7); }
.btn-play:hover {
  border-color: #ddf7ff;
  box-shadow: 0 0 0 1px rgba(95, 216, 255, .45),
              0 14px 34px -12px rgba(95, 216, 255, .8);
}

.btn-xl { font-size: 32px; padding: 18px 46px; }

/* ------------------------------------------------------------------ bands */
.band { position: relative; padding: clamp(64px, 9vw, 110px) 0; }

/* The faint floor texture is the real tile out of the hive the section names,
   dropped to a few per cent so it reads as grain rather than wallpaper.
   The urls live here rather than in a --tile custom property set on the
   element: a url() inside a custom property is resolved against the sheet the
   declaration came from, so passing a document-relative path in from an inline
   style sent every one of these looking under /css/. */
.band-tile::before {
  content: ""; position: absolute; inset: 0;
  background-size: 320px;
  opacity: .055;
  pointer-events: none;
}
.story::before  { background-image: url("../assets/floors/ruins_floor.webp"); }
.shots::before  { background-image: url("../assets/floors/labs_floor.webp"); }
.breach::before { background-image: url("../assets/floors/orbit_floor.webp"); }
.echoes::before { background-image: url("../assets/floors/void_floor.webp"); }
.hives::before  { background-image: url("../assets/floors/throne_floor.webp"); }
.band > .wrap { position: relative; }

.h2 {
  font-family: var(--display); font-size: clamp(32px, 5vw, 52px);
  letter-spacing: .1em; color: var(--amber-2);
  margin: 0 0 10px; display: flex; align-items: center; gap: 14px;
}
.h2.centered { justify-content: center; }
.tick { width: 12px; height: 26px; background: var(--amber); flex: none;
        box-shadow: 0 0 14px rgba(255,166,43,.8); }
.h2.centered .tick { display: none; }
.sub { color: var(--dim); max-width: 72ch; margin: 0 0 40px; }

/* ------------------------------------------------------------------ story */
.panels { list-style: none; margin: 0; padding: 0; display: grid; gap: 22px; }
@media (min-width: 900px) {
  .panels { grid-template-columns: repeat(6, 1fr); }
  .panel:nth-child(1) { grid-column: span 3; }
  .panel:nth-child(2) { grid-column: span 3; }
  .panel:nth-child(3) { grid-column: span 2; }
  .panel:nth-child(4) { grid-column: span 2; }
  .panel:nth-child(5) { grid-column: span 2; }
}
/* The caption sits under the picture rather than on top of it.

   It used to be laid over the bottom of the frame behind a scrim dark enough
   to read white text against — which meant the bottom third of every one of
   these paintings was behind a black wash. They are the best art in the game
   and the words were burying them. Below the image nothing is covered, and the
   text is easier to read besides. */
.panel {
  display: flex; flex-direction: column;
  border: 1px solid var(--line); border-radius: var(--r);
  overflow: hidden; background: var(--panel);
}
.panel img { width: 100%; height: auto; object-fit: cover; aspect-ratio: 16/9; }
.panel p {
  margin: 0; padding: 14px 18px 16px; flex: 1;
  background: linear-gradient(180deg, #0d1320, var(--panel));
  font-size: 15.5px; line-height: 1.55;
}
.panel em { color: var(--cyan); font-style: normal; }
.panel b { color: var(--amber-2); }

/* ------------------------------------------------------------------ mechs */
.mech-grid {
  display: grid; gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(248px, 1fr));
}
.mech {
  border: 1px solid var(--line); border-radius: var(--r);
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  padding: 18px; text-align: center;
  transition: transform .18s, border-color .18s, box-shadow .18s;
}
.mech:hover {
  transform: translateY(-5px);
  border-color: var(--line-hot);
  box-shadow: 0 18px 44px -22px rgba(95,216,255,.55);
}
.mech-stage {
  height: 128px; display: grid; place-items: center; margin-bottom: 6px;
  background: radial-gradient(50% 62% at 50% 78%, rgba(95,216,255,.12), transparent 70%);
}
.mech h3 {
  font-family: var(--display); font-size: 30px; letter-spacing: .14em;
  color: var(--amber); margin: 0;
}
.blurb { font-size: 14.5px; color: var(--dim); margin: 6px 0 14px; min-height: 3.2em; }

/* The value used to sit inside the bar, where a high rating ran the fill
   straight over the top of it. It has its own column now. The raw figure keeps
   its place underneath, because a 0-100 rating on its own tells you how the
   mechs compare but never what the game actually gives you. */
.stats { margin: 0 0 14px; display: grid; gap: 10px; }
.stats > div {
  display: grid; grid-template-columns: 54px 1fr 30px;
  align-items: center; gap: 9px;
}
.stats dt { font-family: var(--display); font-size: 16px; letter-spacing: .1em;
            color: var(--dim); text-align: left; }
.stats dd { margin: 0; position: relative; height: 11px; background: #080d16;
            border: 1px solid var(--line); border-radius: 3px; overflow: hidden; }
.stats dd i { position: absolute; inset: 0 auto 0 0; width: var(--v);
              background: linear-gradient(90deg, var(--cyan), var(--green)); }
.stats > div > b { font-family: var(--display); font-size: 19px; line-height: 1;
                   font-weight: 400; color: var(--text); text-align: right; }
.stats .raw { grid-column: 2 / -1; font-size: 11.5px; color: var(--dim);
              letter-spacing: .03em; margin-top: -5px; text-align: left; }

.guns { list-style: none; margin: 0; padding: 0; display: grid; gap: 5px; }
.guns li {
  font-family: var(--display); font-size: 18px; letter-spacing: .07em;
  padding: 3px 8px; border: 1px solid var(--line); border-radius: 5px;
  background: #080d16; color: var(--text);
}
.guns .rare { color: var(--violet); border-color: #4a2f6e; background: #150c22; }

/* ----------------------------------------------------------------- breach */
.game-shell { border: 1px solid var(--line); border-radius: var(--r);
              background: var(--panel); padding: 14px; }
.game-hud {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin-bottom: 12px;
}
.hud-pill {
  font-family: var(--display); font-size: 19px; letter-spacing: .1em;
  padding: 3px 12px; border: 1px solid var(--line); border-radius: 6px;
  background: #080d16; display: inline-flex; align-items: center; gap: 8px;
}
.hud-hp { position: relative; overflow: hidden; min-width: 108px; }
.hud-hp i { position: absolute; inset: 0 auto 0 0; width: 100%;
            background: linear-gradient(90deg, rgba(255,107,107,.42), rgba(255,107,107,.16));
            transition: width .16s; }
.hud-hp b { position: relative; }
.hud-shard b { color: var(--shard); }
.hud-score b { color: var(--green); }
.hud-pill img { image-rendering: pixelated; }
.hud-btn {
  margin-left: auto; font-family: var(--display); font-size: 17px; letter-spacing: .1em;
  padding: 4px 12px; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--line); background: #080d16; color: var(--dim);
}
.hud-btn:hover { color: var(--cyan); border-color: var(--line-hot); }

.canvas-wrap { position: relative; border-radius: 8px; overflow: hidden; background: #070b12; }
/* The aspect ratio is set by the script, which picks a portrait board on a
   phone — the 16/9 here is only what shows before that runs. */
#breachCanvas { display: block; width: 100%; height: auto; aspect-ratio: 16/9;
                image-rendering: pixelated; touch-action: none; cursor: crosshair; }

/* The overlay carries a paragraph, four mech buttons and a start button, and
   on a phone that stack is taller than the board it sits on. Let it scroll
   rather than spill out over the section. */
@media (max-width: 560px) {
  .overlay { padding: 14px 12px; gap: 10px; overflow-y: auto;
             place-content: start center; align-content: center; }
  .overlay p { font-size: 13.5px; }
  .pick button { font-size: 17px; padding: 4px 11px; }
  .game-hud { gap: 7px; }
  .hud-pill { font-size: 17px; padding: 2px 9px; }
  .hud-btn { font-size: 15px; }
}

.overlay {
  position: absolute; inset: 0; display: grid; place-content: center;
  gap: 14px; text-align: center; padding: 24px;
  background: rgba(5,8,14,.9); backdrop-filter: blur(3px);
}
.overlay.hide { display: none; }
.overlay h3 {
  font-family: var(--display); font-size: clamp(30px, 5vw, 50px);
  letter-spacing: .16em; color: var(--amber); margin: 0;
}
.overlay p { margin: 0; color: var(--dim); max-width: 52ch; font-size: 15px; }
.overlay p b { color: var(--text); }
.overlay .btn { align-self: center; }
.pick { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.pick button {
  font-family: var(--display); font-size: 19px; letter-spacing: .1em;
  padding: 5px 15px; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--line); background: #080d16; color: var(--dim);
}
.pick button:hover { color: var(--text); border-color: var(--line-hot); }
.pick button.on { color: #1a0d00; background: var(--amber); border-color: #ffdb9a; }
.best { font-family: var(--display); font-size: 19px; letter-spacing: .1em;
        color: var(--shard); margin: 0; min-height: 1.4em; }
.game-foot { color: var(--dim); font-size: 14px; margin: 12px 0 0; text-align: center; }

/* ----------------------------------------------------------------- echoes */
.forge {
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); padding: clamp(16px, 3vw, 28px);
}
.gods {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
}
.gods button {
  display: grid; justify-items: center; gap: 6px; padding: 12px 6px;
  border: 1px solid var(--line); border-radius: 8px; cursor: pointer;
  background: #080d16; color: var(--dim);
  font-family: var(--display); font-size: 17px; letter-spacing: .1em;
  transition: transform .15s, border-color .15s, box-shadow .15s, color .15s;
}
.gods button img { image-rendering: pixelated; filter: saturate(.55) brightness(.8); transition: filter .15s; }
.gods button:hover { transform: translateY(-3px); border-color: var(--g); color: var(--text); }
.gods button:hover img { filter: none; }
.gods button.on {
  border-color: var(--g); color: var(--g);
  box-shadow: 0 0 0 1px var(--g), 0 0 26px -6px var(--g);
  background: #0d1420;
}
.gods button.on img { filter: none; }

.duo-out { margin-top: 20px; min-height: 108px; display: grid; place-items: center; }
.duo-idle { color: var(--dim); margin: 0; font-family: var(--display);
            font-size: 22px; letter-spacing: .12em; }
.duo-card {
  text-align: center; padding: 16px 22px; border-radius: 8px; width: 100%;
  border: 1px solid var(--line-hot);
  background: radial-gradient(70% 130% at 50% 0%, rgba(95,216,255,.13), transparent 70%), #0a1119;
  animation: pop .34s;
}
@keyframes pop { from { transform: scale(.93); opacity: 0; } }
.duo-card h3 {
  font-family: var(--display); font-size: clamp(28px, 4.4vw, 42px);
  letter-spacing: .16em; margin: 0; color: var(--amber);
  text-shadow: 0 0 22px rgba(255,166,43,.5);
}
.duo-card .pair { font-family: var(--display); font-size: 20px; letter-spacing: .12em;
                  color: var(--dim); margin: 2px 0 8px; }
.duo-card .eff { margin: 0; color: var(--cyan); font-size: 17px; }
.duo-card.none { border-color: var(--line); background: #0a0e16; }
.duo-card.none h3 { color: var(--dim); text-shadow: none; font-size: 26px; }
.duo-card.none .eff { color: var(--dim); }

.duo-count { text-align: center; color: var(--dim); font-family: var(--display);
             font-size: 20px; letter-spacing: .1em; margin: 14px 0 0; }
.duo-count b { color: var(--shard); }
.linkish { background: none; border: 0; color: var(--dim); cursor: pointer;
           font: inherit; text-decoration: underline; padding: 0 4px; }
.linkish:hover { color: var(--cyan); }

.rarities { list-style: none; display: flex; flex-wrap: wrap; gap: 10px 22px;
            justify-content: center; margin: 26px 0 0; padding: 0;
            font-family: var(--display); font-size: 19px; letter-spacing: .1em; color: var(--dim); }
.rarities li { display: flex; align-items: center; gap: 8px; }
.rarities i { width: 11px; height: 11px; border-radius: 2px; background: var(--c);
              box-shadow: 0 0 10px var(--c); }
.rarities b { color: var(--text); }

/* ------------------------------------------------------------------ hives */
.warden-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.warden {
  display: grid; grid-template-columns: 54px 158px 1fr; align-items: center; gap: 14px;
  border: 1px solid var(--line); border-left: 3px solid var(--line-hot);
  border-radius: var(--r); background: var(--panel); padding: 10px 18px;
  transition: border-color .18s, background .18s, transform .18s;
}
.warden:hover { transform: translateX(4px); border-left-color: var(--amber); background: var(--panel-2); }
.warden.last { border-left-color: var(--red); }
.warden.last:hover { border-left-color: var(--red); box-shadow: 0 0 34px -14px var(--red); }
.no { font-family: var(--display); font-size: 34px; color: var(--line-hot); letter-spacing: .06em; }
.warden:hover .no { color: var(--amber); }
/* The boss sprites are 152px and were shrunk with a transform, which leaves the
   layout box at full size — so the element overflowed its 118px cell and the
   art sat off centre inside the tile. Sizing the background instead keeps the
   box and the picture the same object. */
.wsprite { display: grid; place-items: center; height: 124px; }
.wsprite .sprite {
  width: 118px; height: 118px; background-size: 472px 118px;
  animation-name: stride-boss-sm;
}
@keyframes stride-boss-sm { to { background-position-x: -472px; } }
.wtext h3 { font-family: var(--display); font-size: 27px; letter-spacing: .12em;
            margin: 0; color: var(--text); }
.wtext h3 em { color: var(--dim); font-style: normal; font-size: 20px; letter-spacing: .08em; }
.wtext p { margin: 2px 0 0; color: var(--cyan); font-size: 15.5px; }
@media (max-width: 700px) {
  .warden { grid-template-columns: 40px 1fr; padding: 12px 14px; }
  /* Match the sprite's own 118px. A 96px cell cropped it and pushed the art
     off centre in exactly the way this section was reported for. */
  .wsprite { grid-row: 2; grid-column: 1 / -1; height: 124px; }
  .wtext { grid-column: 2; }
}

/* --------------------------------------------------------------- features */
.feat-grid {
  display: grid; gap: 14px; margin-bottom: 32px;
  grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
}
.feat {
  border: 1px solid var(--line); border-radius: var(--r); background: var(--panel);
  padding: 18px 14px; text-align: center;
}
.feat b { display: block; font-family: var(--display); font-size: 46px;
          line-height: 1; color: var(--amber); }
.feat span { display: block; font-size: 14px; color: var(--dim); margin-top: 6px; }

.bullets { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.bullets li { position: relative; padding-left: 26px; }
.bullets li::before {
  content: ""; position: absolute; left: 4px; top: .62em;
  width: 9px; height: 9px; background: var(--cyan); border-radius: 2px;
  box-shadow: 0 0 10px var(--cyan);
}
.bullets b { color: var(--amber-2); }

/* -------------------------------------------------------------------- get */
.get {
  background:
    radial-gradient(70% 110% at 50% 0%, rgba(255,166,43,.11), transparent 65%),
    var(--ink);
  border-top: 1px solid var(--line);
}
.get-inner { display: grid; justify-items: center; gap: 18px; text-align: center; }
.specs {
  display: grid; gap: 12px; margin-top: 18px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  width: min(760px, 100%);
}
.specs > div { border: 1px solid var(--line); border-radius: 8px;
               background: var(--panel); padding: 12px; }
.specs dt { font-family: var(--display); font-size: 16px; letter-spacing: .12em; color: var(--dim); }
.specs dd { margin: 2px 0 0; font-size: 15px; }

/* ------------------------------------------------------------------- foot */
.foot { border-top: 1px solid var(--line); padding: 34px 0; background: #03050a; }
.foot-inner { display: grid; gap: 4px; justify-items: center; text-align: center;
              color: var(--dim); font-size: 14px; }
.foot-logo { width: min(430px, 78vw); height: auto; margin-bottom: 10px; opacity: .92; }
.foot b { color: var(--text); }
.foot a { color: var(--dim); }
.foot a:hover { color: var(--cyan); }

/* ---------------------------------------------------------------- easter */
/* Clicking the title dumps a squad of machines across the viewport. */
.rogue {
  position: fixed; bottom: 0; z-index: 60; pointer-events: none;
  animation-name: stride-foe, escape;
  animation-duration: .5s, 3.4s;
  animation-timing-function: steps(4), linear;
  animation-iteration-count: infinite, 1;
  animation-fill-mode: none, forwards;
}
@keyframes escape { from { left: -120px; } to { left: 105%; } }

/* ------------------------------------------------------------- motion off */
@media (prefers-reduced-motion: reduce) {
  .hero-parade, .rogue { display: none; }
  .reveal { opacity: 1; transform: none; transition: none; }
  .shot:hover img, .gun:hover, .shot:hover { transform: none; }
  .lightbox { animation: none; }
  .sprite { animation: none !important; }
  .title span { animation: none; }
  .btn:hover, .mech:hover, .warden:hover { transform: none; }
  .duo-card { animation: none; }
}


/* -------------------------------------------------------------- reveal */
/* Sections rise in as they arrive. Held to a small translate and a short
   duration: a landing page that makes you wait for its own content is a
   landing page nobody scrolls twice. Anything without JS stays visible,
   because `.in` is added by script and the base state is only dimmed. */
/* The hidden state is opt-in, applied by script. If JS never runs, or throws
   before this point, every `.reveal` block is simply visible — the opposite
   arrangement hides half the page behind a bug. */
.js-reveal .reveal {
  opacity: 0; transform: translateY(16px);
  transition: opacity .5s ease, transform .5s ease;
}
.js-reveal .reveal.in { opacity: 1; transform: none; }

/* The rescue state. Removing `js-reveal` alone leaves each element to travel
   from 0 to 1 through a transition, and a transition needs frames — in a tab
   that never composites, they simply sit at 0 forever. This snaps them on with
   no animation to depend on. */
.reveal-off .reveal {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}

/* ------------------------------------------------------------ scanline */
/* Two pixels of very dark line every four, over the hero only. At 3% it is
   not visible as stripes — it just stops the sky reading like a stock
   gradient and gives the whole panel a faint CRT grain. */
.hero::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(
    180deg, rgba(0,0,0,.30) 0 1px, transparent 1px 4px);
  opacity: .5;
  mix-blend-mode: multiply;
}

/* --------------------------------------------------------- nav current */
.topbar nav a.here { color: var(--amber-2); }
.topbar nav a.here::after {
  content: ""; display: block; height: 2px; margin-top: -2px;
  background: var(--amber); box-shadow: 0 0 8px var(--amber);
}

/* ---------------------------------------------------------- gallery -- */
.shot-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
@media (min-width: 900px) { .shot-grid { grid-template-columns: repeat(4, 1fr); } }
.shot { margin: 0; display: flex; flex-direction: column;
        border: 1px solid var(--line);
        border-radius: var(--r); overflow: hidden; background: #060a11;
        cursor: zoom-in; transition: transform .2s, border-color .2s, box-shadow .2s; }
@media (min-width: 900px) { .shot { grid-column: span 2; } .shot.big { grid-column: span 4; } }
.shot img { width: 100%; height: auto; object-fit: cover; aspect-ratio: 16/9;
            transition: transform .5s; }
.shot:hover { border-color: var(--line-hot); transform: translateY(-3px);
              box-shadow: 0 22px 48px -26px rgba(95,216,255,.6); }
.shot:hover img { transform: scale(1.02); }
.shot figcaption {
  padding: 11px 15px 13px; flex: 1;
  background: linear-gradient(180deg, #0d1320, var(--panel));
  font-size: 14px; line-height: 1.5; color: var(--text);
}
.shot.big figcaption { font-size: 15.5px; }

/* --------------------------------------------------------- lightbox -- */
.lightbox {
  position: fixed; inset: 0; z-index: 90; display: grid;
  place-content: center; gap: 14px; padding: 4vmin;
  background: rgba(3,5,10,.95); backdrop-filter: blur(6px);
  animation: fade .18s;
}
.lightbox[hidden] { display: none; }
@keyframes fade { from { opacity: 0; } }
.lightbox img { max-width: min(1500px, 92vw); max-height: 82vh;
                border: 1px solid var(--line-hot); border-radius: var(--r); }
.lightbox p { margin: 0; text-align: center; color: var(--dim); font-size: 15px; }
.lb-close {
  position: absolute; top: 14px; right: 18px;
  font: 400 40px/1 var(--display); color: var(--dim);
  background: none; border: 0; cursor: pointer; padding: 6px 14px;
}
.lb-close:hover { color: var(--amber); }

/* ---------------------------------------------------------- arsenal -- */
.ars-row {
  display: grid; grid-template-columns: 190px 1fr; gap: 20px;
  align-items: start; padding: 20px 0;
  border-top: 1px solid var(--line);
}
.ars-row:first-of-type { border-top: 0; }
.ars-who { display: grid; justify-items: center; gap: 2px; position: sticky; top: 92px; }
.ars-who h3 {
  font-family: var(--display); font-size: 30px; letter-spacing: .14em;
  margin: 0; color: var(--w); text-shadow: 0 0 20px color-mix(in srgb, var(--w) 45%, transparent);
}
.guns-grid {
  list-style: none; margin: 0; padding: 0; display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(238px, 1fr));
}
.gun {
  display: grid; grid-template-columns: 64px 1fr; gap: 12px; align-items: center;
  padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--r);
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  transition: transform .16s, border-color .16s, box-shadow .16s;
}
.gun:hover {
  transform: translateY(-3px); border-color: var(--w);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--w) 45%, transparent),
              0 16px 36px -22px var(--w);
}
.gun img { image-rendering: pixelated; filter: saturate(.75) brightness(.92); transition: filter .16s; }
.gun:hover img { filter: none; }
.gun h4 { margin: 0; font-family: var(--display); font-size: 22px;
          letter-spacing: .09em; color: var(--w); }
.gun h4 em {
  font-style: normal; font-size: 14px; letter-spacing: .12em;
  color: var(--violet); border: 1px solid #4a2f6e; border-radius: 4px;
  padding: 0 5px; margin-left: 6px; vertical-align: 2px;
}
.gun-kind { margin: 0; font-family: var(--display); font-size: 15px;
            letter-spacing: .16em; color: var(--dim); text-transform: uppercase; }
.gun-stats { margin: 5px 0 0; display: grid; gap: 5px; }
.gun-stats > div {
  display: grid; grid-template-columns: 38px 1fr 26px;
  align-items: center; gap: 8px;
}
.gun-stats dt { font-family: var(--display); font-size: 14px; letter-spacing: .1em;
                color: var(--dim); }
.gun-stats dd { margin: 0; position: relative; height: 9px; background: #080d16;
                border: 1px solid var(--line); border-radius: 3px; overflow: hidden; }
.gun-stats dd i { position: absolute; inset: 0 auto 0 0; width: var(--v);
                  background: var(--w); opacity: .85; }
.gun-stats > div > b { font-family: var(--display); font-size: 16px; line-height: 1;
                       font-weight: 400; color: var(--w); text-align: right; }
.gun-note { margin: 6px 0 0; font-size: 12.5px; color: var(--dim); }
.scale-note { margin-top: -24px; font-size: 14px; }
.scale-note b { color: var(--text); }
.gun.rare { background: linear-gradient(180deg, #17102a, var(--panel)); }

@media (max-width: 760px) {
  .ars-row { grid-template-columns: 1fr; gap: 10px; }
  .ars-who { position: static; grid-auto-flow: column; justify-content: start;
             align-items: center; gap: 12px; }
  .ars-who .sprite { transform: scale(.7); margin: -16px; }
}
