/* ==========================================================================
   Field of Valor — site stylesheet
   Direction: "Forged Iron & Vellum" (GDD 25.1)

   Every colour, size, duration and edge in this file is a token declared once
   in :root and referred to by name from there. No rule below writes a raw
   colour. That is the same discipline the in-game system holds, and it is what
   makes a change to the ally green or the plate edge a single edit.

   Three rules from the game are load-bearing here and must not be relaxed:
     1. Iron is cut metal, so corners are square. Zero radius is the default
        everywhere; a 2px radius exists for menu chrome only.
     2. Depth is a bevel, not a blur — a lit top line, a dark bottom cut, a
        drop shadow. No translucency on plate chrome.
     3. Hover lifts the INK, never the geometry. Nothing moves, nothing scales.
   ========================================================================== */

:root {
  /* --- Iron: the chrome ramp, darkest first (GDD 25.4) ------------------- */
  --iron-0: #0d0b08;
  --iron-1: #16130e;
  --iron-2: #1d1a13;
  --iron-3: #26221a;
  --iron-4: #2a251b;
  --iron-5: #3a3428;
  --iron-6: #4a4132;
  --iron-7: #514634;

  /* Channel forms of the four iron steps that carry alpha, plus the shadow.
     A hex cannot take an alpha in rgba(), so a translucent value would
     otherwise have to write its own colour and break the single-source rule.
     Iron is warm; its shadow is not, so the shadow is true black. */
  --iron-0-rgb: 13, 11, 8;
  --iron-5-rgb: 58, 52, 40;
  --iron-7-rgb: 81, 70, 52;
  --edge-bright-rgb: 138, 122, 92;
  --shadow-rgb: 0, 0, 0;

  /* Print is a different medium: ink on paper, not iron on a screen. */
  --print-ground: #ffffff;
  --print-ink: #000000;

  /* --- Vellum: the ink ramp. These four are also the four attention tiers */
  --ink-number:    #f5efdf;
  --ink-primary:   #e8ddc3;
  --ink-secondary: #b8ab8c;
  --ink-micro:     #8a7a5c;

  /* --- Engraved rules: the entire border vocabulary is three values ------ */
  --edge-faint:  #3b3428;
  --edge:        #5d5341;
  --edge-bright: #8a7a5c;

  /* --- The team pair, and neutral --------------------------------------- */
  --ally:    #3ddc6b;
  --enemy:   #f4382c;
  --neutral: #a89a7c;

  /* --- Type: three faces, one job each ---------------------------------- */
  --face-display: "Cinzel", "Trajan Pro", "Palatino Linotype", Georgia, serif;
  --face-body:    "Spectral", Georgia, "Times New Roman", serif;
  --face-number:  "IBM Plex Mono", ui-monospace, SFMono-Regular, "Courier New", monospace;

  /* Web-anchored scale. The roles and the ratios are the game's; the pixel
     values are not — the in-match ramp bottoms out at a 9px HUD floor, which
     on a page reads as a cramped port of a HUD rather than a site. */
  --t-micro:   0.6875rem;  /* 11px — micro-labels          */
  --t-label:   0.75rem;    /* 12px — engraved labels       */
  --t-small:   0.875rem;   /* 14px — small body, captions  */
  --t-body:    1rem;       /* 16px — body                  */
  --t-lead:    1.125rem;   /* 18px — lead paragraphs       */
  --t-h3:      1.375rem;   /* 22px — hero name role        */
  --t-h2:      1.75rem;    /* 28px — section headings      */
  --t-h1:      2.5rem;     /* 40px — page titles           */
  --t-display: clamp(2rem, 4.4vw, 3.4rem);

  /* Tracking rises as size falls — Cinzel's capitals close up when small.
     Body text is never tracked. Numbers are never styled as uppercase. */
  --track-display: 0.14em;
  --track-label:   0.22em;
  --track-micro:   0.3em;

  --lh-tight: 1.05;
  --lh-snug:  1.25;
  --lh-body:  1.5;

  /* --- Geometry --------------------------------------------------------- */
  --radius:        0;      /* iron is cut metal            */
  --radius-chrome: 2px;    /* menu chrome only             */
  --gap-1: 0.25rem;
  --gap-2: 0.5rem;
  --gap-3: 0.75rem;
  --gap-4: 1rem;
  --gap-5: 1.5rem;
  --gap-6: 2rem;
  --gap-7: 3rem;
  --gap-8: 4.5rem;
  --measure: 68ch;         /* prose line length            */
  --page: 1200px;

  /* --- Depth is a bevel ------------------------------------------------- */
  --bevel:      inset 0 1px 0 rgba(var(--edge-bright-rgb), 0.28),
                inset 0 -1px 0 rgba(var(--shadow-rgb), 0.55);
  --bevel-deep: inset 0 2px 4px rgba(var(--shadow-rgb), 0.75),
                inset 0 -1px 0 rgba(var(--edge-bright-rgb), 0.12);
  --drop:       0 2px 10px rgba(var(--shadow-rgb), 0.55);
  --plate-fill: linear-gradient(180deg, var(--iron-3) 0%, var(--iron-2) 62%, var(--iron-1) 100%);

  /* --- Motion: an allowlist, not a library. Ink only, never geometry. --- */
  --ease: cubic-bezier(0.2, 0, 0.2, 1);
  --dur:  120ms;
  --ease-out:   cubic-bezier(0.16, 1, 0.3, 1);
  --dur-reveal: 900ms;
}

/* ==========================================================================
   Reset and page ground
   ========================================================================== */

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

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

body {
  margin: 0;
  background-color: var(--iron-0);
  color: var(--ink-primary);
  font-family: var(--face-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  /* A hammered ground rather than a painted one: two very low-contrast washes
     over the darkest iron step. No image, no request. */
  background-image:
    radial-gradient(1200px 600px at 50% -10%, rgba(var(--iron-7-rgb), 0.22), transparent 70%),
    radial-gradient(900px 500px at 100% 100%, rgba(var(--iron-5-rgb), 0.16), transparent 70%);
  background-attachment: fixed;
}

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

a { color: var(--ink-primary); text-decoration: none; }
a:hover { color: var(--ink-number); }

:focus-visible {
  outline: 2px solid var(--edge-bright);
  outline-offset: 2px;
}

hr {
  border: 0;
  border-top: 1px solid var(--edge-faint);
  margin: var(--gap-6) 0;
}

::selection { background: var(--iron-6); color: var(--ink-number); }

/* Skip link — visible only when focused. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--iron-3);
  color: var(--ink-number);
  border: 1px solid var(--edge-bright);
  padding: var(--gap-3) var(--gap-4);
  font-family: var(--face-display);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.skip:focus { left: var(--gap-4); top: var(--gap-4); }

/* ==========================================================================
   Type roles — composite, so no component assembles size+weight+height itself
   ========================================================================== */

.display,
h1, h2, h3, h4 {
  font-family: var(--face-display);
  font-weight: 600;
  line-height: var(--lh-tight);
  letter-spacing: var(--track-display);
  color: var(--ink-primary);
  margin: 0;
  text-transform: uppercase;
}

h1 { font-size: var(--t-h1); }
h2 { font-size: var(--t-h2); }
h3 { font-size: var(--t-h3); }
h4 { font-size: var(--t-body); }

/* Headings wrap on sense rather than on width. */
h1, h2, h3, .banner__title { text-wrap: balance; }

/* A headline is prose, not a label, and the game sets prose in title case —
   display capitals are for things that are read by position rather than
   actually read. News and patch titles therefore drop the uppercase and most
   of the tracking, and keep the display face. */
.title-prose {
  text-transform: none;
  letter-spacing: 0.01em;
}

p { margin: 0 0 var(--gap-4); max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

.lead {
  font-size: var(--t-lead);
  color: var(--ink-primary);
}

.label {
  font-family: var(--face-display);
  font-weight: 500;
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-micro);
  display: block;
}

.micro {
  font-family: var(--face-display);
  font-weight: 500;
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-micro);
}

/* Every number in the product is mono, fixed-width and semibold. */
.num {
  font-family: var(--face-number);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  color: var(--ink-number);
  text-transform: none;
}

.secondary { color: var(--ink-secondary); }
.muted     { color: var(--ink-micro); }

/* Absent data is BLANK — never a question mark, never the word "hidden".
   This class exists so a blank still occupies its column. */
.blank { display: inline-block; min-width: 2ch; color: transparent; }

/* ==========================================================================
   Layout
   ========================================================================== */

.wrap {
  width: 100%;
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 var(--gap-5);
}

.section { padding: var(--gap-8) 0; }
.section--tight { padding: var(--gap-7) 0; }

.section-head {
  display: flex;
  align-items: baseline;
  gap: var(--gap-4);
  margin-bottom: var(--gap-5);
  padding-bottom: var(--gap-3);
  border-bottom: 1px solid var(--edge-faint);
}

.grid { display: grid; gap: var(--gap-5); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid--heroes { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: var(--gap-4); }

/* ==========================================================================
   The plate — one of the three shared building blocks
   ========================================================================== */

.plate {
  background: var(--plate-fill);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  box-shadow: var(--bevel), var(--drop);
  padding: var(--gap-5);
}

/* Spare density: a single engraved hairline over a flat dark field, no bevel
   and no drop. Same palette, same type, same square corners — a weight, not a
   second style. */
.plate--spare {
  background: var(--iron-1);
  border-color: var(--edge-faint);
  box-shadow: none;
}

/* ==========================================================================
   Header and navigation
   ========================================================================== */

.masthead {
  position: sticky;
  top: 0;
  z-index: 40;
  background: linear-gradient(180deg, var(--iron-3) 0%, var(--iron-1) 100%);
  border-bottom: 1px solid var(--edge);
  box-shadow: var(--bevel), 0 4px 18px rgba(var(--shadow-rgb), 0.6);
}

.masthead__inner {
  display: flex;
  align-items: center;
  gap: var(--gap-5);
  min-height: 64px;
  padding-top: var(--gap-2);
  padding-bottom: var(--gap-2);
}

.wordmark {
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-body);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  color: var(--ink-number);
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: var(--gap-3);
}
.wordmark:hover { color: var(--ink-number); }
.wordmark__mark { flex: none; }

.nav { margin-left: auto; }

.nav__list {
  list-style: none;
  display: flex;
  align-items: stretch;
  gap: var(--gap-1);
  margin: 0;
  padding: 0;
}

.nav__item { position: relative; display: flex; }

.nav__link {
  display: flex;
  align-items: center;
  gap: var(--gap-2);
  padding: var(--gap-3) var(--gap-4);
  font-family: var(--face-display);
  font-weight: 500;
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-secondary);
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

/* Hover lifts the ink and the border. Nothing moves. */
.nav__link:hover { color: var(--ink-number); border-color: var(--edge-faint); }
.nav__link[aria-current="page"],
.nav__link[data-section-current="true"] {
  color: var(--ink-number);
  border-color: var(--edge);
  background: var(--iron-2);
  box-shadow: var(--bevel-deep);
}

.nav__caret { width: 7px; height: 7px; flex: none; fill: currentColor; }

/* Submenu — the one place a 2px chrome radius is allowed. */
.nav__sub {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 220px;
  list-style: none;
  margin: 0;
  padding: var(--gap-2);
  background: var(--plate-fill);
  border: 1px solid var(--edge);
  border-radius: var(--radius-chrome);
  box-shadow: var(--bevel), var(--drop);
  display: none;
  z-index: 50;
}
.nav__item:hover > .nav__sub,
.nav__item:focus-within > .nav__sub,
.nav__sub[data-open="true"] { display: block; }

.nav__sub .nav__link { width: 100%; padding: var(--gap-3); }

/* ==========================================================================
   Buttons — square, gold hairline, bevel. Press deepens the well.
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--gap-3);
  padding: var(--gap-4) var(--gap-6);
  font-family: var(--face-display);
  font-weight: 600;
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-number);
  background: linear-gradient(180deg, var(--iron-4) 0%, var(--iron-2) 100%);
  border: 1px solid var(--edge-bright);
  border-radius: var(--radius);
  box-shadow: var(--bevel), var(--drop);
  cursor: pointer;
  transition: color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
.btn:hover { color: var(--ink-number); border-color: var(--ink-micro); background: linear-gradient(180deg, var(--iron-5) 0%, var(--iron-3) 100%); }
.btn:active { box-shadow: var(--bevel-deep); }

/* The hot slot: a chamfered double-struck outline. Reserved for the one
   action the page exists for. */
.btn--hot {
  position: relative;
  padding: var(--gap-5) var(--gap-7);
  font-size: var(--t-body);
  outline: 1px solid var(--edge-faint);
  outline-offset: 3px;
}

/* Disabled drops the ink and keeps the plate at full opacity, so a dead
   button is still a legible landmark. */
.btn[aria-disabled="true"] {
  color: var(--ink-micro);
  border-color: var(--edge);
  cursor: default;
  box-shadow: var(--bevel);
}
.btn[aria-disabled="true"]:hover {
  color: var(--ink-micro);
  border-color: var(--edge);
  background: linear-gradient(180deg, var(--iron-4) 0%, var(--iron-2) 100%);
}

.btn__glyph { width: 14px; height: 14px; flex: none; fill: currentColor; }

.btn-note {
  margin-top: var(--gap-3);
  font-family: var(--face-body);
  font-size: var(--t-small);
  color: var(--ink-micro);
}

/* ==========================================================================
   Banner — the one full-bleed surface
   ========================================================================== */

.banner {
  position: relative;
  border-bottom: 1px solid var(--edge);
  background: var(--iron-0);
  overflow: hidden;
}

.banner__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.banner__media picture { display: block; width: 100%; height: 100%; }
.banner__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 58% 30%;
}

/* The render is captured against the map's black void, so it needs no cutout:
   the same darkness runs off the edge of the frame into the page ground. */
.banner__veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  /* Dark enough on the left to hold the headline at full contrast, then out of
     the way over the render, with a light vignette at the far edge so the
     figure does not run flat into the border. */
  background:
    linear-gradient(90deg, var(--iron-0) 0%, rgba(var(--iron-0-rgb), 0.94) 26%, rgba(var(--iron-0-rgb), 0.6) 44%, rgba(var(--iron-0-rgb), 0.08) 64%, rgba(var(--iron-0-rgb), 0.3) 100%),
    linear-gradient(0deg, var(--iron-0) 0%, transparent 34%);
}

.banner__inner {
  position: relative;
  z-index: 2;
  min-height: min(76vh, 660px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-top: var(--gap-8);
  padding-bottom: var(--gap-8);
}

.banner__copy { max-width: 40rem; }

.banner__title {
  font-size: var(--t-display);
  margin-bottom: var(--gap-4);
  text-shadow: 0 2px 24px rgba(var(--shadow-rgb), 0.9);
}

.banner__sub {
  font-size: var(--t-lead);
  color: var(--ink-secondary);
  margin-bottom: var(--gap-6);
  max-width: 34rem;
}

.banner__cta { display: flex; flex-direction: column; align-items: flex-start; }

/* ==========================================================================
   Corner motif — slot chrome for tiles. A gold hairline and a corner cut,
   never a bevelled plate; bevels are reserved for large panels.
   ========================================================================== */

.tile {
  position: relative;
  display: block;
  background: var(--iron-1);
  border: 1px solid var(--edge-faint);
  border-radius: var(--radius);
  color: var(--ink-secondary);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.tile::before,
.tile::after {
  content: "";
  position: absolute;
  width: 12px;
  height: 12px;
  border: 1px solid var(--edge-bright);
  opacity: 0.55;
  transition: opacity var(--dur) var(--ease);
  pointer-events: none;
}
.tile::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; }
.tile::after  { bottom: -1px; right: -1px; border-left: 0; border-top: 0; }
.tile:hover { color: var(--ink-number); border-color: var(--edge); }
.tile:hover::before, .tile:hover::after { opacity: 1; }

/* ==========================================================================
   Hero tiles and hero pages
   ========================================================================== */

.hero-tile__figure {
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background:
    radial-gradient(120% 90% at 50% 12%, var(--iron-3) 0%, var(--iron-0) 72%);
  border-bottom: 1px solid var(--edge-faint);
}
.hero-tile__figure img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 12%;
  filter: saturate(0.94) contrast(1.04);
}

/* Graceful degrade: no render file yet, so the tile carries the name on a
   dark plate rather than a broken image. */
.render-missing {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--gap-4);
  text-align: center;
  font-family: var(--face-display);
  font-weight: 600;
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-micro);
}

.hero-tile__name {
  display: block;
  padding: var(--gap-3) var(--gap-4);
  font-family: var(--face-display);
  font-weight: 600;
  font-size: var(--t-small);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: inherit;
}

.hero-tile__meta {
  display: block;
  padding: 0 var(--gap-4) var(--gap-3);
  font-family: var(--face-display);
  font-weight: 500;
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-micro);
  min-height: 1.6em;
}

/* --- Hero detail ------------------------------------------------------- */

.hero-head {
  display: grid;
  grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
  gap: var(--gap-6);
  align-items: start;
}

.hero-portrait {
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: radial-gradient(120% 90% at 50% 10%, var(--iron-3) 0%, var(--iron-0) 74%);
  border: 1px solid var(--edge);
  box-shadow: var(--bevel), var(--drop);
}
.hero-portrait img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 10%; }

.hero-title { font-size: var(--t-h1); margin-bottom: var(--gap-3); }

.chips { display: flex; flex-wrap: wrap; gap: var(--gap-2); margin: 0; padding: 0; list-style: none; }

.chip {
  padding: var(--gap-1) var(--gap-3);
  border: 1px solid var(--edge-faint);
  background: var(--iron-2);
  font-family: var(--face-display);
  font-weight: 500;
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-secondary);
}

/* --- Stat tables ------------------------------------------------------- */

.stats { width: 100%; border-collapse: collapse; }
.stats caption {
  text-align: left;
  padding-bottom: var(--gap-3);
  font-family: var(--face-display);
  font-weight: 500;
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-micro);
}
.stats th, .stats td {
  padding: var(--gap-3) var(--gap-2);
  border-bottom: 1px solid var(--edge-faint);
  text-align: left;
  font-weight: 400;
  font-size: var(--t-small);
}
.stats th { color: var(--ink-secondary); font-family: var(--face-body); }
.stats td { color: var(--ink-number); text-align: right; }
.stats tr:last-child th, .stats tr:last-child td { border-bottom: 0; }
.stats .unit { color: var(--ink-micro); font-family: var(--face-body); font-size: var(--t-micro); }

/* --- Role bars: information beside a claim, never a verdict ------------- */

.roles { list-style: none; margin: var(--gap-3) 0 0; padding: 0; display: grid; gap: var(--gap-4); }
.role { display: grid; gap: var(--gap-2); }
.role__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--gap-3); }
.role__name {
  font-family: var(--face-display);
  font-weight: 500;
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-secondary);
}
.role__track {
  height: 12px;
  background: var(--iron-0);
  border: 1px solid var(--edge-faint);
  box-shadow: var(--bevel-deep);
}
/* Parchment on iron. The old iron-on-iron fill was so close to the empty
   track that the bar read as empty and only its right edge showed, which is
   why the mark looked unrelated to the number beside it. */
.role__fill {
  /* A span is inline by default, and an inline box ignores width and height —
     which is why this bar was drawing as an empty track no matter what number
     sat beside it. */
  display: block;
  height: 100%;
  background: linear-gradient(180deg, var(--ink-secondary), var(--ink-micro));
  box-shadow: inset 0 1px 0 rgba(var(--edge-bright-rgb), 0.5);
}
.role__value { font-size: var(--t-small); }

/* ==========================================================================
   Feeds — news, patches, gameplay updates
   ========================================================================== */

.feed { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--gap-4); }

.entry { padding: var(--gap-5); }
.entry__date {
  font-family: var(--face-number);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-size: var(--t-small);
  color: var(--ink-micro);
}
.entry__title { font-size: var(--t-h3); margin: var(--gap-2) 0 var(--gap-3); }
.entry__summary { color: var(--ink-secondary); margin: 0; }

.empty-state {
  padding: var(--gap-7) var(--gap-5);
  text-align: center;
  border: 1px dashed var(--edge);
  background: var(--iron-1);
}
.empty-state h2 { color: var(--ink-secondary); margin-bottom: var(--gap-3); }
.empty-state p { margin: 0 auto; color: var(--ink-micro); }

.article__body { max-width: var(--measure); }
.article__body p { margin-bottom: var(--gap-5); }

.backlink {
  font-family: var(--face-display);
  font-weight: 500;
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-micro);
}
.backlink:hover { color: var(--ink-primary); }

/* ==========================================================================
   Footer
   ========================================================================== */

.footer {
  margin-top: var(--gap-8);
  border-top: 1px solid var(--edge);
  background: linear-gradient(180deg, var(--iron-1) 0%, var(--iron-0) 100%);
  padding: var(--gap-6) 0;
}
.footer__inner { display: flex; flex-wrap: wrap; gap: var(--gap-5); align-items: baseline; justify-content: space-between; }
.footer__links { display: flex; flex-wrap: wrap; gap: var(--gap-5); list-style: none; margin: 0; padding: 0; }

/* ==========================================================================
   Responsive — the nav collapses to a disclosure below 860px
   ========================================================================== */

.nav__toggle { display: none; }

/* ==========================================================================
   Platforms at launch

   Hairlines come from a 1px grid gap over a rule-coloured ground rather than
   from per-cell borders: a border-left leaves a stray edge on the first cell of
   every wrapped row, and the number of columns here changes with the viewport.
   ========================================================================== */

.platforms {
  border-top: 1px solid var(--edge-faint);
  border-bottom: 1px solid var(--edge-faint);
  background: var(--iron-1);
  padding: var(--gap-5) 0;
}
.platforms__title { margin-bottom: var(--gap-4); }

.platforms__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1px;
  background: var(--edge-faint);
  border: 1px solid var(--edge-faint);
}

.platform {
  display: flex;
  flex-direction: column;
  gap: var(--gap-2);
  padding: var(--gap-4);
  background: var(--iron-1);
}
.platform__name {
  font-family: var(--face-display);
  font-weight: 600;
  font-size: var(--t-lead);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  color: var(--ink-primary);
}
.platform__note {
  font-family: var(--face-body);
  font-size: var(--t-small);
  color: var(--ink-micro);
}

/* ==========================================================================
   Narrow screens

   The banner is the one thing that cannot simply reflow. On a wide screen the
   render sits beside the copy and the veil is a left-to-right gradient. On a
   narrow one there is no beside: the render becomes its own block above the
   copy, and the veil becomes a bottom fade over that block alone. Leaving it
   as an absolute backdrop was what hid the hero on a phone entirely — the
   banner collapsed to the height of its text, and object-fit: cover then
   showed the empty left half of a 16:9 image.
   ========================================================================== */

@media (max-width: 860px) {
  :root {
    --banner-media: min(52vh, 440px);
    --page-pad: var(--gap-4);
  }

  .wrap { padding: 0 var(--page-pad); }

  /* A fixed background attachment is a known scroll-performance trap on
     mobile Safari, and the wash it carries is invisible at this size anyway. */
  body { background-attachment: scroll; }

  .banner { display: flex; flex-direction: column; }
  .banner__media {
    position: relative;
    inset: auto;
    height: var(--banner-media);
    flex: none;
  }
  /* The narrow crop is framed on the hero already; centre it and let the
     drift stop, since a slow zoom on a phone is only a battery cost. */
  .banner__media img { object-position: 50% 12%; animation: none; }
  .banner__veil {
    top: 0;
    bottom: auto;
    height: var(--banner-media);
    background: linear-gradient(180deg,
      rgba(var(--iron-0-rgb), 0.1) 0%,
      rgba(var(--iron-0-rgb), 0.05) 42%,
      rgba(var(--iron-0-rgb), 0.72) 82%,
      var(--iron-0) 100%);
  }
  .banner__inner {
    min-height: 0;
    padding-top: var(--gap-5);
    padding-bottom: var(--gap-7);
  }
  .banner__sub { margin-bottom: var(--gap-5); }

  /* Nav: every target comfortably past the 44px thumb minimum. */
  .nav__toggle { display: inline-flex; margin-left: auto; padding: var(--gap-3) var(--gap-5); }
  .nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin: 0;
    background: var(--plate-fill);
    border-bottom: 1px solid var(--edge);
    box-shadow: var(--drop);
    padding: var(--gap-3) var(--page-pad) var(--gap-5);
    display: none;
    max-height: calc(100vh - 64px);
    overflow-y: auto;
  }
  .nav[data-open="true"] { display: block; }
  .nav__list { flex-direction: column; align-items: stretch; gap: 0; }
  .nav__item { flex-direction: column; }
  .nav__link {
    justify-content: space-between;
    border-bottom: 1px solid var(--edge-faint);
    padding: var(--gap-4) var(--gap-2);
    min-height: 48px;
  }
  /* Shut until the parent is opened. Showing every child of every menu the
     moment the menu opens is what made this look expanded by default. */
  .nav__sub {
    position: static;
    display: none;
    min-width: 0;
    border: 0;
    box-shadow: none;
    background: transparent;
    padding: 0 0 0 var(--gap-4);
  }
  /* Hover and focus must not open a stacked submenu; only the disclosure does.
     These two cancel the desktop rules, and the open rule below has to match
     their specificity (three) and come after them, or tapping the parent gives
     it focus and :focus-within shuts the submenu the tap just opened. That is
     exactly what kept this from expanding on a phone. */
  .nav__item:hover > .nav__sub,
  .nav__item:focus-within > .nav__sub { display: none; }
  .nav__item > .nav__sub[data-open="true"] { display: block; }
  .nav__link[aria-expanded="true"] { color: var(--ink-number); }

  .hero-head { grid-template-columns: 1fr; }
  .hero-portrait { max-width: 420px; }
  .kit { grid-template-columns: 1fr; }
  .section { padding: var(--gap-7) 0; }
  .section-head { flex-wrap: wrap; row-gap: var(--gap-2); }
}

@media (max-width: 520px) {
  :root { --t-h1: 1.875rem; --t-h2: 1.5rem; --t-h3: 1.25rem; }

  .wordmark { font-size: var(--t-small); gap: var(--gap-2); }
  .masthead__inner { gap: var(--gap-3); }

  .banner__title { letter-spacing: 0.08em; }
  .btn--hot { width: 100%; padding: var(--gap-4) var(--gap-4); font-size: var(--t-small); }

  .grid--heroes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .rail__tile { width: 58vw; }
  .platforms__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* The talent spine keeps both sides, which is the rule, but the tier badge
     shrinks so two real choices still fit either side of it. */
  .tier { gap: var(--gap-2); }
  .tier__level { width: 2rem; height: 2rem; font-size: var(--t-micro); }
  .tier__side { min-height: 3.25rem; padding: var(--gap-2); font-size: var(--t-micro); }

  .slot { flex-wrap: wrap; }
  .stats th, .stats td { font-size: var(--t-micro); }
  .entry { padding: var(--gap-4); }
}

/* Nothing on the page may scroll the document sideways. */
@media (max-width: 860px) {
  html, body { overflow-x: hidden; }
}

/* Motion is an allowlist. If the reader has asked for less, there is none. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

/* Print: the page is dark-only on screen, but ink on paper. */
@media print {
  body { background: var(--print-ground); color: var(--print-ink); }
  .masthead, .footer, .banner__media, .banner__veil { display: none; }
}


/* ==========================================================================
   Entrance reveals — a marketing-surface extension, not a change to the rule

   The in-match rule stands everywhere on this site: hover lifts the ink and
   never the geometry, and nothing scales under a cursor. This is a different
   thing. A section fades in and rises a short distance ONCE, the first time it
   is scrolled to, and is then left alone permanently. Nothing here is read
   under pressure and nothing here repeats.

   The initial hidden state is scoped to .js, which is set by an inline script
   in the head before first paint. With scripting off the class never lands,
   the rule never applies, and the whole page is simply visible.
   ========================================================================== */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(28px);
}

.js [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] { opacity: 1; transform: none; }
}

/* ==========================================================================
   Utilities
   ========================================================================== */

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

/* Banner with no render yet: the void the renders are captured against,
   drawn instead of photographed. */
.banner__media--empty {
  background:
    radial-gradient(140% 100% at 62% 18%, var(--iron-4) 0%, var(--iron-1) 46%, var(--iron-0) 78%);
}

/* Skeleton for a hero render that has not been captured yet. An engraved
   initial on a dark plate, so the tile reads as intentional rather than
   broken while the art is still being made. */
.render-missing { padding: 0; }
.render-missing .sigil {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}
.render-missing::after {
  content: "RENDER PENDING";
  position: absolute;
  left: 0; right: 0; bottom: var(--gap-4);
  text-align: center;
  font-family: var(--face-display);
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  color: var(--iron-7);
}





/* ==========================================================================
   Hero rails — three rows, each running against the one above it

   The roster shows itself rather than waiting to be asked. Each rail carries a
   third of the heroes twice over and slides by exactly half its own width, so
   the loop closes with no seam. A tile carries the render and nothing else;
   the name is there for assistive technology and surfaces on hover or focus.

   This is the one thing on the site that moves without being asked, so it is
   held to two conditions: pointing at it stops it, and a reader who has asked
   for reduced motion gets a plain scrollable rail with no animation at all.
   ========================================================================== */

.rails { display: grid; gap: var(--gap-3); }

.rail {
  overflow: hidden;
  /* Fade both ends into the page ground, so a rail reads as continuing past
     the edge of the page rather than being cut off at it. */
  -webkit-mask-image: linear-gradient(90deg, transparent, black 6%, black 94%, transparent);
          mask-image: linear-gradient(90deg, transparent, black 6%, black 94%, transparent);
}

.rail__track {
  display: flex;
  width: max-content;
  animation: rail-left var(--rail-dur, 60s) linear infinite;
}
.rail__track--right { animation-name: rail-right; }

/* Spacing is a margin on the tile, not a gap on the track: a gap would add one
   extra gap between the two copies of the list and the loop would visibly
   jump every time it came round. */
.rail__tile {
  position: relative;
  flex: 0 0 auto;
  width: clamp(180px, 21vw, 260px);
  aspect-ratio: 16 / 9;
  margin-right: var(--gap-3);
  overflow: hidden;
  background: var(--iron-1);
  border: 1px solid var(--edge-faint);
  transition: border-color var(--dur) var(--ease);
}
.rail__tile img,
.rail__tile .sigil { width: 100%; height: 100%; display: block; }
.rail__tile:hover { border-color: var(--edge-bright); }

.rail__name {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: var(--gap-3);
  font-family: var(--face-display);
  font-weight: 600;
  font-size: var(--t-micro);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-number);
  background: linear-gradient(0deg, rgba(var(--iron-0-rgb), 0.92), transparent);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.rail__tile:hover .rail__name,
.rail__tile:focus-visible .rail__name { opacity: 1; }

/* Pointing at any rail holds all three, so the row being read and the rows
   beside it stop together rather than one sliding under the cursor. */
.rails:hover .rail__track,
.rails:focus-within .rail__track { animation-play-state: paused; }

@keyframes rail-left  { from { transform: translateX(0);    } to { transform: translateX(-50%); } }
@keyframes rail-right { from { transform: translateX(-50%); } to { transform: translateX(0);    } }

@media (prefers-reduced-motion: reduce) {
  .rail {
    overflow-x: auto;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .rail__track { animation: none; }
}

/* ==========================================================================
   Kit: ability slots, the innate and the ultimate upgrade

   Slot chrome is a hairline and a corner cut, never a bevelled plate — bevels
   are for large panels. An empty slot takes the dashed edge the game gives an
   empty slot, so "nothing here yet" is a drawn state rather than a hole.
   ========================================================================== */

.kit {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: var(--gap-6);
  align-items: start;
}

.slots { list-style: none; margin: var(--gap-3) 0 0; padding: 0; display: grid; gap: var(--gap-2); }

.slot {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--gap-4);
  padding: var(--gap-4);
  background: var(--iron-1);
  border: 1px solid var(--edge-faint);
}
/* An empty slot has nothing to align to, so it centres on its key instead. */
.slot[data-empty="true"] { align-items: center; padding: var(--gap-3) var(--gap-4); }
.slot--wide { margin-top: var(--gap-3); }
.slot[data-empty="true"] { border-style: dashed; }

/* The notch: the ability-button slot frame, a chamfer struck off the top left. */
.slot--notch::before {
  content: "";
  position: absolute;
  top: -1px; left: -1px;
  width: 10px; height: 10px;
  border-top: 1px solid var(--edge-bright);
  border-left: 1px solid var(--edge-bright);
}

.slot__key {
  flex: none;
  width: 2rem; height: 2rem;
  display: grid;
  place-items: center;
  font-size: var(--t-small);
  background: var(--iron-2);
  border: 1px solid var(--edge);
  box-shadow: var(--bevel-deep);
}
.slot[data-empty="true"] .slot__key { color: var(--ink-micro); border-color: var(--edge-faint); }

.slot__body { display: flex; flex-direction: column; gap: var(--gap-2); min-width: 0; flex: 1; }
.slot__head { display: flex; align-items: center; gap: var(--gap-3); flex-wrap: wrap; }
.slot__name {
  font-family: var(--face-display);
  font-weight: 600;
  font-size: var(--t-small);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-primary);
}
.slot__name--empty {
  text-transform: none;
  letter-spacing: 0;
  font-family: var(--face-body);
  font-weight: 400;
  color: var(--ink-micro);
}
.slot__desc { font-size: var(--t-small); color: var(--ink-secondary); margin: 0; }
.slot__flag {
  align-self: flex-start;
  padding: 0 var(--gap-2);
  border: 1px solid var(--edge-faint);
  font-family: var(--face-display);
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  color: var(--ink-micro);
}

/* One pip per rank the entry carries. */
.pips { display: flex; gap: 3px; }
.pip { width: 7px; height: 7px; background: var(--edge-bright); }

/* ==========================================================================
   Talent tree — a spine, with both sides of every tier always drawn
   ========================================================================== */

.tree {
  position: relative;
  list-style: none;
  margin: var(--gap-4) 0 0;
  padding: 0;
}
.tree::before {
  content: "";
  position: absolute;
  left: 50%; top: 0; bottom: 0;
  width: 1px;
  background: var(--edge-faint);
  transform: translateX(-50%);
}

.tier {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--gap-4);
  padding: var(--gap-2) 0;
}

.tier__level {
  width: 2.5rem; height: 2.5rem;
  display: grid;
  place-items: center;
  font-size: var(--t-small);
  background: var(--iron-2);
  border: 1px solid var(--edge);
  box-shadow: var(--bevel);
}

.tier__side {
  display: flex;
  align-items: center;
  min-height: 3rem;
  padding: var(--gap-3);
  background: var(--iron-1);
  border: 1px solid var(--edge-faint);
  font-size: var(--t-small);
  color: var(--ink-primary);
}
.tier > .tier__side:first-child { justify-content: flex-end; text-align: right; }
.tier__side--empty { border-style: dashed; }

/* ==========================================================================
   Notes — the small print under a section: body face, never tracked
   ========================================================================== */

.note {
  margin-top: var(--gap-4);
  font-family: var(--face-body);
  font-size: var(--t-small);
  color: var(--ink-micro);
  max-width: var(--measure);
}

/* A slow drift on the banner render, so the page has a pulse before anything
   is scrolled. It is the render that moves, never the type over it. */
.banner__media img { animation: banner-drift 42s var(--ease) infinite alternate; }
@keyframes banner-drift {
  from { transform: scale(1.02) translate3d(0, 0, 0); }
  to   { transform: scale(1.09) translate3d(-1.2%, -1%, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .banner__media img { animation: none; }
}

@media (max-width: 860px) {
  .kit { grid-template-columns: 1fr; }
  .tier { gap: var(--gap-3); }
}

@media (max-width: 520px) {
  .rail__tile { width: 62vw; }
  .tier__side { min-height: 2.5rem; padding: var(--gap-2); }
}


/* ==========================================================================
   Ability readout — the tooltip, on a page

   The in-match tooltip gives a name, the ranks it carries, then one line per
   value with every rank of that value laid out along it. That is what this is:
   the label in body face on the left, the per-rank numbers in the number role
   on the right, fixed-width so the ranks line up down the card.
   ========================================================================== */

.stat-rows {
  display: grid;
  gap: 0;
  margin-top: var(--gap-2);
  border-top: 1px solid var(--edge-faint);
}

.stat-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--gap-4);
  padding: var(--gap-2) 0;
  border-bottom: 1px solid var(--edge-faint);
}
.stat-row:last-child { border-bottom: 0; }

.stat-row__label {
  font-family: var(--face-display);
  font-weight: 500;
  font-size: var(--t-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-micro);
  white-space: nowrap;
}
.stat-row__value {
  font-size: var(--t-small);
  text-align: right;
}

/* The Steam mark carries the button, so it sits a step above the arrow glyphs. */
.btn__glyph--steam { width: 18px; height: 18px; }

@media (max-width: 520px) {
  .stat-row { flex-direction: column; align-items: flex-start; gap: 2px; }
  .stat-row__value { text-align: left; }
}


/* ==========================================================================
   Hero page — a desktop layout, not a widened phone layout

   Below 900px the page is one column, which is the right answer on a phone.
   From 900px the portrait sits beside the identity. From 1280px the numbers
   join them as a third column, so the whole of a hero — who they are, where
   they play, and what they are made of — is one screen rather than three
   stacked bands of half-empty width.
   ========================================================================== */

.hero-head {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--gap-6);
  align-items: start;
}
.hero-head__art { min-width: 0; }
.hero-head__id { min-width: 0; }
.hero-head__stats { min-width: 0; }

@media (min-width: 900px) {
  .hero-head {
    grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
  }
  .hero-head__stats { grid-column: 1 / -1; }
  /* Side by side while they have the room to be. */
  .hero-head__stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap-4); align-items: start; }
  .hero-head__stats > .plate:nth-child(2) { margin-top: 0 !important; }
  .hero-head__stats > .note { grid-column: 1 / -1; margin-top: 0; }
}

@media (min-width: 1280px) {
  .hero-head {
    grid-template-columns: minmax(0, 340px) minmax(0, 1fr) minmax(0, 420px);
    align-items: stretch;
  }
  /* The portrait takes the height of the tallest column instead of stopping at
     its own aspect ratio and leaving a hole under itself. */
  .hero-head__art, .hero-head__art > .hero-portrait { height: 100%; }
  .hero-head__art > .hero-portrait { aspect-ratio: auto; min-height: 460px; }
  .hero-head__stats {
    grid-column: auto;
    grid-template-columns: 1fr;
  }
  .hero-head__stats > .plate:nth-child(2) { margin-top: var(--gap-4) !important; }

  /* Five ability cards in one column leaves a very tall, very narrow page on a
     wide screen; two columns keeps the reading order and halves the height. */
  .slots { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kit { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }

  /* The tree is a spine: past a point, more width only pushes the two sides
     apart. Cap it and centre it rather than stretching it to the page. */
  .tree { max-width: 980px; }
}

/* The page itself has more room to give on a large monitor. */
@media (min-width: 1500px) {
  :root { --page: 1360px; }
}
