/* ==========================================================================
   Void Defenders — site stylesheet
   Colours are lifted directly from the game (lib/src/ui/widgets/store_widgets.dart
   and the rarity scale), so the pages sit in the same world as the app.
   Self-contained: no frameworks, no webfonts, no external requests.
   ========================================================================== */

:root {
  --vd-bg: #05070d;          /* the game's background */
  --vd-panel: #0b0f1a;       /* card surface */
  --vd-panel-2: #101623;     /* raised surface */
  --vd-line: #1b2434;        /* hairline borders */

  --vd-cyan: #5ec8ff;        /* primary accent */
  --vd-gold: #ffc24a;        /* legendary / highlight */
  --vd-green: #6fe39a;       /* defense / success */
  --vd-violet: #b98cff;      /* ability */
  --vd-danger: #ff5470;      /* risk / warning */

  --vd-text: #ffffff;
  --vd-text-dim: #b3becd;
  --vd-text-faint: #6c7686;

  --vd-radius: 14px;
  --vd-radius-lg: 20px;
  --vd-max: 1120px;
  --vd-gutter: 20px;
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--vd-bg);
  color: var(--vd-text-dim);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }

a { color: var(--vd-cyan); text-decoration: none; }
a:hover { text-decoration: underline; }

/* --------------------------------------------------------------- layout */
.vd-wrap {
  max-width: var(--vd-max);
  margin: 0 auto;
  padding: 0 var(--vd-gutter);
}

.vd-section { padding: 64px 0; }
.vd-section--tight { padding: 40px 0; }

/* ------------------------------------------------------------- typography */
h1, h2, h3, h4 {
  color: var(--vd-text);
  line-height: 1.2;
  margin: 0 0 14px;
  font-weight: 900;
}

h1 { font-size: clamp(34px, 7vw, 60px); letter-spacing: -0.5px; }
h2 { font-size: clamp(24px, 4vw, 34px); }
h3 { font-size: 19px; }
h4 { font-size: 15px; letter-spacing: 1px; text-transform: uppercase; }

p { margin: 0 0 16px; }

.vd-eyebrow {
  color: var(--vd-cyan);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 3px;
  text-transform: uppercase;
  margin-bottom: 10px;
}

.vd-lede { font-size: 18px; color: var(--vd-text-dim); max-width: 60ch; }
.vd-muted { color: var(--vd-text-faint); font-size: 14px; }

/* ------------------------------------------------------------------ header */
.vd-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(5, 7, 13, 0.92);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--vd-line);
}

.vd-header__inner {
  max-width: var(--vd-max);
  margin: 0 auto;
  padding: 14px var(--vd-gutter);
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.vd-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--vd-text);
  font-weight: 900;
  letter-spacing: 2px;
  font-size: 14px;
  text-transform: uppercase;
}
.vd-brand:hover { text-decoration: none; }

.vd-nav {
  margin-left: auto;
  display: flex;
  gap: 20px;
  align-items: center;
  flex-wrap: wrap;
}

.vd-nav a {
  color: var(--vd-text-dim);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.6px;
}
.vd-nav a:hover { color: var(--vd-cyan); text-decoration: none; }
.vd-nav a.is-active { color: var(--vd-cyan); }

/* ------------------------------------------------------------------- hero */
.vd-hero {
  position: relative;
  padding: 72px 0 56px;
  overflow: hidden;
  border-bottom: 1px solid var(--vd-line);
}

/* the radar glow from the game's home screen */
.vd-hero::before {
  content: "";
  position: absolute;
  top: -220px;
  left: 50%;
  width: 760px;
  height: 760px;
  transform: translateX(-50%);
  background: radial-gradient(circle, rgba(94, 200, 255, 0.16) 0%,
                                      rgba(94, 200, 255, 0) 62%);
  pointer-events: none;
}

.vd-hero__inner { position: relative; display: grid; gap: 40px; align-items: center; }

@media (min-width: 880px) {
  .vd-hero__inner { grid-template-columns: 1.15fr 0.85fr; }
}

/* ----------------------------------------------------------- the device
   A screenshot in a phone, rather than a screenshot with rounded corners.
   The flat image read as a picture of a game; a device reads as a thing you
   can hold, which is what a store page is for.

   Built in CSS rather than shipped as a rendered mockup for three reasons:
   the screenshot inside it changes every time the game does and a baked
   image would go stale silently, it costs no download, and it stays sharp
   on any display. Everything here is layers on one element —
   `.vd-device` is the titanium body, `::before` is the screen bezel and
   `::after` the glass reflection.

   The 3D is a perspective rotation rather than a pre-drawn angle, so the
   band of light across the glass is on the same axis as the rotation and
   the whole thing holds together. It straightens up on hover, on focus and
   for anyone who asked for less motion. */
.vd-device {
  --vd-device-tilt: -14deg;
  --vd-device-radius: 46px;
  justify-self: center;
  position: relative;
  width: min(300px, 78vw);
  /* The capture's own ratio — iPhone 17e, 390x844 points. Matching it means
     object-fit has nothing to crop, so the screenshot is shown whole. */
  aspect-ratio: 780 / 1688;
  padding: 10px;
  border-radius: var(--vd-device-radius);
  /* Brushed titanium: a hard specular on each long edge, a softer one on
     the ends, over a dark body. Two stops close together make an edge read
     as machined; a smooth gradient reads as plastic. */
  background:
    linear-gradient(100deg,
      #5d6472 0%, #9aa3b4 3%, #6e7686 7%,
      #2b313d 22%, #222731 50%, #2b313d 78%,
      #6e7686 93%, #9aa3b4 97%, #5d6472 100%);
  transform: perspective(1600px) rotateY(var(--vd-device-tilt)) rotateX(3deg);
  transform-style: preserve-3d;
  transition: transform 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
  box-shadow:
    /* the body's own thickness, thrown opposite the tilt */
    22px 26px 0 -14px rgba(0, 0, 0, 0.45),
    0 40px 90px rgba(0, 0, 0, 0.7),
    0 0 70px rgba(94, 200, 255, 0.10);
}
.vd-device:hover,
.vd-device:focus-within { --vd-device-tilt: -4deg; }

/* The screen surround. Inset so a hairline of the body shows all round,
   which is what stops the glass looking painted on. */
.vd-device::before {
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: calc(var(--vd-device-radius) - 4px);
  background: #05070d;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
  pointer-events: none;
}

/* Glass. A single soft band across the panel on the same axis as the
   rotation, plus a cool wash at the top edge. Kept under 10% so it never
   fights the screenshot underneath it. */
.vd-device::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--vd-device-radius);
  background:
    linear-gradient(104deg,
      rgba(255, 255, 255, 0.00) 34%,
      rgba(255, 255, 255, 0.07) 44%,
      rgba(255, 255, 255, 0.09) 48%,
      rgba(255, 255, 255, 0.00) 58%),
    linear-gradient(185deg,
      rgba(158, 206, 255, 0.10) 0%,
      rgba(158, 206, 255, 0.00) 26%);
  mix-blend-mode: screen;
  pointer-events: none;
  z-index: 3;
}

.vd-device__screen {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  border-radius: calc(var(--vd-device-radius) - 10px);
  background: var(--vd-bg);
}

/* The Dynamic Island. A real pill on the glass, not part of the capture —
   the screenshots are taken without one, and drawing it here means the
   device stays right when the screenshot is retaken. */
.vd-device__island {
  position: absolute;
  z-index: 2;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  width: 29%;
  height: 3.0%;
  min-height: 18px;
  border-radius: 999px;
  background: #000;
  box-shadow: inset 0 -1px 2px rgba(255, 255, 255, 0.10);
  pointer-events: none;
}

/* Side buttons, drawn on the body's edge. They sit slightly outside the
   padding so they read as raised rather than printed. */
.vd-device__btn {
  position: absolute;
  z-index: 0;
  width: 3px;
  border-radius: 2px;
  background: linear-gradient(90deg, #39404e, #767f90 60%, #2a2f3a);
  pointer-events: none;
}
.vd-device__btn--power  { right: -3px; top: 25%;   height: 11%; }
.vd-device__btn--vol-up { left:  -3px; top: 19.5%; height: 7%; }
.vd-device__btn--vol-dn { left:  -3px; top: 28.5%; height: 7%; }
.vd-device__btn--action { left:  -3px; top: 13%;   height: 4.5%; }

/* A device is decorative scaffolding around a screenshot; on a narrow
   screen the screenshot is the only part worth the pixels. The frame stays,
   the tilt goes — a rotated phone on a 380px screen loses real width to the
   perspective and gains nothing. */
@media (max-width: 720px) {
  .vd-device { --vd-device-tilt: 0deg; box-shadow: 0 24px 60px rgba(0,0,0,0.7); }
}

@media (prefers-reduced-motion: reduce) {
  .vd-device { --vd-device-tilt: 0deg; transition: none; }
}

/* Kept: other pages still use the plain framing, and a screenshot that is
   not the hero does not need a phone around it. */
.vd-hero__shot {
  justify-self: center;
  max-width: 300px;
  border-radius: 28px;
  border: 1px solid var(--vd-line);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.6),
              0 0 60px rgba(94, 200, 255, 0.12);
}

/* ---------------------------------------------------------------- buttons */
.vd-btn {
  display: inline-block;
  padding: 14px 28px;
  border-radius: 12px;
  font-weight: 900;
  font-size: 14px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  border: 1px solid transparent;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.vd-btn:hover { text-decoration: none; transform: translateY(-1px); }

/* The App Store button.
   Built on the same .vd-btn as the rest rather than an Apple badge image: the
   badge is a black-and-white asset with its own typography and it looks
   borrowed on this palette, where everything else is cyan on near-black. The
   parent site uses a plain text link for the same reason.

   The pre-release state is a <span>, not a disabled <a>. There is no URL to
   point at until the app is approved — apps.apple.com 404s for an unreleased
   id — and an anchor with no href is a link that does nothing, which is worse
   than something that plainly says it is not ready yet. */
.vd-btn--store {
  background: linear-gradient(180deg, #223049 0%, #1a2436 100%);
  border-color: var(--vd-line);
  color: var(--vd-text-dim);
  cursor: default;
}

a.vd-btn--store {
  /* Once it has an href it becomes the loudest thing on the page, which is
     the point: by then it is the only instruction that matters. */
  background: var(--vd-cyan);
  border-color: var(--vd-cyan);
  color: #05070d;
  cursor: pointer;
}

a.vd-btn--store:hover {
  box-shadow: 0 8px 26px rgba(94, 200, 255, 0.3);
}

.vd-btn--primary {
  background: var(--vd-cyan);
  color: var(--vd-bg);
  box-shadow: 0 8px 30px rgba(94, 200, 255, 0.28);
}
.vd-btn--primary:hover { box-shadow: 0 10px 38px rgba(94, 200, 255, 0.4); }

.vd-btn--ghost {
  background: transparent;
  color: var(--vd-text);
  border-color: var(--vd-line);
}
.vd-btn--ghost:hover { border-color: var(--vd-cyan); color: var(--vd-cyan); }

.vd-btns { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 26px; }

/* ------------------------------------------------------------------ cards */
.vd-grid { display: grid; gap: 16px; }
.vd-grid--2 { grid-template-columns: 1fr; }
.vd-grid--3 { grid-template-columns: 1fr; }
.vd-grid--4 { grid-template-columns: repeat(2, 1fr); }

@media (min-width: 680px) {
  .vd-grid--2 { grid-template-columns: repeat(2, 1fr); }
  .vd-grid--3 { grid-template-columns: repeat(3, 1fr); }
  .vd-grid--4 { grid-template-columns: repeat(4, 1fr); }
}

.vd-card {
  background: var(--vd-panel);
  border: 1px solid var(--vd-line);
  border-radius: var(--vd-radius);
  padding: 22px;
}

.vd-card h3 { margin-bottom: 8px; }
.vd-card p:last-child { margin-bottom: 0; }

/* ---------------------------------------------------------------- hostiles */
.vd-hostiles { display: grid; gap: 14px; grid-template-columns: repeat(2, 1fr); }

@media (min-width: 640px) { .vd-hostiles { grid-template-columns: repeat(4, 1fr); } }

.vd-hostile {
  background: var(--vd-panel);
  border: 1px solid var(--vd-line);
  border-radius: var(--vd-radius);
  padding: 18px 14px;
  text-align: center;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.vd-hostile:hover { transform: translateY(-3px); }

.vd-hostile img {
  width: 76px;
  height: 76px;
  margin: 0 auto 12px;
  image-rendering: -webkit-optimize-contrast;
}

.vd-hostile__name {
  color: var(--vd-text);
  font-weight: 900;
  font-size: 13px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
}

.vd-hostile__wave {
  color: var(--vd-text-faint);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1px;
  margin: 4px 0 10px;
}

.vd-hostile__counter {
  font-size: 12px;
  line-height: 1.45;
  color: var(--vd-text-dim);
  min-height: 52px;
}

/* per-kind accents, matching the in-game art */
.vd-hostile--stalker  { border-color: rgba(224, 138, 46, 0.45); }
.vd-hostile--brute    { border-color: rgba(234, 79, 255, 0.45); }
.vd-hostile--wraith   { border-color: rgba(255, 92, 122, 0.45); }
.vd-hostile--sentinel { border-color: rgba(46, 196, 224, 0.45); }
.vd-hostile--reaper   { border-color: rgba(124, 255, 107, 0.45); }
.vd-hostile--colossus { border-color: rgba(255, 159, 59, 0.45); }
.vd-hostile--swarmer  { border-color: rgba(191, 255, 59, 0.45); }
.vd-hostile--phantom  { border-color: rgba(191, 232, 255, 0.45); }
/* one per later sector, each built around that sector's rule */
.vd-hostile--lurker    { border-color: rgba(138, 90, 60, 0.55); }
.vd-hostile--entropy   { border-color: rgba(122, 90, 168, 0.55); }
.vd-hostile--shrike    { border-color: rgba(74, 138, 207, 0.55); }
.vd-hostile--echo      { border-color: rgba(106, 90, 207, 0.55); }
.vd-hostile--shroud    { border-color: rgba(192, 200, 255, 0.45); }
.vd-hostile--reckoning { border-color: rgba(207, 74, 90, 0.55); }

/* Which sector a kind belongs to. Only the later ones carry it — the first
   eight are Nova Surge's whole vocabulary and saying so fourteen times is
   noise. */
.vd-hostile__sector {
  display: inline-block;
  font-size: 9px;
  font-weight: 900;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--vd-text-faint);
  border: 1px solid var(--vd-line);
  border-radius: 999px;
  padding: 3px 9px;
  margin-bottom: 10px;
}

/* ------------------------------------------------------------------ bosses */
/* Wider cards than the hostiles get: a boss fills a good part of the screen in
   play, and judging one at thumbnail size judges the wrong thing. */
.vd-bosses { display: grid; gap: 16px; grid-template-columns: 1fr; }

@media (min-width: 560px) { .vd-bosses { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 960px) { .vd-bosses { grid-template-columns: repeat(3, 1fr); } }

.vd-boss {
  background: var(--vd-panel);
  border: 1px solid var(--vd-line);
  border-radius: var(--vd-radius);
  padding: 16px 16px 18px;
  text-align: center;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.vd-boss:hover { transform: translateY(-3px); }

.vd-boss img {
  width: 100%;
  max-width: 230px;
  height: auto;
  margin: 0 auto 10px;
  display: block;
}

.vd-boss__wave {
  color: var(--vd-text-faint);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1.2px;
  text-transform: uppercase;
}

.vd-boss__name {
  color: var(--vd-text);
  font-weight: 900;
  font-size: 15px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  margin: 6px 0 6px;
}

/* How it actually fights, and what answers it. */
.vd-boss__brief {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--vd-text-dim);
  margin: 10px 0 0;
  text-align: left;
}

/* The line the game itself puts under the name on the incoming banner. */
.vd-boss__epithet {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--vd-text-faint);
  font-style: italic;
  margin: 0;
}

.vd-boss--harbinger { border-color: rgba(255, 107, 74, 0.45); }
.vd-boss--lancet    { border-color: rgba(111, 226, 140, 0.45); }
.vd-boss--bulwark   { border-color: rgba(185, 140, 255, 0.45); }
.vd-boss--quasar    { border-color: rgba(140, 216, 255, 0.45); }
.vd-boss--maelstrom { border-color: rgba(255, 194, 74, 0.45); }
.vd-boss--warden    { border-color: rgba(255, 84, 112, 0.55); }
.vd-boss--carrion   { border-color: rgba(216, 203, 168, 0.50); }
.vd-boss--siphon    { border-color: rgba(53, 214, 232, 0.50); }
.vd-boss--maw       { border-color: rgba(108, 92, 224, 0.55); }
.vd-boss--vesper    { border-color: rgba(127, 240, 200, 0.50); }
.vd-boss--spindle   { border-color: rgba(159, 212, 255, 0.50); }
.vd-boss--thresher  { border-color: rgba(79, 224, 138, 0.50); }
.vd-boss--conclave  { border-color: rgba(224, 72, 95, 0.50); }
.vd-boss--pyre      { border-color: rgba(255, 179, 71, 0.50); }
.vd-boss--nocturne  { border-color: rgba(142, 123, 216, 0.50); }
.vd-boss--umbra     { border-color: rgba(59, 74, 122, 0.60); }

/* ------------------------------------------------------------- screenshots */
.vd-shots {
  display: grid;
  gap: 18px;
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 900px) { .vd-shots { grid-template-columns: repeat(4, 1fr); } }

/* The six sector arrivals. Three across rather than four, so the set divides
   evenly into two rows instead of leaving a widowed pair on the second. */
.vd-arrivals {
  display: grid;
  gap: 18px;
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 760px) { .vd-arrivals { grid-template-columns: repeat(3, 1fr); } }

/* --------------------------------------------------------------- lightbox */
/* A sector reel over the page it was opened from.
   A second page was the first attempt and it is the wrong answer on a phone:
   you lose your place, and coming back is a navigation rather than a
   dismissal. Native <dialog> so Esc, the backdrop and focus containment are
   the platform's problem, not ours — and so the whole thing degrades to the
   plain link it still is when the element is unsupported. */
.vd-lightbox {
  border: 0;
  padding: 0;
  background: transparent;
  max-width: 100vw;
  max-height: 100dvh;
  overflow: visible;
}

.vd-lightbox::backdrop { background: rgba(3, 5, 10, 0.88); }

.vd-lightbox__inner {
  position: relative;
  /* Tall enough to read, never taller than the window. The clip's own aspect
     does the rest — see .vd-reel video. */
  max-height: 82dvh;
  display: flex;
}

.vd-lightbox video {
  max-height: 82dvh;
  width: auto;
  max-width: 92vw;
  display: block;
  border-radius: 16px;
  border: 1px solid var(--vd-line);
  background: #05070D;
}

.vd-lightbox__close {
  position: absolute;
  top: -46px;
  right: 0;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.35);
  background: rgba(5, 7, 13, 0.7);
  color: #fff;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}

.vd-lightbox__caption {
  margin: 12px 0 0;
  text-align: center;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--vd-text-faint);
}

/* ------------------------------------------------------- the finished state */
/* Shown after three passes. The clip holds its last frame rather than
   rewinding, so this sits over a still picture of where the run got to. */
.vd-reel__replay,
.vd-lightbox__replay {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  padding: 10px 18px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.8);
  background: rgba(5, 7, 13, 0.72);
  color: #fff;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  cursor: pointer;
  display: none;
}

[data-reel-wrap].is-finished .vd-reel__replay,
[data-reel-wrap].is-finished .vd-lightbox__replay { display: block; }

/* The wrap has to be a positioning context for the button above it. */
.vd-reel, .vd-lightbox__inner { position: relative; }

/* -------------------------------------------------------------- play link */
/* A sector still that launches its own reel.
   The stills were plain images and nothing said otherwise, so the only route
   to the footage was noticing "Watch" in the nav. A picture of the game is the
   obvious thing to click, and it was inert.

   The badge is drawn rather than an asset: one glyph, so it cannot fail to
   load, cannot be the wrong colour in dark mode, and costs no request. */
.vd-playlink {
  position: relative;
  display: block;
  border-radius: 18px;
  /* The image already carries the radius; this clips the badge's glow to it. */
  overflow: hidden;
  text-decoration: none;
  outline-offset: 3px;
}

.vd-playlink img { display: block; margin-bottom: 0; }

.vd-playlink__badge {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  /* The wash is what makes the disc readable on a bright corona as well as a
     near-black eclipse; the disc is what makes it read as a button rather
     than a stray glyph sitting on the artwork. A bare triangle was tried and
     disappeared into the art. */
  background: radial-gradient(circle at 50% 50%,
              rgba(5, 7, 13, 0.5) 0%, rgba(5, 7, 13, 0.1) 60%,
              rgba(5, 7, 13, 0) 78%);
  opacity: 0.95;
  transition: opacity 140ms ease, transform 140ms ease;
}

.vd-playlink__badge::before {
  content: '';
  position: absolute;
  width: 58px;
  height: 58px;
  border-radius: 50%;
  background: rgba(5, 7, 13, 0.55);
  border: 2px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 4px 22px rgba(0, 0, 0, 0.6);
}

.vd-playlink__badge {
  font-size: 22px;
  line-height: 1;
  color: #fff;
  /* Nudged right of centre: a triangle's visual middle sits left of its
     bounding box, so centring the box leaves it looking off inside the disc. */
  text-indent: 4px;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.8);
}

/* The glyph is wrapped in an element on purpose. ::before paints the disc and
   a bare text node cannot be lifted above it — no position, no z-index on a
   text node — so the triangle rendered behind the circle and the badge came
   out as six empty rings. */
.vd-playlink__badge > i {
  position: relative;
  z-index: 1;
  font-style: normal;
}

.vd-playlink:hover .vd-playlink__badge,
.vd-playlink:focus-visible .vd-playlink__badge {
  opacity: 1;
  transform: scale(1.08);
}

@media (prefers-reduced-motion: reduce) {
  .vd-playlink__badge { transition: none; }
  .vd-playlink:hover .vd-playlink__badge { transform: none; }
}

/* ------------------------------------------------------------------ reels */
/* Portrait gameplay capture, shown portrait.
   The game is locked to portrait, so a 16:9 frame would crop away the two
   things that matter: the outpost line along the bottom and what is coming
   down at it.

   Three across on a wide screen, and the reason is the opposite of the usual
   one: with a fixed portrait aspect, a NARROWER column is a SHORTER tile. Two
   across on a 1280 page gives 530-wide tiles 1150px tall; three gives 345-wide
   tiles at 748. More columns is less scrolling here, not more. */
.vd-reels {
  display: grid;
  gap: 22px;
  grid-template-columns: 1fr;
  margin-top: 26px;
}
@media (min-width: 640px) { .vd-reels { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .vd-reels { grid-template-columns: repeat(3, 1fr); } }

.vd-reel {
  margin: 0;
  /* Clears the sticky header when the browser jumps to #<sector> from a tile
     on the landing page. Native fragment navigation does the scrolling; this
     is the only part of it worth overriding. */
  scroll-margin-top: 90px;
}

.vd-reel video {
  width: 100%;
  /* Needed, and not redundant with aspect-ratio: the element carries
     width/height attributes so the intrinsic size is known before the file
     loads, and without this the attribute's 1920 is taken literally as pixels. */
  height: auto;
  display: block;
  /* Portrait from first paint, at each clip's OWN aspect.
     The poster is the landscape arrival still, so without a declared aspect
     the box takes the poster's shape — 345x217 — and snaps to 345x748 the
     instant the video's dimensions arrive, shoving everything below it down
     the page mid-read. The width/height attributes on each element supply
     that aspect up front, which is why they carry the real measured numbers
     rather than one shared guess.

     One shared guess is what was here first, and it was wrong: the clips are
     932x1920 except Crimson Drift at 888 and Umbral Expanse at 936, so a
     single 886/1920 would have cropped about 5% off every tile — and no one
     value fits all six. Cropping gameplay is not like cropping a sky: the
     outpost line along the bottom and the formation at the top are the whole
     picture. Deriving from the attributes crops nothing.

     Declared inline on each element rather than here, because the clips do not
     agree and one rule cannot serve them. Measured, not assumed: removing the
     declaration entirely does NOT fall back to the width/height attributes —
     the poster's landscape shape wins and the box renders 345x217.

     object-fit: cover still applies, and now effectively only to the poster,
     which is a landscape still being asked to fill a portrait frame. That one
     is an abstract sky and crops happily. */
  object-fit: cover;
  border-radius: 18px;
  border: 1px solid var(--vd-line);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.55);
  background: #05070D;
}

.vd-reel figcaption { margin-top: 12px; }

.vd-reel figcaption strong {
  display: block;
  font-size: 14px;
  letter-spacing: 0.4px;
}

.vd-reel figcaption span {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--vd-text-faint);
}

.vd-reel figcaption p {
  margin: 8px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--vd-text-dim);
}

.vd-shot { text-align: center; margin: 0; }

.vd-shot img {
  border-radius: 18px;
  border: 1px solid var(--vd-line);
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.55);
  margin-bottom: 10px;
}

.vd-shot span {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--vd-text-faint);
}

/* ------------------------------------------------------------------ pills */
.vd-pill {
  display: inline-block;
  padding: 4px 11px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1px;
  text-transform: uppercase;
  border: 1px solid;
}
.vd-pill--cyan   { color: var(--vd-cyan);   border-color: rgba(94,200,255,.4);  background: rgba(94,200,255,.1); }
.vd-pill--gold   { color: var(--vd-gold);   border-color: rgba(255,194,74,.4);  background: rgba(255,194,74,.1); }
.vd-pill--green  { color: var(--vd-green);  border-color: rgba(111,227,154,.4); background: rgba(111,227,154,.1); }
.vd-pill--violet { color: var(--vd-violet); border-color: rgba(185,140,255,.4); background: rgba(185,140,255,.1); }

/* ------------------------------------------------------------------- prose
   Used by privacy.html and support.html. Narrower measure, softer rhythm. */
.vd-prose { max-width: 74ch; }

.vd-prose h2 {
  margin-top: 44px;
  padding-top: 22px;
  border-top: 1px solid var(--vd-line);
  font-size: 22px;
}
.vd-prose h2:first-of-type { margin-top: 12px; border-top: 0; padding-top: 0; }

.vd-prose h3 { margin-top: 28px; font-size: 16px; color: var(--vd-cyan); }

.vd-prose ul, .vd-prose ol { padding-left: 22px; margin: 0 0 18px; }
.vd-prose li { margin-bottom: 7px; }

.vd-prose strong { color: var(--vd-text); }

.vd-prose table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 20px;
  font-size: 14px;
}
.vd-prose th, .vd-prose td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--vd-line);
}
.vd-prose th {
  color: var(--vd-text);
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
}

/* a callout for the things people must not miss */
.vd-note {
  background: var(--vd-panel-2);
  border: 1px solid var(--vd-line);
  border-left: 3px solid var(--vd-cyan);
  border-radius: 10px;
  padding: 16px 18px;
  margin: 0 0 22px;
}
.vd-note--warn { border-left-color: var(--vd-gold); }
.vd-note p:last-child { margin-bottom: 0; }

/* ------------------------------------------------------------------ footer */
.vd-footer {
  border-top: 1px solid var(--vd-line);
  padding: 34px 0;
  margin-top: 40px;
  font-size: 13px;
  color: var(--vd-text-faint);
}

.vd-footer__inner {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
}

.vd-footer nav { display: flex; gap: 18px; flex-wrap: wrap; }

/* --------------------------------------------------------- accessibility */
:focus-visible {
  outline: 2px solid var(--vd-cyan);
  outline-offset: 3px;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition: none !important; }
}

/* The pages are dark by design — the game is. Declaring this stops browsers
   and iOS from trying to invert or re-tint them in light mode. */
:root { color-scheme: dark; }

/* ==========================================================================
   The video page, and the things that make these pages read as the game
   rather than as a dark website.
   ========================================================================== */

/* ------------------------------------------------------------------ video */
/* 16:9 that holds its shape at any width. aspect-ratio alone would do it on
   current browsers; the padding fallback keeps older Safari from collapsing
   the iframe to nothing, which is a blank page rather than a broken one. */
.vd-video {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin: 26px 0 12px;
  border: 1px solid var(--vd-line);
  border-radius: var(--vd-radius-lg);
  overflow: hidden;
  background: #000;
  box-shadow: 0 0 0 1px rgba(94, 200, 255, 0.10),
              0 18px 60px rgba(0, 0, 0, 0.55),
              0 0 70px rgba(94, 200, 255, 0.07);
}

@supports not (aspect-ratio: 16 / 9) {
  .vd-video { height: 0; padding-bottom: 56.25%; }
}

.vd-video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* ------------------------------------------------------------- starfield */
/* Three layers of static stars, drawn as repeating radial gradients. No
   image, no script, no request: it costs one element and survives being
   printed. Fixed rather than scrolling, so the page moves against the sky the
   way the fleet moves against it. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background-color: var(--vd-bg);
  background-image:
    radial-gradient(1.4px 1.4px at 18% 22%, rgba(255,255,255,0.55), transparent 60%),
    radial-gradient(1.2px 1.2px at 73% 14%, rgba(255,255,255,0.40), transparent 60%),
    radial-gradient(1.6px 1.6px at 41% 67%, rgba(255,255,255,0.50), transparent 60%),
    radial-gradient(1.1px 1.1px at 87% 58%, rgba(255,255,255,0.35), transparent 60%),
    radial-gradient(1.3px 1.3px at 9% 81%, rgba(255,255,255,0.45), transparent 60%),
    radial-gradient(1.0px 1.0px at 57% 39%, rgba(255,255,255,0.30), transparent 60%),
    radial-gradient(1.5px 1.5px at 31% 6%, rgba(255,255,255,0.40), transparent 60%),
    radial-gradient(1.2px 1.2px at 64% 91%, rgba(255,255,255,0.35), transparent 60%);
  background-size: 520px 520px, 380px 380px, 640px 640px, 300px 300px,
                   700px 700px, 440px 440px, 560px 560px, 480px 480px;
}

/* A vignette over the lot, so the corners fall away the way they do in the
   arena. Behind the content and above the stars. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(ellipse at 50% 38%,
              transparent 45%, rgba(0, 0, 0, 0.55) 100%);
}

/* The page's own surfaces have to sit on top of both. */
.vd-header, .vd-hero, section, footer { position: relative; z-index: 1; }

/* ------------------------------------------------- the six sector accents */
/* Lifted from lib/src/model/sector.dart, so a sector is the same colour here
   as it is in the game. */
:root {
  --vd-nova: #ffb03a;
  --vd-crimson: #ff4d5e;
  --vd-ion: #35d6e8;
  --vd-aurora: #5fe39b;
  --vd-umbral: #9b6bff;
  --vd-solstice: #ffe066;
}

/* Anything carrying data-sector gets that sector's edge and glow. */
[data-sector] { border-left: 3px solid var(--sector, var(--vd-cyan)); }
[data-sector="nova"]     { --sector: var(--vd-nova); }
[data-sector="crimson"]  { --sector: var(--vd-crimson); }
[data-sector="ion"]      { --sector: var(--vd-ion); }
[data-sector="aurora"]   { --sector: var(--vd-aurora); }
[data-sector="umbral"]   { --sector: var(--vd-umbral); }
[data-sector="solstice"] { --sector: var(--vd-solstice); }

/* ----------------------------------------------------- HUD-ish small print */
/* The game labels everything in heavy, wide-tracked capitals. Borrowing it
   for eyebrows and nav is most of why these pages read as the same product. */
.vd-eyebrow, .vd-brand, h4 { font-variant-numeric: tabular-nums; }

/* Respect a reader who has asked for less motion or a plain background. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* ================================================================= lifted
   Rules that were inline `style="..."` attributes on the markup.

   They are here because an inline attribute beats any stylesheet rule on
   specificity, so a restyle applied to this file would have been silently
   ignored at thirty-eight points across five pages — you change the design
   and find pieces of the old one showing through, which is a miserable way
   to discover the problem.

   Nothing below changes how the site looks. Every value is the one that was
   in the attribute; `website/REFACTOR.md` records how that was verified.

   Four of these are component rules rather than utilities, because the
   "inline" styles were not one-offs at all — the same decision was repeated
   nine, ten, five and four times. Those belong to the component. */

/* Every h3 in a card carried this. `.vd-card` appears only in index.html, so
   the descendant selector cannot reach the bare h3s in the generated
   privacy and support pages. */
.vd-card h3 { margin-top: 12px; }

/* Five of the ledes close a section and need the gap before the grid under
   them; the others sit mid-section and do not. A modifier rather than a
   change to `.vd-lede`, because it is the position that wants the space. */
.vd-lede--spaced { margin-bottom: 30px; }

/* The four sub-pages share a page title smaller than the hero's. Given its
   own class rather than selected as `.vd-eyebrow + h1`, which would also
   match index's hero and shrink it. */
.vd-title-sub { font-size: clamp(28px, 5vw, 42px); }

/* ------------------------------------------------------------- utilities
   The genuine one-offs. Named by value, which is deliberate: these are six
   different top margins between 16 and 28 pixels with no scale behind them,
   and inventing one here would change the layout — the opposite of what this
   refactor is for. A revamp should collapse them into a real scale; until
   then they are at least in one place. */
.vd-mt-16 { margin-top: 16px; }
.vd-mt-18 { margin-top: 18px; }
.vd-mt-22 { margin-top: 22px; }
.vd-mt-24 { margin-top: 24px; }
.vd-mt-26 { margin-top: 26px; }
.vd-mt-28 { margin-top: 28px; }
.vd-mb-18 { margin-bottom: 18px; }
.vd-center { text-align: center; }
.vd-btns--center { justify-content: center; }
