/* THE HIDDEN ATTRIBUTE HAS TO WIN, EVERYWHERE, AND THIS IS THE ONE SHEET EVERY
   PAGE LOADS. The browser's own `[hidden] { display: none }` lives in the UA
   stylesheet, so ANY author `display:` outranks it at any specificity.

   This corner has now been bitten four times: tools.css, realm/hall.css,
   `.cmp-list` in the wrestling generator (1,023 generated copies), and
   Oathsworn — whose `.os-council { display: flex }` left an 82%-opaque overlay
   across the whole game for its entire shipped life, swallowing every tap meant
   for the buttons underneath, while the element truthfully reported itself
   hidden. The first three were each fixed per-element, which only ever protects
   the element somebody already noticed.

   Put HERE rather than in fun.css because fun.css reaches only the 83 pages
   inside /site/fun/. The wings that carry the fan corner's identity from
   outside that directory — /site/wrestling/ (1,023 pages) and
   /site/last-kingdom/ (5) — load NO other shared sheet, and the wrestling
   sector is precisely where this class already bit once. All 1,426 site pages
   load this file; the React game client does not, so the blast radius stops at
   the companion site. !important because the point is to beat every layout rule
   in every sheet loaded after this one, the ones not yet written included.

   Checked rather than assumed: every element on the site that ships `hidden` is
   one meant to be invisible, nothing reveals an element by writing inline
   style.display while leaving `hidden` set, and `hidden=until-found` is unused. */
[hidden] { display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════════
   IRON REALMS — companion site theme. Standalone (no build step); the fonts
   are the same self-hosted faces the game ships, loaded from /fonts.

   ───────────────────────────────────────────────────────────────────────────
   THE PALETTE, AND THE JOB EACH COLOUR DOES
   ───────────────────────────────────────────────────────────────────────────
   Read this before changing a value. Every colour on ~260 pages resolves
   through the tokens below, so a token is not a preference — it is a role, and
   the role is what the next person needs to inherit.

   WHAT WAS WRONG WITH THE OLD SHEET, stated plainly so it is not rebuilt:

     1. ONE ACCENT DID EVERY JOB. A single gold marked headings, links,
        buttons, table headers, hairlines, active nav and bold body text. When
        everything is the accent, nothing is emphasised — there was no way for
        a page to say "this bit matters more than that bit".
     2. NO TONAL DIFFERENCE BETWEEN SECTIONS. Guides, newsroom, database,
        tools and the fan hub were the same three colours, so moving between
        them felt like never having moved.
     3. FLAT SURFACES. Near-black ground, hairline border, nothing else. No
        elevation, no material, nothing that rewards looking closely.
     4. PURE-GREY NEUTRALS. A neutral with no hue reads as inherited from a
        framework. A neutral with a deliberate bias reads as chosen.

   ── 1. THE NEUTRAL RAMP ─────────────────────────────────────────────────────
   Not black-to-white. Every step is generated at OKLCH hue 265° (blue-violet)
   with the chroma tapering off toward the light end so pale text never picks
   up a cast. The bias is small — 1.4–2.6% chroma — and that is the point: it
   is felt as "cold forged iron" rather than seen as "blue".

     --ink-900  #090c12  the floor. Footer, code wells, the deepest inset.
     --ink-800  #0f131a  THE PAGE GROUND. Everything else sits on this.
     --ink-700  #141822  the alternating band, one step up from the ground.
     --surface-1 #171b24  a card at rest (its darker end).
     --surface-2 #1f242d  a card at rest (its lighter end) / raised panels.
     --surface-3 #282d39  hover, inset wells, table zebra.
     --surface-4 #323845  chips, table headers, the topmost material.

     --text-strong #f0f2f6  headings and anything that must be read first.
     --text        #dadee6  running text.                     13.8:1 on ground
     --text-dim    #8b92a1  captions, meta, secondary.          6.0:1 on ground
     --text-faint  #7c8392  the disclaimer and source notes.    4.9:1 on ground

     --edge        #6e7686  the border of an INTERACTIVE control. 4.1:1 on the
                            ground, 3.4:1 on the lightest surface — the 3:1
                            floor WCAG 1.4.11 sets for a non-text UI boundary.
     --hair / --hair-strong  decorative dividers between things that already
                            have their own background. Deliberately neutral:
                            the old sheet drew every hairline in translucent
                            gold, which is most of why the page read as
                            monochrome.

   ── 2. PRIMARY ACCENT: GOLD ─────────────────────────────────────────────────
   Kept, because it is the brand — but its job is now NARROW. Gold means: the
   wordmark, the primary call to action, a number worth reading, and the
   brand's own flourishes. It is no longer the colour of headings, links,
   borders or bold text.

     --gold-100 #fcf0cf   --gold-300 #f5dc98   --gold-500 #dfbf66  (the brand)
     --gold-600 #c7a74d   --gold-700 #a58938   --gold-900 #564614
     --gold-ink #17130a   text ON a gold button.        10.9:1 on --gold-500

   ── 3. SECONDARY ACCENT: FROST ──────────────────────────────────────────────
   Gold's opposite temperature, and the site's INTERACTIVE colour. Frost means
   exactly one thing wherever it appears: you can act on this. Links in running
   text, secondary buttons, the focus ring. It never decorates.

     --frost-300 #aae4ff  hover        --frost-400 #85cef9  link  10.8:1 ground
     --frost-600 #4a9dc5  pressed / underline at rest

   ── 4. SEMANTIC COLOURS — AND THEY ARE NOT THE ACCENT ───────────────────────
   The old sheet had no semantic layer at all: a warning callout was gold with
   a slightly browner bar. Four colours, none of which is gold or frost, so a
   state can never be mistaken for an emphasis.

     --ok     #67d89c  confirmed, live, safe        10.5:1 on ground
     --warn   #f6b45c  caution, stage notice        10.3:1 on ground
     --danger #f98078  destructive, broken, locked   7.4:1 on ground
     --info   #90cdf1  neutral aside                10.8:1 on ground

   ── 5. SECTION IDENTITY: THE TEMPER COLOURS ─────────────────────────────────
   Six areas of the site, six tones, ONE family — because the family is real
   and not a hand-wave. Steel run through a fire changes colour as it heats
   (straw, bronze, violet, blue) and copper changes colour as it ages
   (verdigris). Those are the six tones below, and they are siblings by
   construction: every one is generated at exactly the same OKLCH lightness
   (0.815) and near-identical chroma (0.10–0.115). Only the hue differs. That
   is what stops six colours reading as six decisions.

     --brass     #dfbf66  CORE — home, about, FAQ, world, press, legal
     --copper    #fbae86  NEWSROOM — news, updates, roadmap (heat, freshly struck)
     --verdigris #80d8ae  GUIDES & ARTICLES — the aged, standing archive
     --cyan      #6ad6dc  FREE TOOLS — a bright instrument in a workshop
     --periwinkle #aebdff  GAME DATABASE — cold and exact
     --heather   #e9aae3  FAN HUB — quizzes and diversions, and they know it

   A section tone gets FIVE jobs and no more: the eyebrow above a heading, the
   rule under a page title, a card's top edge, a callout's spine, and a faint
   wash at the top of the page. It is never the body text, never a link, never
   a button. Gold and frost appear on every page regardless, and that is the
   thread that keeps the whole thing one site.

   HOW A PAGE KNOWS WHICH SECTION IT IS IN, without touching 260 files: every
   page already carries a <link rel="canonical">, and its path says where the
   page lives. `html:has(link[rel=canonical][href*="/guides/"])` reads it. A
   browser without :has() simply gets the default brass, which is the site as
   it looked before — degradation costs nothing.

   ── 6. THE SCHEME IS DECLARED, AND IT IS DARK ───────────────────────────────
   The sheet was dark-only and did not declare color-scheme at all, so a visitor
   whose system is set to light got dark pages with white scrollbars and white
   form fields — the browser drawing its own widgets for a theme the page was
   not using. `color-scheme: dark` above fixes that.

   Dark is unconditional on purpose: it is the brand, not a preference. A full
   light palette was built and then removed rather than left wired to the
   operating system — see the note at the foot of this file for where it lives
   and how to restore it behind a real toggle.
   ═══════════════════════════════════════════════════════════════════════════ */

/* font-weight is declared as a RANGE because both files are variable-weight
   (see fonts/cinzel/README.md). Without it the browser assumes a single 400
   master and SYNTHESISES bold by smearing the outline sideways — which is
   exactly what every 700-weight heading and button on this site was getting. */
@font-face { font-family: "Cinzel"; src: url("/fonts/cinzel/cinzel-latin.woff2") format("woff2"); font-weight: 400 700; font-display: swap; }
@font-face { font-family: "Cinzel Decorative"; src: url("/fonts/cinzel/cinzel-decorative-latin.woff2") format("woff2"); font-weight: 400 700; font-display: swap; }

:root {
  /* DARK IS THE DEFAULT AND AN OS PREFERENCE DOES NOT OVERRIDE IT.
     This read `dark light`, which told the browser either theme was acceptable
     and handed the choice to the visitor's system — so a laptop set to light
     mode was served a pale Iron Realms. The dark ground is this game's brand,
     not a preference. It still declares a scheme (rather than nothing at all,
     which was the original bug) so the browser paints its own scrollbars and
     form fields dark to match. */
  color-scheme: dark;

  /* ── Neutral ramp (OKLCH hue 265°, chroma tapering to the light end) ───── */
  --ink-900: #090c12;
  --ink-800: #0f131a;
  --ink-700: #141822;
  --surface-1: #171b24;
  --surface-2: #1f242d;
  --surface-3: #282d39;
  --surface-4: #323845;

  --text-strong: #f0f2f6;
  --text: #dadee6;
  --text-dim: #8b92a1;
  /* 4.9:1 on the ground and 4.5:1 on the lightest card surface. The obvious
     value here is one step further down the ramp, and it measures 4.08:1 —
     which is what the standing disclaimer and the database's source notes were
     actually being set in. "Faint" is a role, not a licence to fail. */
  --text-faint: #7c8392;

  --edge: #6e7686;
  --edge-soft: #545b6a;
  --hair: rgba(174, 186, 214, 0.13);
  --hair-strong: rgba(174, 186, 214, 0.26);

  /* ── Primary accent: gold. Brand, primary action, numbers. ─────────────── */
  --gold-100: #fcf0cf;
  --gold-300: #f5dc98;
  --gold-500: #dfbf66;
  --gold-600: #c7a74d;
  --gold-700: #a58938;
  --gold-900: #564614;
  --gold-ink: #17130a;

  /* ── Secondary accent: frost. Interactive, and only interactive. ───────── */
  --frost-300: #aae4ff;
  --frost-400: #85cef9;
  --frost-600: #4a9dc5;

  /* ── Semantics. None of these is gold or frost. ────────────────────────── */
  --ok: #67d89c;
  --warn: #f6b45c;
  --danger: #f98078;
  --info: #90cdf1;
  /* The matching fills. Written out rather than mixed at runtime so a browser
     without color-mix() still gets a tinted callout instead of a bare one. */
  --ok-wash: rgba(103, 216, 156, 0.10);
  --warn-wash: rgba(246, 180, 92, 0.10);
  --danger-wash: rgba(249, 128, 120, 0.10);
  --info-wash: rgba(144, 205, 241, 0.10);

  /* ── Section tones. Same OKLCH lightness and chroma; hue is the variable. */
  --brass: #dfbf66;
  --copper: #fbae86;
  --verdigris: #80d8ae;
  --cyan: #6ad6dc;
  --periwinkle: #aebdff;
  --heather: #e9aae3;
  --tone: var(--brass);          /* whichever of the six this page belongs to */
  --tone-wash: rgba(223, 191, 102, 0.075);
  --tone-edge: rgba(223, 191, 102, 0.36);

  /* ── Legacy names, kept because ~260 pages and five section stylesheets
        already consume them. Retargeting these is how a colour change reaches
        every page without editing one of them. ──────────────────────────── */
  --gold: var(--gold-500);
  --gold-bright: var(--gold-300);
  --gold-dim: var(--gold-700);
  --ink: var(--ink-800);
  --panel: var(--surface-1);
  --panel-2: var(--surface-2);
  --muted: var(--text-dim);
  --line: var(--hair);
  --ember: var(--copper);

  --display: "Cinzel", "Iowan Old Style", Palatino, Georgia, serif;
  --fancy: "Cinzel Decorative", "Cinzel", serif;
  --body: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --maxw: 1080px;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0; color: var(--text); font-family: var(--body); line-height: 1.65;
  background:
    radial-gradient(1200px 600px at 50% -10%, #1c2534 0%, rgba(20,25,37,0) 60%),
    linear-gradient(180deg, #0b0f16, #0c1017 60%);
  min-height: 100vh;
}
/* A link is frost, and frost is only ever a link. It carries a resting
   underline at low contrast rather than none at all: an underline that only
   appears on hover is invisible to anyone not using a mouse, and colour alone
   is not an acceptable way to mark a link inside a paragraph. */
a { color: var(--frost-400); text-decoration: none; }
a:hover { text-decoration: underline; }
/* height:auto is load-bearing: our <img> carry width/height attributes, and on
   iOS WebKit (Safari AND Chrome) a width constraint without an explicit
   height:auto makes the literal height attribute win over the intrinsic
   aspect-ratio — the image renders stretched. Never drop the height:auto. */
img { max-width: 100%; height: auto; }

/* Top bar */
.nav {
  position: sticky; top: 0; z-index: 30; backdrop-filter: blur(10px);
  background: linear-gradient(180deg, rgba(12,16,23,0.92), rgba(12,16,23,0.72));
  border-bottom: 1px solid var(--line);
}
.nav-inner { max-width: var(--maxw); margin: 0 auto; padding: 12px 20px; display: flex; align-items: center; gap: 20px; }
.brand { font-family: var(--fancy); font-size: 20px; color: var(--gold); letter-spacing: 0.5px; white-space: nowrap; }
.brand span { color: var(--gold-bright); }
.nav-links { display: flex; gap: 18px; flex-wrap: wrap; margin-left: auto; }
.nav-links a { color: var(--text); font-size: 14px; }
.nav-links a:hover { color: var(--gold-bright); text-decoration: none; }
.nav-cta {
  font-family: var(--display); font-weight: 700; font-size: 14px;
  background: linear-gradient(180deg, var(--gold-bright), var(--gold-dim));
  color: #241a06 !important; padding: 8px 16px; border-radius: 10px;
  border: 1px solid #7a5a22; box-shadow: inset 0 1px 0 rgba(255,248,220,0.6), 0 3px 8px rgba(0,0,0,0.4);
}
.nav-cta:hover { filter: brightness(1.06); text-decoration: none; }

/* Nav dropdowns — Community / Explore. Plain <details>/<summary>, deliberately
   not a JavaScript menu: every link inside is a real, always-present anchor,
   so a crawler or a browser with JS off reaches exactly the same pages a
   mouse does. It also means there is only ONE nav to keep in sync rather than
   a desktop version and a separate mobile one — the same markup collapses
   into a plain expanding section under the phone breakpoint below instead of
   needing its own component. */
/* A generic `details { border; background; box-shadow; margin; }` styles the
   FAQ accordion elsewhere on the site, and a bare-tag selector still beats a
   class for anything it does not explicitly name — every property here is a
   RESET of one of those, not a fresh design, or this would inherit an
   accordion card's chrome around two words in the nav bar. Same story for
   `details summary::before` and its "▸ "/"▾ " text markers: reset to nothing
   so this dropdown gets exactly one caret, the one drawn below. */
.nav-drop {
  position: relative; border: none; border-radius: 0; margin: 0;
  background: none; box-shadow: none; overflow: visible;
}
/* `details[open]` (tag+attribute) outweighs a plain class, so the open state
   needs its own reset too or the accordion's shadow reappears the moment this
   is expanded. */
.nav-drop[open] { box-shadow: none; }
.nav-drop > summary {
  list-style: none; cursor: pointer; color: var(--text); font-size: 14px;
  font-family: inherit; font-weight: 400;
  padding: 4px 2px; display: inline-flex; align-items: center; gap: 4px;
  -webkit-appearance: none; appearance: none; background: none; border: none;
  outline-offset: 2px;
}
.nav-drop > summary::-webkit-details-marker { display: none; }
.nav-drop > summary::before { content: none; }
.nav-drop > summary::marker { content: ""; }
/* `details[open] summary::before` (tag+attribute+tag) outweighs the reset two
   lines up (class+tag) the moment this is expanded — the open-state leading
   "▾ " reappeared in exactly that state and nowhere else, which is the tell.
   Matched here with the same attribute-selector shape so this wins instead. */
.nav-drop[open] > summary::before { content: none; }
.nav-drop > summary::after {
  content: ""; width: 0; height: 0; margin-top: 2px;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid currentColor; opacity: .6;
}
.nav-drop > summary:hover, .nav-drop[open] > summary { color: var(--gold-bright); }
.nav-drop-menu {
  position: absolute; top: calc(100% + 10px); left: 0; min-width: 190px;
  display: flex; flex-direction: column; gap: 2px; padding: 8px;
  background: var(--panel, #161b24); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: 0 12px 28px rgba(0,0,0,.45);
  z-index: 40;
}
.nav-drop-menu a { padding: 7px 10px; border-radius: 8px; font-size: 14px; color: var(--text); }
.nav-drop-menu a:hover { background: rgba(227,193,104,.08); color: var(--gold-bright); text-decoration: none; }

/* Layout */
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 32px 20px 64px; }
.crumbs { color: var(--muted); font-size: 13px; margin: 0 0 20px; }
.crumbs a { color: var(--muted); }

/* Text utilities — used by the legal, roadmap and news pages, and never
 * written until now. `--muted` existed as a colour VARIABLE, so `.muted`
 * looked defined at a glance and silently did nothing: every "muted small"
 * caption on those pages rendered at full body weight, which is the opposite
 * of what it was marked up to say. */
.lede { font-size: 18.5px; line-height: 1.62; color: var(--text); margin: 0 0 20px; }
.muted { color: var(--muted); }
.small { font-size: 13.5px; line-height: 1.6; }
.muted a { color: var(--muted); text-decoration: underline; }

/* Available to a screen reader, absent from the page. For headings a document
 * needs for its outline but a sighted reader does not need repeated back. */
.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;
}

/* Hero */
.hero { text-align: center; padding: 64px 20px 40px; max-width: 860px; margin: 0 auto; }
.hero h1 { font-family: var(--fancy); font-size: clamp(34px, 7vw, 62px); color: var(--gold); margin: 0 0 8px; line-height: 1.05; }
.hero .kicker { font-family: var(--display); color: var(--gold-bright); letter-spacing: 3px; text-transform: uppercase; font-size: 13px; }
.hero p.lead { font-size: 18px; color: var(--text); max-width: 640px; margin: 18px auto 28px; }
.cta-row { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
.btn {
  font-family: var(--display); font-weight: 700; font-size: 16px; padding: 13px 26px; border-radius: 12px; cursor: pointer;
  display: inline-block; border: 1px solid var(--line); color: var(--text); background: linear-gradient(180deg, #232a38, #141922);
}
.btn:hover { border-color: var(--gold); text-decoration: none; }
.btn.primary {
  background: linear-gradient(180deg, var(--gold-bright), var(--gold-dim)); color: #241a06; border-color: #7a5a22;
  box-shadow: inset 0 1px 0 rgba(255,248,220,0.6), 0 4px 12px rgba(0,0,0,0.45);
}
.btn.primary:hover { filter: brightness(1.06); }

/* Section headings. NOT gold — see the palette note at the top of this file.
   A heading earns its place through size, weight and space, and the section
   tone is carried by the eyebrow above it and the rule beneath it. When every
   heading was gold there was no way to make one of them matter more. */
h2 { font-family: var(--display); color: var(--text-strong); font-size: var(--fs-2xl); font-weight: 600; margin: var(--s7) 0 var(--s2); }
h3 { font-family: var(--display); color: var(--text-strong); font-size: var(--fs-xl); font-weight: 600; margin: var(--s6) 0 var(--s2); }
h4 { font-family: var(--display); color: var(--text-strong); font-size: var(--fs-lg); font-weight: 600; margin: var(--s5) 0 var(--s2); }
.section-sub { color: var(--text-dim); margin: 0 0 var(--s4); max-width: var(--measure); }

/* Cards grid */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; margin: 22px 0; }
.card {
  background: linear-gradient(180deg, var(--panel), var(--panel-2)); border: 1px solid var(--line);
  border-radius: 14px; padding: 20px; transition: border-color 0.2s, transform 0.2s;
}
.card:hover { border-color: var(--gold); transform: translateY(-2px); }
.card h3 { margin: 0 0 6px; }
.card p { color: var(--muted); font-size: 14px; margin: 0; }
.card .ico { font-size: 26px; }
a.card, a.card:hover { text-decoration: none; color: inherit; display: block; }

/* Prose */
.prose { max-width: 760px; }
.prose p, .prose li { font-size: 16px; }
.prose ul, .prose ol { padding-left: 22px; }
.prose li { margin: 6px 0; }
/* Bold inside running text is EMPHASIS, not decoration. Gold here meant every
   paragraph had three or four gold words in it, which is most of why the old
   pages read as one undifferentiated colour. */
.prose strong { color: var(--text-strong); font-weight: 650; }
.callout {
  border-left: 3px solid var(--tone); background: var(--tone-wash);
  padding: var(--s3) var(--s4); border-radius: 0 var(--r-sm) var(--r-sm) 0;
  margin: var(--s5) 0; color: var(--text);
}
/* The semantic variants take a SEMANTIC colour, not a warmer shade of the
   accent. A caution that is merely a browner gold is not a caution. */
.callout.warn { border-left-color: var(--warn); background: var(--warn-wash); }
.callout.danger { border-left-color: var(--danger); background: var(--danger-wash); }
.callout.good, .callout.ok { border-left-color: var(--ok); background: var(--ok-wash); }
.callout.info { border-left-color: var(--info); background: var(--info-wash); }

/* Table */
table { width: 100%; border-collapse: collapse; margin: var(--s5) 0; font-size: var(--fs-sm); }
th, td { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--hair); }
/* The header row is a SURFACE, not a colour of text. That is what makes a
   long table scannable — the eye reads the band, not eleven gold words. */
th {
  color: var(--text-strong); font-family: var(--display); font-weight: 600;
  background: var(--surface-3); border-bottom: 2px solid var(--tone);
  font-size: var(--fs-sm); letter-spacing: .02em;
}
tbody tr:nth-child(even) { background: rgba(174,186,214,0.022); }
tbody tr:hover { background: var(--tone-wash); }

/* FAQ */
details {
  border: 1px solid var(--hair); border-radius: var(--r-md); margin: var(--s2) 0;
  background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
  box-shadow: var(--lift), var(--e1); overflow: hidden;
  transition: border-color .2s, box-shadow .2s;
}
details:hover { border-color: var(--hair-strong); }
details[open] { box-shadow: var(--lift), var(--e2); }
details summary {
  cursor: pointer; padding: 14px 18px; font-family: var(--display); font-weight: 600;
  color: var(--text-strong); font-size: var(--fs-base); list-style: none;
}
details summary::-webkit-details-marker { display: none; }
/* The disclosure caret is the one place the section tone belongs on a summary
   row: it marks the affordance without recolouring the question itself. */
details summary::before { content: "▸ "; color: var(--tone); }
details[open] summary::before { content: "▾ "; }
details .answer { padding: 0 18px 16px; color: var(--text); max-width: var(--measure); }

/* Footer. The body gradient already runs the page down toward --ink-900, so
   the footer needs no background of its own — one that disagreed with the
   canvas would draw a visible seam under the disclaimer, which sits outside
   the <footer> element and cannot be given the same fill. */
.foot { border-top: 1px solid var(--hair); margin-top: var(--s8); }
.foot-inner { max-width: var(--maxw); margin: 0 auto; padding: var(--s7) var(--s5) var(--s5); color: var(--text-dim); font-size: var(--fs-sm); display: flex; gap: var(--s5); flex-wrap: wrap; justify-content: space-between; }
.foot a { color: var(--text-dim); }
.foot a:hover { color: var(--frost-300); }
.foot .cols { display: flex; gap: var(--s7); flex-wrap: wrap; }
.foot h4 {
  color: var(--text-strong); font-family: var(--display); font-weight: 600;
  font-size: var(--fs-xs); letter-spacing: .16em; text-transform: uppercase;
  margin: 0 0 var(--s3); padding-bottom: var(--s2);
  border-bottom: 1px solid var(--hair);
}
.foot ul { list-style: none; padding: 0; margin: 0; }
.foot li { margin: 5px 0; }
/* The build stamp sitebanner.js appends. `flex-basis: 100%` puts it on a line
   of its own rather than letting it compete with the copyright notice for the
   space-between row — it is a diagnostic for a bug report, not a footer link,
   and it should read as the quietest thing on the page. Selectable on purpose:
   the whole point is that a reader can copy it rather than transcribe it. */
.foot-version { flex-basis: 100%; color: var(--text-faint); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; -webkit-user-select: text; user-select: text; }
.disclaimer { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--s5) var(--s6); color: var(--text-faint); font-size: var(--fs-xs); line-height: 1.7; }

@media (max-width: 640px) {
  /* The nav used to hide every link but the CTA here, which left phone visitors
     with no way to reach the guides, world, forum or news at all — the footer
     was the only route, and only if you scrolled to the bottom of the page.
     Instead: let the links wrap onto a second line and stop the bar sticking,
     so a taller header costs nothing once you scroll past it. */
  .nav { position: static; }
  .nav-inner { padding: 10px 16px; gap: 10px 14px; flex-wrap: wrap; }
  .brand { font-size: 18px; }
  .nav-links { gap: 10px 14px; flex-wrap: wrap; width: 100%; margin-left: 0; align-items: center; }
  .nav-links a:not(.nav-cta) { font-size: 13.5px; padding: 2px 0; }
  .nav-cta { padding: 7px 13px; font-size: 13px; margin-left: auto; }
  /* The floating panel becomes a plain expanding section — a fixed-width
     popover this narrow would either overflow the viewport or need its own
     positioning logic, and an inline list is both simpler and the more
     natural touch pattern anyway. */
  .nav-drop { width: 100%; }
  .nav-drop > summary { width: 100%; justify-content: space-between; padding: 2px 0; font-size: 13.5px; }
  .nav-drop-menu {
    position: static; margin: 4px 0 2px; padding: 2px 0 2px 12px;
    background: transparent; border: none; box-shadow: none; width: auto;
  }
  .nav-drop-menu a { padding: 6px 0; font-size: 13.5px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   AAA REDESIGN LAYER — cinematic landing components + a polish pass over the
   shared chrome. Everything below overrides/extends the base above; inner
   guide pages inherit the upgraded nav, cards, tables and typography for free.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* Colour values here now point at the ramp declared at the top of this file
     rather than restating it — three separate :root blocks each holding their
     own idea of "the darkest ink" is how a palette stops being a palette. */
  --ink-0: var(--ink-900); --ink-1: var(--ink-800);
  /* A glass surface has to be a TINT OF THE GROUND, not a colour of its own,
     or every backdrop-filtered panel reads as a slightly different material. */
  --glass: rgba(31, 36, 45, 0.62);
  --maxw-wide: 1200px;
  --ease: cubic-bezier(.2,.7,.2,1);
}
body {
  background:
    radial-gradient(1400px 700px at 50% -12%, #1b2637 0%, rgba(20,25,37,0) 55%),
    radial-gradient(900px 500px at 90% 20%, rgba(120,70,40,0.10), transparent 60%),
    linear-gradient(180deg, var(--ink-1), var(--ink-0) 70%);
  -webkit-font-smoothing: antialiased;
}

/* ── Nav: glass, crest, scroll-aware ─────────────────────────────────────── */
.nav { transition: background .3s var(--ease), border-color .3s; }
.nav-inner { max-width: var(--maxw-wide); }
.brand { display: inline-flex; align-items: center; gap: 9px; font-size: 21px; }
/* The crest slot has been here all along, showing /icon-192.png — the generic
   PWA app icon, not a brand mark. It now shows the actual mark, which is the
   whole of "wire the logo into the nav": one rule, every page, no HTML touched.
   SVG rather than the PNG because this is a 26px box on a display that may be
   3x, and the raster was being downscaled from 192 to 26 and blurring. */
.brand::before {
  content: ""; width: 26px; height: 26px; flex: 0 0 auto; border-radius: 6px;
  background: url("/brand/mark.svg") center/contain no-repeat;
  filter: drop-shadow(0 1px 3px rgba(0,0,0,.6));
}
.nav-links a:not(.nav-cta) { position: relative; padding: 4px 2px; transition: color .2s; }
.nav-links a:not(.nav-cta)::after {
  content: ""; position: absolute; left: 0; right: 100%; bottom: -3px; height: 2px;
  background: linear-gradient(90deg, var(--gold), var(--gold-bright)); transition: right .25s var(--ease);
}
.nav-links a:not(.nav-cta):hover::after { right: 0; }

/* ── Cinematic masthead ──────────────────────────────────────────────────── */
.masthead {
  position: relative; overflow: hidden; isolation: isolate;
  min-height: min(88vh, 760px); display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 80px 20px 96px;
}
.masthead::before {          /* the live gameplay backdrop */
  content: ""; position: absolute; inset: 0; z-index: -3;
  background: url("img/hero-world.jpg") center 30%/cover no-repeat;
  /* AVIF first, WebP next, JPEG last. The plain url() line above is the
     fallback for engines without image-set() type() support; they ignore
     this declaration entirely and keep the JPEG. See
     scripts/build-site-images.mjs for why the originals stay. */
  background: image-set(
      url("img/hero-world.avif") type("image/avif"),
      url("img/hero-world.webp") type("image/webp"),
      url("img/hero-world.jpg") type("image/jpeg")) center 30%/cover no-repeat;
  transform: scale(1.08); animation: heroPan 32s ease-in-out infinite alternate;
}
.masthead::after {           /* cinematic scrim for legibility */
  content: ""; position: absolute; inset: 0; z-index: -2;
  background:
    radial-gradient(120% 90% at 50% 30%, rgba(7,10,16,.30), rgba(7,10,16,.86) 78%),
    linear-gradient(180deg, rgba(7,10,16,.55) 0%, rgba(7,10,16,.15) 35%, rgba(7,10,16,.92) 100%);
}
@keyframes heroPan { from { transform: scale(1.08) translate3d(-1.5%,-1%,0); } to { transform: scale(1.14) translate3d(1.5%,1.5%,0); } }
.mast-inner { max-width: 900px; position: relative; z-index: 1; }
.mast-kicker {
  font-family: var(--display); color: var(--gold-bright); letter-spacing: 4px; text-transform: uppercase;
  font-size: 13px; display: inline-flex; align-items: center; gap: 12px; margin-bottom: 18px;
}
.mast-kicker::before, .mast-kicker::after { content: ""; width: 34px; height: 1px; background: linear-gradient(90deg, transparent, var(--gold)); }
.mast-kicker::after { background: linear-gradient(90deg, var(--gold), transparent); }
.masthead h1 {
  font-family: var(--fancy); color: #fff; margin: 0 0 4px; line-height: .98;
  font-size: clamp(44px, 9vw, 104px); letter-spacing: 1px;
  text-shadow: 0 2px 40px rgba(0,0,0,.6), 0 0 60px rgba(227,193,104,.12);
}
.masthead h1 .g { color: var(--gold); }
.mast-sub { font-size: clamp(17px, 2.4vw, 22px); color: #e7ecf3; max-width: 640px; margin: 18px auto 30px; text-shadow: 0 1px 12px rgba(0,0,0,.7); }
.mast-cta { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
/* The realm's population, under the masthead buttons. Reads as a living stat
   rather than a badge: a soft pulsing dot, the figure in gold, the rest quiet. */
.mast-pop {
  margin-top: 18px; color: #cdd6e4; font-size: 14.5px; letter-spacing: .3px;
  display: inline-flex; align-items: center; gap: 9px;
}
.mast-pop b { color: var(--gold-bright); font-weight: 700; font-variant-numeric: tabular-nums; font-size: 17px; }
.pop-dot {
  width: 8px; height: 8px; border-radius: 50%; background: #59d98a;
  box-shadow: 0 0 0 0 rgba(89,217,138,.65); animation: pop-pulse 2.4s ease-out infinite;
}
@keyframes pop-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(89,217,138,.6); }
  70%  { box-shadow: 0 0 0 9px rgba(89,217,138,0); }
  100% { box-shadow: 0 0 0 0 rgba(89,217,138,0); }
}
/* A pulsing dot is decoration, not information — hold it still for anyone who
   has asked the system for less motion. */
@media (prefers-reduced-motion: reduce) { .pop-dot { animation: none; } }
.mast-note { margin-top: 22px; color: #b9c3d2; font-size: 13.5px; letter-spacing: .3px; }
.mast-note b { color: var(--gold-bright); font-weight: 700; }
/* Drifting embers */
.embers { position: absolute; inset: 0; z-index: -1; pointer-events: none; overflow: hidden; }
.ember { position: absolute; bottom: -12px; width: 3px; height: 3px; border-radius: 50%;
  background: radial-gradient(circle, #ffd98a, rgba(255,150,60,.5) 60%, transparent 70%);
  box-shadow: 0 0 8px 1px rgba(255,180,90,.5); opacity: 0; animation: ember linear infinite; }
@keyframes ember { 0%{ transform: translateY(0) translateX(0) scale(1); opacity:0; } 12%{opacity:.9;} 100%{ transform: translateY(-78vh) translateX(28px) scale(.4); opacity:0; } }
.ember:nth-child(1){left:6%;animation-duration:11s;animation-delay:0s}
.ember:nth-child(2){left:16%;animation-duration:14s;animation-delay:2s}
.ember:nth-child(3){left:27%;animation-duration:9s;animation-delay:1s}
.ember:nth-child(4){left:38%;animation-duration:13s;animation-delay:4s}
.ember:nth-child(5){left:49%;animation-duration:10s;animation-delay:2.5s}
.ember:nth-child(6){left:59%;animation-duration:15s;animation-delay:.5s}
.ember:nth-child(7){left:70%;animation-duration:12s;animation-delay:3s}
.ember:nth-child(8){left:80%;animation-duration:9.5s;animation-delay:1.8s}
.ember:nth-child(9){left:90%;animation-duration:13.5s;animation-delay:3.6s}
.ember:nth-child(10){left:34%;animation-duration:16s;animation-delay:5s}
.ember:nth-child(11){left:64%;animation-duration:11.5s;animation-delay:6s}
.ember:nth-child(12){left:12%;animation-duration:12.5s;animation-delay:4.4s}

/* ── Full-bleed section bands ────────────────────────────────────────────── */
.band { padding: 72px 20px; }
.band.alt { background: linear-gradient(180deg, rgba(255,255,255,.015), rgba(255,255,255,.015)), rgba(10,14,21,.5); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.band .inner { max-width: var(--maxw-wide); margin: 0 auto; }
.band-head { text-align: center; max-width: 720px; margin: 0 auto 34px; }
.band-head h2 { font-family: var(--fancy); color: var(--gold); font-size: clamp(28px, 4.5vw, 44px); margin: 0 0 10px; }
.band-head p { color: var(--muted); font-size: 17px; margin: 0; }
.eyebrow { font-family: var(--display); color: var(--gold-dim); letter-spacing: 3px; text-transform: uppercase; font-size: 12px; }

/* ornate divider */
.rule { display: flex; align-items: center; justify-content: center; gap: 14px; color: var(--gold-dim); margin: 6px 0 30px; }
.rule::before, .rule::after { content: ""; height: 1px; width: min(160px, 22vw); background: linear-gradient(90deg, transparent, var(--gold-dim)); }
.rule::after { background: linear-gradient(90deg, var(--gold-dim), transparent); }

/* ── Pillars (value props) ───────────────────────────────────────────────── */
.pillars { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
@media (max-width: 820px){ .pillars { grid-template-columns: 1fr; } }
.pillar { text-align: center; padding: 30px 22px; border-radius: 16px;
  background: var(--glass); border: 1px solid var(--line); backdrop-filter: blur(8px); }
.pillar .pico { font-size: 34px; display: block; margin-bottom: 12px; filter: drop-shadow(0 3px 8px rgba(0,0,0,.4)); }
.pillar h3 { font-family: var(--fancy); color: var(--gold-bright); margin: 0 0 8px; font-size: 21px; }
.pillar p { color: var(--muted); font-size: 14.5px; margin: 0; }

/* ── Stat strip ──────────────────────────────────────────────────────────── */
/* STYLED SINCE BEFORE THE HOME PAGE EXISTED, AND RENDERED BY NOTHING UNTIL
   NOW. Five rules and a breakpoint for a component no .html file in the repo,
   hand-written or generated, ever emitted. Dead CSS is the quietest defect
   there is — it costs no bytes a reader notices, breaks no layout, and shows
   up in no screenshot; the only way to find it is to go looking for a
   component that is styled and never used. Wired up rather than deleted, which
   is the standing call, and it happens to be exactly the strip the front door
   was missing.
   The column count is auto-fit rather than a literal 4, because the number of
   facts the game can state is not a property of the stylesheet — it was four
   when this was drawn and it is five now. */
.statstrip { display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); gap: 16px; }
@media (max-width: 720px){ .statstrip { grid-template-columns: repeat(2,1fr); } }
.stat { text-align: center; padding: 16px 10px; }
.stat b { display: block; font-family: var(--fancy); color: var(--gold); font-size: clamp(28px, 4.5vw, 40px); line-height: 1; }
.stat span { color: var(--muted); font-size: 13px; text-transform: uppercase; letter-spacing: 1px; }
/* A FIGURE A READER CANNOT CHECK IS A BOAST. Each stat is an anchor to the
   page that proves it, so the strip is navigation as well as evidence — and
   the hover state has to say so, because a link that looks exactly like a
   label is a link nobody presses. */
a.stat { text-decoration: none; border-radius: var(--r-md); transition: background .18s, transform .18s; }
a.stat:hover, a.stat:focus-visible { background: rgba(227,193,104,.07); transform: translateY(-2px); }
a.stat:hover span, a.stat:focus-visible span { color: var(--gold-bright); }

/* ── The live-now strip, directly under the masthead ─────────────────────── */
/* A rule above and below and nothing else: it is a caption on the hero, not a
   section of the page, and giving it band padding would make it one. */
.livestrip { border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  background: rgba(10,14,21,.5); }
.livestrip .inner { max-width: var(--maxw-wide); margin: 0 auto; padding: 8px 20px;
  display: flex; align-items: center; gap: 26px; flex-wrap: wrap; }
.livestrip-label { font-family: var(--display); color: var(--gold-bright); letter-spacing: 3px;
  text-transform: uppercase; font-size: 11px; margin: 0; white-space: nowrap; }
.livestrip .statstrip { flex: 1 1 520px; }
@media (max-width: 720px){
  /* The label stops being a gutter and becomes a heading — at 390 it would
     otherwise take a third of the row and leave the figures two-up in a
     column too narrow to read. */
  .livestrip .inner { gap: 10px; }
  .livestrip-label { flex: 1 1 100%; }
}

/* ── Showcase (image + text split) ───────────────────────────────────────── */
.showcase { display: grid; grid-template-columns: 1.1fr .9fr; gap: 40px; align-items: center; }
.showcase.flip { grid-template-columns: .9fr 1.1fr; }
.showcase.flip .sc-media { order: 2; }
@media (max-width: 860px){ .showcase, .showcase.flip { grid-template-columns: 1fr; gap: 24px; } .showcase.flip .sc-media { order: 0; } }
.sc-media { position: relative; border-radius: 16px; overflow: hidden; border: 1px solid var(--line);
  box-shadow: 0 24px 60px rgba(0,0,0,.5), inset 0 0 0 1px rgba(255,255,255,.04); }
.sc-media img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; }
.sc-media::after { content: ""; position: absolute; inset: 0; box-shadow: inset 0 -60px 80px rgba(7,10,16,.5); pointer-events: none; }
.sc-body h3 { font-family: var(--fancy); color: var(--gold); font-size: clamp(24px, 3.4vw, 34px); margin: 0 0 12px; }
.sc-body p { color: var(--text); font-size: 16px; }
.sc-body .lead2 { color: var(--muted); }
.sc-list { list-style: none; padding: 0; margin: 14px 0 0; }
.sc-list li { padding: 7px 0 7px 28px; position: relative; color: var(--text); }
.sc-list li::before { content: "❖"; position: absolute; left: 0; color: var(--gold); }

/* ── Gameplay gallery ────────────────────────────────────────────────────── */
.gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 820px){ .gallery { grid-template-columns: 1fr; } }
.shot { position: relative; border-radius: 14px; overflow: hidden; border: 1px solid var(--line); box-shadow: 0 14px 34px rgba(0,0,0,.45); }
.shot img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; transition: transform .5s var(--ease); }
.shot:hover img { transform: scale(1.05); }
.shot figcaption { position: absolute; left: 0; right: 0; bottom: 0; padding: 26px 14px 12px; font-size: 13.5px; color: #eef2f7;
  background: linear-gradient(180deg, transparent, rgba(7,10,16,.85)); }
.shot figcaption b { color: var(--gold-bright); }
/* The 2.0 frames on the front page. A phone capture is portrait and the
   .shot rule above crops everything to 16:9, which would cut a phone screen
   down to its middle third; so the phone sits in a narrow column beside the
   desktop keep and fills that column's height instead. Below 820px both
   stack, and the phone frame is shown whole at a phone-like width. */
.g20 { display: grid; grid-template-columns: 3fr 1fr; gap: 16px; }
.g20.g20-row { grid-template-columns: 1fr 1fr; margin-top: 16px; }
/* A <figure>'s UA margin (40px a side) is what the gallery's grid never had to
   undo; here it pushed every frame off its column. And the frames are 16:10
   captures, so the 16:9 crop above would shave the HUD strip off the top and
   the dock off the bottom, the two things the frames are there to show. */
.g20 .shot { margin: 0; }
.g20 .shot img { aspect-ratio: 16 / 10; }
/* The phone frame must not set the row's height: its intrinsic portrait
   height is taller than the keep beside it, and the row stretched the keep's
   figure into a dark band under its image. Out of flow, it takes the height
   the keep gives the row and crops from the bottom. */
.g20 .shot-phone picture { position: absolute; inset: 0; display: block; }
.g20 .shot-phone img { aspect-ratio: auto; height: 100%; object-fit: cover; object-position: top; }
@media (max-width: 820px) {
  .g20, .g20.g20-row { grid-template-columns: 1fr; }
  .g20 .shot-phone { max-width: 300px; justify-self: center; }
  .g20 .shot-phone picture { position: static; }
  .g20 .shot-phone img { height: auto; aspect-ratio: 585 / 1266; }
}
/* The one-line 2.0 announcement above the masthead kicker. 44px tall so it
   is a real touch target on a phone, not a line of small print. */
.mast-news {
  display: flex; width: fit-content; align-items: center; gap: 10px; min-height: 44px; padding: 8px 16px;
  margin-bottom: 18px; border-radius: 999px; border: 1px solid rgba(227,193,104,.55);
  background: rgba(14,12,8,.55); color: #f4ead2; font-size: 14.5px; text-decoration: none;
  max-width: 100%; box-sizing: border-box; text-align: left;
}
.mast-news b { color: #1a1206; background: var(--gold-bright); border-radius: 999px; padding: 2px 9px; font-size: 12px; letter-spacing: 1px; text-transform: uppercase; }
.mast-news:hover, .mast-news:focus-visible { border-color: var(--gold-bright); color: #fff; }

/* ── Card grid polish (also lifts every guide hub card) ──────────────────── */
.grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.card { position: relative; background: var(--glass); backdrop-filter: blur(6px); border-radius: 16px; overflow: hidden;
  transition: transform .25s var(--ease), border-color .25s, box-shadow .25s; }
.card::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 2px; background: linear-gradient(90deg, transparent, var(--gold), transparent); opacity: 0; transition: opacity .25s; }
.card:hover { border-color: var(--gold); transform: translateY(-4px); box-shadow: 0 18px 40px rgba(0,0,0,.5); }
.card:hover::before { opacity: 1; }
.card .ico { font-size: 30px; filter: drop-shadow(0 2px 5px rgba(0,0,0,.4)); }
.card h3 { font-family: var(--fancy); }

/* ── Buttons: shimmer + large ────────────────────────────────────────────── */
.btn { position: relative; overflow: hidden; letter-spacing: .3px; transition: transform .15s var(--ease), filter .2s, border-color .2s; }
.btn:hover { transform: translateY(-2px); }
.btn.primary::after { content: ""; position: absolute; top: 0; left: -60%; width: 40%; height: 100%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.55), transparent); transform: skewX(-18deg); animation: sheen 4.5s ease-in-out infinite; }
@keyframes sheen { 0%,60%{ left:-60%; } 80%,100%{ left:130%; } }
.btn.lg { font-size: 18px; padding: 16px 34px; border-radius: 14px; }
.btn.ghost { background: rgba(255,255,255,.04); border-color: rgba(255,255,255,.22); color: #fff; }
.btn.ghost:hover { border-color: var(--gold); background: rgba(227,193,104,.10); }

/* ── Final CTA band ──────────────────────────────────────────────────────── */
.cta-final { position: relative; text-align: center; padding: 96px 20px; overflow: hidden; }
.cta-final::before { content: ""; position: absolute; inset: 0; z-index: -2; background: url("img/hero-map.jpg") center/cover; opacity: .5; transform: scale(1.05); }
/* AVIF/WebP override; the rule above stays as the fallback for engines
   without image-set() type() support. */
.cta-final::before { background: image-set(
    url("img/hero-map.avif") type("image/avif"),
    url("img/hero-map.webp") type("image/webp"),
    url("img/hero-map.jpg") type("image/jpeg")) center/cover; }
.cta-final::after { content: ""; position: absolute; inset: 0; z-index: -1; background: radial-gradient(80% 100% at 50% 50%, rgba(7,10,16,.6), rgba(7,10,16,.94)); }
.cta-final h2 { font-family: var(--fancy); color: var(--gold); font-size: clamp(30px, 5vw, 52px); margin: 0 0 12px; }
.cta-final p { color: #dbe2ec; max-width: 560px; margin: 0 auto 26px; font-size: 17px; }

@media (prefers-reduced-motion: reduce) {
  .masthead::before, .ember, .btn.primary::after { animation: none !important; }
}
@media (max-width: 640px) {
  .band { padding: 52px 18px; }
  .masthead { min-height: 78vh; }
}

/* ── Community forum ──────────────────────────────────────────────────────
   Board chrome. Everything here is layout only — the forum client builds its
   DOM with textContent, so no style hook depends on user-supplied markup. */
.f-topbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.f-title { font-family: var(--fancy); color: var(--gold); font-size: clamp(28px, 4.5vw, 40px); margin: 0 0 6px; }
.f-badge { display: inline-block; margin-left: 8px; padding: 2px 8px; border-radius: 999px; font-size: 11px;
  letter-spacing: .5px; text-transform: uppercase; background: rgba(227,193,104,.16); color: var(--gold-bright);
  border: 1px solid var(--line); vertical-align: middle; font-family: var(--body); }
.f-badge.lock { background: rgba(255,255,255,.06); color: var(--muted); }

.f-cats, .f-threads { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; }
.f-cat, .f-thread {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 16px 18px; border-radius: 14px; text-decoration: none; color: inherit;
  background: var(--glass); border: 1px solid var(--line); transition: border-color .2s, transform .2s var(--ease);
}
.f-cat:hover, .f-thread:hover { border-color: var(--gold); transform: translateY(-2px); }
.f-cat-main, .f-thread-main { min-width: 0; }
.f-cat-main h3, .f-thread-main h3 { font-family: var(--fancy); color: var(--gold-bright); margin: 0 0 4px; font-size: 19px; }
.f-cat-main p, .f-thread-main p { margin: 0; font-size: 14px; }
.f-cat-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex: 0 0 auto; text-align: right; }
.f-cat-meta b { font-family: var(--fancy); color: var(--gold); font-size: 20px; line-height: 1; }
@media (max-width: 560px) {
  .f-cat, .f-thread { flex-direction: column; align-items: flex-start; gap: 8px; }
  .f-cat-meta { flex-direction: row; align-items: baseline; gap: 8px; text-align: left; }
}

.f-posts { display: flex; flex-direction: column; gap: 12px; margin: 18px 0; }
.f-post { padding: 16px 18px; border-radius: 14px; background: var(--glass); border: 1px solid var(--line); }
.f-post-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 8px;
  padding-bottom: 8px; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
.f-author { font-family: var(--fancy); color: var(--gold-bright); font-size: 16px; }
/* overflow-wrap is load-bearing: a post can be one 8,000-character "word". */
.fp-body { overflow-wrap: anywhere; }
.fp-body p { margin: 0 0 10px; }
.fp-body p:last-child { margin-bottom: 0; }

.f-composer { margin: 18px 0; padding: 16px 18px; border-radius: 14px;
  background: rgba(255,255,255,.03); border: 1px solid var(--line); }
.f-composer summary { cursor: pointer; font-family: var(--fancy); color: var(--gold-bright); font-size: 17px; }
.f-composer.open > form, .f-composer[open] > form { margin-top: 14px; }
.f-field { display: block; margin-bottom: 12px; }
.f-label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 5px;
  text-transform: uppercase; letter-spacing: .6px; }
.f-composer input[type="text"], .f-composer textarea {
  width: 100%; box-sizing: border-box; padding: 10px 12px; border-radius: 10px;
  background: rgba(7,10,16,.6); border: 1px solid var(--line); color: var(--text);
  font: inherit; font-size: 15px; resize: vertical;
}
.f-composer input[type="text"]:focus, .f-composer textarea:focus { outline: none; border-color: var(--gold); }
.f-composer .row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

.f-pager { display: flex; align-items: center; justify-content: center; gap: 14px; margin: 18px 0; flex-wrap: wrap; }

/* Leaderboards: the podium should look like a podium. A ranking table where
   first place is styled identically to fourteenth is a list, not a board. */
#lb-claimants tr.lb-top td, #lb-standings tr.lb-top td {
  background: rgba(224, 176, 74, 0.07);
  font-weight: 600;
}
#lb-claimants tr.lb-top:first-child td, #lb-standings tr.lb-top:first-child td {
  background: rgba(224, 176, 74, 0.14);
  color: #ffe9b0;
}
/* Wide tables must scroll inside themselves rather than pushing the page
   sideways — the claimants board gained two columns and phones are narrow. */
#lb-claimants, #lb-standings { overflow-x: auto; }

/* ═══════════════════════════════════════════════════════════════════════════
   DESIGN SYSTEM v2
   ───────────────────────────────────────────────────────────────────────────
   Appended rather than rewritten on purpose: ~58 pages share this sheet, and
   redefining the TOKENS lifts every one of them without touching their markup.
   New components are added below; nothing above is renamed.

   The brief was "a million-dollar consultancy site". That work does not look
   expensive because it is busy — it looks expensive because it is rigorous:
   one clear hierarchy, real numbers, disciplined whitespace, drawn icons
   instead of emoji, and no decoration that isn't carrying information.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* THE SURFACE AND ACCENT TOKENS THAT USED TO LIVE HERE HAVE MOVED to the
     documented palette at the top of this file. They were declared three
     times across this sheet — here, and twice above — and the last block to
     load quietly won. That is not a design system, it is a race. Everything
     below is scale, rhythm and elevation: the parts of the system that carry
     no colour and so belong wherever they are used. */

  /* ── Fluid type scale. Ratio ~1.25, clamped so nothing collapses on a phone
        or balloons on a 27" display. */
  --fs-xs: clamp(11px, 0.68rem + 0.1vw, 12.5px);
  --fs-sm: clamp(13px, 0.8rem + 0.1vw, 14.5px);
  --fs-base: clamp(15px, 0.93rem + 0.15vw, 17px);
  --fs-lg: clamp(17px, 1rem + 0.35vw, 20px);
  --fs-xl: clamp(21px, 1.15rem + 0.7vw, 27px);
  --fs-2xl: clamp(26px, 1.35rem + 1.3vw, 38px);
  --fs-3xl: clamp(32px, 1.5rem + 2.4vw, 56px);
  --fs-4xl: clamp(40px, 1.6rem + 4.6vw, 92px);

  /* ── 8pt spacing scale. Ad-hoc margins are why the old page had a hole
        between the hero and the pillars and none between a heading and its
        rule. */
  --s1: 4px;  --s2: 8px;   --s3: 12px;  --s4: 16px;
  --s5: 24px; --s6: 32px;  --s7: 48px;  --s8: 64px;
  --s9: 88px; --s10: 120px; --s11: 160px;

  /* ── Elevation. Each step is TWO shadows plus a light line along the top
        edge, because that is what a real material does: it occludes what is
        behind it AND catches the light above it. A single drop shadow is the
        thing that makes a card read as a rectangle drawn on a page rather
        than an object resting on one. */
  --lift: inset 0 1px 0 rgba(255,255,255,0.055);
  --e1: 0 1px 2px rgba(0,0,0,0.45), 0 1px 1px rgba(0,0,0,0.30);
  --e2: 0 2px 4px rgba(0,0,0,0.35), 0 8px 20px rgba(0,0,0,0.42);
  --e3: 0 4px 8px rgba(0,0,0,0.35), 0 22px 52px rgba(0,0,0,0.55);
  --glow: 0 0 0 1px var(--hair-strong), 0 10px 40px rgba(223,191,102,0.10);

  --maxw: 1200px;      /* 1080 was cramped on anything modern */
  /* 65ch is the target for running text. It is enforced on the prose column
     and on every paragraph inside a band or a card, not merely declared. */
  --measure: 65ch;
  --r-sm: 8px; --r-md: 12px; --r-lg: 18px; --r-xl: 26px;
}

body { font-size: var(--fs-base); background: var(--ink-800); color: var(--text); }

/* Focus that is visible on every interactive thing, without a browser default
   outline sitting awkwardly on a rounded gold button.

   THE RING IS FROST, NOT GOLD, and the change matters twice over. Frost is the
   site's interactive colour, so the ring says the same thing as the link it is
   sitting on; and gold-on-gold is 1.03:1, which means the focus state on the
   "Play Now" button — the single most important control here — was very nearly
   invisible on the one element that most needed it. */
a:focus-visible, button:focus-visible, .btn:focus-visible, .nav-cta:focus-visible {
  outline: 2px solid var(--frost-300);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

/* ── Section rhythm ─────────────────────────────────────────────────────── */
.band { padding: var(--s10) 0; }
.band .inner { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--s5); }
.band.alt { background: linear-gradient(180deg, var(--ink-700), var(--ink-800)); border-block: 1px solid var(--hair); }

/* Section headers: LEFT aligned, with the measure enforced. Centred paragraphs
   over a wide column are the fastest way to look like a template. */
.band-head { max-width: var(--measure); margin: 0 0 var(--s8); text-align: left; }
.band-head .rule { margin: var(--s4) 0 var(--s5); width: 64px; height: 2px;
  background: linear-gradient(90deg, var(--gold-500), transparent); }
.band-head p { color: var(--text-dim); font-size: var(--fs-lg); }
.eyebrow {
  font-family: var(--display); text-transform: uppercase; letter-spacing: 0.22em;
  font-size: var(--fs-xs); color: var(--gold-700); margin: 0 0 var(--s3);
}
h1, h2, h3 { color: var(--text-strong); line-height: 1.08; letter-spacing: -0.01em; }
.band-head h2 { font-family: var(--display); font-size: var(--fs-2xl); margin: 0; }

/* ── Cards with actual material ─────────────────────────────────────────── */
.card-v2 {
  background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border: 1px solid var(--hair);
  border-radius: var(--r-lg);
  padding: var(--s6);
  box-shadow: var(--e2);
  transition: transform 0.22s cubic-bezier(0.2,0.7,0.3,1), border-color 0.22s, box-shadow 0.22s;
}
.card-v2:hover { transform: translateY(-3px); border-color: var(--hair-strong); box-shadow: var(--e3); }
.card-v2 h3 { font-family: var(--display); font-size: var(--fs-lg); margin: var(--s4) 0 var(--s2); }
.card-v2 p { color: var(--text-dim); font-size: var(--fs-sm); margin: 0; line-height: 1.7; }

/* Drawn icons, not emoji. A stroked glyph in a tinted well. */
.ico-well {
  width: 46px; height: 46px; border-radius: var(--r-md);
  display: grid; place-items: center;
  background: radial-gradient(120% 120% at 30% 20%, rgba(227,193,104,0.16), rgba(227,193,104,0.04));
  border: 1px solid var(--hair);
}
.ico-well svg { width: 22px; height: 22px; stroke: var(--gold-300); fill: none;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

/* ── Evidence row — the "show the numbers" device ───────────────────────── */
.evidence { display: grid; gap: var(--s5); grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.ev { border-left: 2px solid var(--gold-700); padding-left: var(--s4); }
.ev b {
  display: block; font-family: var(--display); font-size: var(--fs-2xl);
  color: var(--gold-300); line-height: 1; letter-spacing: -0.02em;
}
.ev span { display: block; margin-top: var(--s2); color: var(--text-dim); font-size: var(--fs-sm); }
.ev small { display: block; margin-top: var(--s1); color: var(--text-dim); opacity: 0.75; font-size: var(--fs-xs); }

/* ── Editorial split: asymmetric, not a 50/50 slab ──────────────────────── */
.split { display: grid; gap: var(--s8); align-items: center; grid-template-columns: 1.05fr 0.95fr; }
.split.flip > :first-child { order: 2; }
.split-media { border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--hair); box-shadow: var(--e3); }
.split-media img { display: block; width: 100%; }
@media (max-width: 860px) {
  .split { grid-template-columns: 1fr; gap: var(--s6); }
  .split.flip > :first-child { order: 0; }
}

/* ── Scroll reveal. Opt-in via [data-reveal]; honours reduced motion. ────── */
[data-reveal] { opacity: 0; transform: translateY(18px); transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.2,0.7,0.3,1); }
[data-reveal].is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
}

/* ── Masthead v2 ─────────────────────────────────────────────────────────────
   The old hero laid a thin scrim over a raw gameplay screenshot and centred
   everything on it. Two problems, both fatal to a premium read:

   1. THE GAME'S OWN UI WAS VISIBLE IN IT — "Seats of Power", the season pill,
      keep tooltips, minimap buttons. A hero showing another interface's chrome
      instantly reads as a screenshot someone pasted in, because it is one. The
      fix is framing, not retouching: scale the backdrop up and shift it so the
      HUD (which lives at the edges) falls outside the frame, then vignette
      hard so whatever survives at the margins is crushed to black.

   2. Dead-centred text over a busy image has no anchor. An editorial column
      pinned left gives the eye somewhere to start and lets the artwork breathe
      on the right.
   ────────────────────────────────────────────────────────────────────────── */
.masthead { min-height: min(92vh, 900px); display: grid; align-items: center; }

.masthead::before {
  /* Crop past the HUD: 128% and pushed up-left so the game's own edge chrome
     sits outside the visible frame entirely. */
  transform: scale(1.28) translate(-3%, -6%);
  transform-origin: 50% 45%;
  filter: saturate(0.86) contrast(1.06) brightness(0.72);
}

.masthead::after {
  /* A real cinematic grade: a directional scrim for the text column, a floor
     gradient into the next section, and a vignette that eats the corners
     (where any surviving HUD lives). */
  background:
    linear-gradient(100deg, rgba(7,10,16,0.95) 0%, rgba(7,10,16,0.80) 34%, rgba(7,10,16,0.30) 62%, rgba(7,10,16,0.55) 100%),
    linear-gradient(180deg, rgba(7,10,16,0.85) 0%, rgba(7,10,16,0) 22%, rgba(7,10,16,0) 58%, var(--ink-800) 100%),
    radial-gradient(130% 92% at 50% 45%, rgba(0,0,0,0) 42%, rgba(0,0,0,0.72) 100%);
}

.mast-inner {
  max-width: var(--maxw); width: 100%; margin: 0 auto;
  padding: 0 var(--s5); text-align: left;
}
.mast-col { max-width: 620px; }

.mast-kicker { justify-content: flex-start; color: var(--gold-500); }
.mast-kicker::before { display: none; }   /* one flourish, not a pair around centred text */

.masthead h1 {
  font-size: var(--fs-4xl); letter-spacing: -0.02em; line-height: 0.92;
  text-shadow: 0 2px 40px rgba(0,0,0,0.85);
}
.mast-sub {
  margin: var(--s5) 0 var(--s6); max-width: 33rem;
  font-size: var(--fs-lg); line-height: 1.6; color: #dbe3ee;
}
.mast-cta { justify-content: flex-start; gap: var(--s3); }
.mast-pop, .mast-note { justify-content: flex-start; text-align: left; }
.mast-pop { margin-top: var(--s6); }
.mast-note { max-width: 34rem; }

/* A trust row under the fold-line: the three claims that actually decide
   whether someone clicks play, stated as facts rather than adjectives. */
.mast-trust {
  display: flex; flex-wrap: wrap; gap: var(--s5);
  margin-top: var(--s7); padding-top: var(--s5);
  border-top: 1px solid rgba(227,193,104,0.18);
  max-width: 40rem;
}
.mast-trust div { display: flex; align-items: center; gap: var(--s2); font-size: var(--fs-sm); color: #c3ccd9; }
.mast-trust svg { width: 16px; height: 16px; stroke: var(--gold-500); fill: none; stroke-width: 1.6; flex: none; }

@media (max-width: 720px) {
  .masthead { min-height: 88vh; }
  .masthead::before { transform: scale(1.5) translate(-2%, -4%); }
  .masthead::after {
    background:
      linear-gradient(180deg, rgba(7,10,16,0.72) 0%, rgba(7,10,16,0.86) 45%, var(--ink-800) 100%),
      radial-gradient(120% 80% at 50% 40%, rgba(0,0,0,0) 30%, rgba(0,0,0,0.7) 100%);
  }
  .mast-trust { gap: var(--s3); }
}

/* ── Card icons now hold drawn glyphs, not emoji ─────────────────────────── */
.card .ico {
  width: 44px; height: 44px; border-radius: var(--r-md);
  display: grid; place-items: center; color: var(--gold-300);
  background: radial-gradient(120% 120% at 30% 20%, rgba(227,193,104,0.15), rgba(227,193,104,0.035));
  border: 1px solid var(--hair);
  /* The redesign moved card icons to inline SVG and zeroed the font size to
   * kill the leftover emoji line-box. Only 15 cards were ever converted — the
   * other 90 across the site still carry an emoji, and this rule was rendering
   * every one of them as an empty box. Zero the font ONLY where there is
   * actually an SVG to show instead. */
  font-size: 22px; line-height: 1;
  transition: border-color 0.2s, transform 0.2s;
}
.card .ico:has(svg) { font-size: 0; }
.card .ico svg { width: 21px; height: 21px; }
.card:hover .ico { border-color: var(--hair-strong); transform: translateY(-1px); }

/* Cards as links need the material and the lift too. */
a.card { display: block; text-decoration: none; }
a.card:hover { text-decoration: none; }

/* ── Nav: condense on scroll so the hero owns the first screen ───────────── */
.nav { transition: padding 0.25s ease, background 0.25s ease, box-shadow 0.25s ease; }
.nav.is-scrolled {
  background: linear-gradient(180deg, rgba(7,10,16,0.97), rgba(7,10,16,0.90));
  box-shadow: var(--e2);
}
.nav.is-scrolled .nav-inner { padding-block: var(--s2); }
.nav-links a { position: relative; }
.nav-links a::after {
  content: ""; position: absolute; left: 0; right: 100%; bottom: -5px; height: 1px;
  background: var(--gold-500); transition: right 0.25s cubic-bezier(0.2,0.7,0.3,1);
}
.nav-links a:hover::after { right: 0; }
.nav-links a.nav-cta::after { display: none; }

/* ── Prose measure. Long body text at full container width is unreadable and
      is the other half of why the old page felt like a template. ─────────── */
.band p, .card p { max-width: var(--measure); }
.statstrip { max-width: none; }

/* ── The hero now uses a purpose-shot plate ──────────────────────────────────
   img/hero-plate.jpg is captured from the live game with photo-mode on and
   every DOM overlay removed — no HUD, no tooltips, no build ribbon. That is
   why the aggressive 1.28x crop above is no longer needed: there is nothing
   to crop past. A gentle scale is kept only to give the drift room to move
   without exposing an edge. */
.masthead::before {
  background: url("img/hero-plate.jpg") center 38%/cover no-repeat;
  /* AVIF first, WebP next, JPEG last. The plain url() line above is the
     fallback for engines without image-set() type() support; they ignore
     this declaration entirely and keep the JPEG. See
     scripts/build-site-images.mjs for why the originals stay. */
  background: image-set(
      url("img/hero-plate.avif") type("image/avif"),
      url("img/hero-plate.webp") type("image/webp"),
      url("img/hero-plate.jpg") type("image/jpeg")) center 38%/cover no-repeat;
  transform: scale(1.06);
  transform-origin: 55% 40%;
  filter: saturate(0.92) contrast(1.05) brightness(0.78);
  animation: heroDrift 46s ease-in-out infinite alternate;
}
@keyframes heroDrift {
  from { transform: scale(1.06) translate(0, 0); }
  to   { transform: scale(1.12) translate(-1.2%, -1.6%); }
}
@media (prefers-reduced-motion: reduce) {
  .masthead::before { animation: none; }
}
@media (max-width: 720px) {
  /* Portrait needs a tighter read on the keeps rather than the empty sward. */
  .masthead::before { background-position: 60% 30%; transform: scale(1.35); animation: none; }
}

/* The masthead is a grid whose single track was AUTO-sized, so
   `justify-content: center` centred the track itself — which is why the
   editorial column kept floating mid-canvas no matter what margin the inner
   wrapper was given. Make the track fill the viewport and the container's own
   max-width + auto margins do the centring, leaving the column pinned to the
   container's left edge where it belongs. */
.masthead { grid-template-columns: minmax(0, 1fr); justify-content: stretch; }
.mast-inner { margin-inline: auto; }

/* The trust row is a footnote to the whole hero, not to the text column, so it
   may run wider than the 620px measure — which stops the third item dropping
   to a line of its own. */
.mast-trust { max-width: 52rem; column-gap: var(--s6); }

/* ═══════════════════════════════════════════════════════════════════════════
   ANSWER BLOCKS & GUIDE FIGURES
   ───────────────────────────────────────────────────────────────────────────
   Every guide now opens with a short, self-contained Q&A pair-set that mirrors
   its FAQPage schema exactly. Two reasons it is VISIBLE and not schema-only:
   an answer block that disagrees with the page it sits on is a spam signal, and
   a reader who arrived from a search for "how long to max a keep" deserves the
   number above the fold rather than four sections down.
   ═══════════════════════════════════════════════════════════════════════════ */

.quick-answers {
  margin: var(--s6, 24px) 0 var(--s7, 32px);
  padding: var(--s5, 20px) var(--s6, 24px);
  background: linear-gradient(180deg, var(--surface-2, #1a2331), var(--surface-1, #141b26));
  border: 1px solid var(--hair, rgba(227,193,104,.16));
  border-left: 3px solid var(--gold-700, #b9922f);
  border-radius: 12px;
}
.quick-answers > h2 {
  margin: 0 0 var(--s4, 16px);
  font-size: var(--fs-lg, 1.05rem);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--gold-300, #f2dc9c);
  border: 0;
  padding: 0;
}
.qa { margin: 0; }
.qa dt {
  font-weight: 700;
  color: var(--text-strong, #eef2f8);
  margin-top: var(--s4, 16px);
}
.qa dt:first-of-type { margin-top: 0; }
.qa dd {
  margin: 6px 0 0;
  color: var(--text, #c9d2df);
  max-width: var(--measure, 66ch);
}

/* A figure is content, not decoration: it keeps its aspect box so the page
   never reflows as it loads, and it never runs wider than the column. */
.guide-figure {
  margin: var(--s6, 24px) 0;
  border: 1px solid var(--hair, rgba(227,193,104,.16));
  border-radius: 12px;
  overflow: hidden;
  background: var(--ink-800, #0b0f17);
}
.guide-figure img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
.guide-figure figcaption {
  padding: 10px var(--s4, 16px);
  font-size: var(--fs-sm, .86rem);
  color: var(--text-dim, #8b96a8);
  border-top: 1px solid var(--hair, rgba(227,193,104,.16));
}

/* The "Keep reading" block. Each row is a link plus the reason to follow it —
   a bare list of blue links tells a reader nothing about which one answers
   their question, and reads to a crawler exactly like a link farm. */
.related {
  margin: var(--s8, 40px) 0 var(--s4, 16px);
  padding-top: var(--s5, 20px);
  border-top: 1px solid var(--hair, rgba(227,193,104,.16));
}
.related > h2 {
  margin: 0 0 var(--s3, 12px);
  font-size: var(--fs-sm, .86rem);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-dim, #8b96a8);
  border: 0;
  padding: 0;
}
.related ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.related li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 10px;
  padding: 9px 0;
  border-bottom: 1px solid rgba(255, 255, 255, .05);
}
.related li:last-child { border-bottom: 0; }
.related li a { font-weight: 600; color: var(--gold-300, #f2dc9c); text-decoration: none; }
.related li a:hover { text-decoration: underline; }
.related li span { color: var(--text-dim, #8b96a8); font-size: var(--fs-sm, .86rem); }

/* Wide tables must scroll inside themselves, never push the page.
   The guides now carry real data tables — a 4-column caravan schedule, the
   terrain table, the garrison ladder — and at 390px the widest of them shoved
   the whole document 41px sideways. Making the table its own scroll container
   is the fix that does not require wrapping every table in markup, and it
   keeps the header row aligned with its columns. */
@media (max-width: 720px) {
  .prose table,
  .quick-answers + table {
    display: block;
    width: 100%;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    font-size: var(--fs-sm, .86rem);
  }
  .prose table th,
  .prose table td { white-space: nowrap; }

  /* A clipped table with no affordance just looks broken — the reader cannot
     tell there is more to the right. This right-edge shade rides the scroll
     (background-attachment: local) so it fades out exactly when the reader
     reaches the end, and disappears entirely on tables that already fit. */
  .prose table {
    background:
      linear-gradient(to left, var(--ink-800, #0b0f17), rgba(11, 15, 23, 0)) right center / 28px 100% no-repeat local;
  }
}

/* Alpha, built in public. Stated plainly on the pages where somebody forms
   a first impression, rather than only in footer small print — a visitor who
   discovers the build is unfinished AFTER investing an evening has been
   misled, however true the footer was. The class kept its pre-alpha name when
   2.0 moved the game to Alpha (14 Oct 2026): the newsroom's dated dispatches
   carry it too, and build-news-feed.mjs finds each dispatch's notice by it. */
.prealpha-note {
  margin: 14px 0 18px; padding: 12px 14px; border-radius: 10px;
  background: rgba(230,192,103,0.07);
  border: 1px solid rgba(230,192,103,0.28);
  border-left: 3px solid var(--gold, #c9a227);
  font-size: 14px; line-height: 1.55;
}

/* ═══════════════════════════════════════════════════════════════════════════
   DESIGN SYSTEM v3 — a few more degrees of "expensive"
   ───────────────────────────────────────────────────────────────────────────
   v2 built the rigor: real numbers, drawn icons, a disciplined grid. What was
   still missing is the part you feel before you've read a word. A flat CSS
   gradient reads as CSS; a few percent of grain reads as film. Appended, same
   as v2 — nothing above is renamed or removed.
   ═══════════════════════════════════════════════════════════════════════════ */

/* A whisper of film grain over the whole page. A flat dark gradient bands on
   a real display; the few percent of noise a camera sensor adds is exactly
   what makes a "black" background read as a material instead of a colour
   value — every AAA marketing site does this, usually with a texture image.
   This is an inline SVG instead, so it costs zero extra requests. Fixed and
   pointer-events:none: it sits over everything but never intercepts a click. */
body::after {
  content: "";
  position: fixed; inset: 0; z-index: 999; pointer-events: none;
  opacity: .05; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-reduced-motion: reduce), (max-width: 480px) {
  /* Texture, not a feature — the smallest screens have less paint budget to
     spend on it than it is worth, and a reduced-motion visitor asked this
     page to keep still, not to keep shimmering. */
  body::after { display: none; }
}

/* Big numbers get a little metal in them instead of flat gold — the two
   places on the page whose whole job is to look like a fact worth reading. */
.stat b, .ev b {
  background: linear-gradient(180deg, var(--gold-100, #fbf0cf) 0%, var(--gold-500, #e3c168) 55%, var(--gold-700, #b9922f) 100%);
  -webkit-background-clip: text; background-clip: text;
  color: var(--gold-500, #e3c168); -webkit-text-fill-color: transparent;
}

/* The primary button was the one interactive element with no ambient glow —
   every card and split-image already has ONE more shadow layer than a flat
   drop shadow (see --e3, --glow). This gives the button people actually
   press the same treatment, so it doesn't read like an older pass. */
.btn.primary {
  box-shadow:
    inset 0 1px 0 rgba(255,248,220,0.6),
    0 4px 12px rgba(0,0,0,0.45),
    0 0 32px rgba(227,193,104,0.14);
}
.btn.primary:hover {
  box-shadow:
    inset 0 1px 0 rgba(255,248,220,0.7),
    0 6px 18px rgba(0,0,0,0.5),
    0 0 44px rgba(227,193,104,0.22);
}

/* Same ambient glow on hover for both card families, so a hovered card and a
   hovered button feel like one material system. */
.card:hover { box-shadow: 0 18px 40px rgba(0,0,0,.5), 0 0 40px rgba(227,193,104,0.08); }
.card-v2:hover { box-shadow: var(--e3), 0 0 40px rgba(227,193,104,0.08); }
.prealpha-note strong { color: var(--gold, #c9a227); }

/* ── The operator's banner ────────────────────────────────────────────────
   Written from the admin console into the database and fetched by
   sitebanner.js; absent by default, so a page that shipped without one looks
   exactly as it always did. Full-bleed above the nav because it is a notice
   about the SITE, not about whichever page you happen to be reading. */
.site-banner {
  padding: 10px 20px; text-align: center;
  font-size: 14.5px; line-height: 1.5; font-weight: 600;
  background: rgba(227,193,104,0.14);
  color: var(--gold-100, #fbf0cf);
  border-bottom: 1px solid var(--hair-strong, rgba(227,193,104,0.34));
}
.site-banner a { color: inherit; text-decoration: underline; }
.site-banner.warn { background: rgba(216,112,63,0.18); color: #ffd9c4; border-bottom-color: rgba(216,112,63,0.5); }
.site-banner.good { background: rgba(89,217,138,0.15); color: #cdf5de; border-bottom-color: rgba(89,217,138,0.4); }

/* ── The consent banner ───────────────────────────────────────────────────
   Built by sitebanner.js's applyConsent(), and only for a geo-resolved EU/UK
   visitor with no answer on file — every other reader of this stylesheet
   never has this class in their DOM at all. Fixed to the BOTTOM, not pushed
   above the nav like .site-banner: this is a question being asked of one
   reader, not a notice about the site, and the two must never be confused
   for each other when both could theoretically be on screen at once. */
.consent-banner {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 500;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 12px 20px; padding: 14px 20px;
  background: var(--surface-2, #1f242d); color: var(--text, #dadee6);
  border-top: 1px solid var(--hair-strong, rgba(174,186,214,0.26));
  box-shadow: 0 -8px 24px rgba(0,0,0,.35);
}
.consent-banner p { margin: 0; max-width: 62ch; font-size: var(--fs-sm, 14px); line-height: 1.5; }
.consent-banner a { color: var(--gold-500, #dfbf66); text-decoration: underline; }
.consent-actions { display: flex; gap: 10px; flex-shrink: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   DESIGN SYSTEM v4 — THE COLOUR AND FINISH PASS
   ───────────────────────────────────────────────────────────────────────────
   v2 and v3 built rigour and texture on top of a palette that was never
   designed: one gold, one near-black, one grey. This layer spends the palette
   declared at the top of this file. It is appended rather than woven in for
   the same reason the earlier layers were — ~260 pages share this sheet and
   the safe way to reach all of them is to override, never to rename.

   Three things happen here, in this order:
     1. SECTION IDENTITY   — each area of the site picks up its temper tone.
     2. HIERARCHY          — the type scale is actually applied, and the
                             accent stops doing five jobs at once.
     3. FINISH             — material on cards, tables, callouts, code,
                             buttons, inputs, focus and hover.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. SECTION IDENTITY ──────────────────────────────────────────────────────
   No markup changes and no per-page stylesheet: every page already ships a
   <link rel="canonical">, and the path in it is the only fact needed to know
   which part of the site the reader is standing in. :has() reads it off the
   <head> and sets three custom properties on the root; everything downstream
   consumes --tone and never mentions a section by name.

   A browser without :has() (or a page that somehow lost its canonical) keeps
   the default brass and looks exactly like the site did before. There is no
   broken state to fall into, which is the only reason this is safe to hang a
   whole visual system on.
   ────────────────────────────────────────────────────────────────────────── */
html:has(link[rel="canonical"][href*="/guides/"]),
html:has(link[rel="canonical"][href*="/articles/"]) {
  --tone: var(--verdigris);
  --tone-wash: rgba(128, 216, 174, 0.075);
  --tone-edge: rgba(128, 216, 174, 0.36);
  --tone-veil: rgba(128, 216, 174, 0.05);
}
html:has(link[rel="canonical"][href*="/news/"]),
html:has(link[rel="canonical"][href$="/updates.html"]),
html:has(link[rel="canonical"][href$="/roadmap.html"]),
/* The Resident Blog is a publication, so it takes the newsroom's copper.
   Path-only and host-agnostic, like every rule here: the blog follows
   SITE_ORIGIN, and a host change must not silently drop the tone. No static
   canonical contains /blog (public/site has no blog directory). */
html:has(link[rel="canonical"][href$="/blog"]),
html:has(link[rel="canonical"][href*="/blog/"]) {
  --tone: var(--copper);
  --tone-wash: rgba(251, 174, 134, 0.075);
  --tone-edge: rgba(251, 174, 134, 0.36);
  --tone-veil: rgba(251, 174, 134, 0.05);
}
html:has(link[rel="canonical"][href*="/db/"]) {
  --tone: var(--periwinkle);
  --tone-wash: rgba(174, 189, 255, 0.075);
  --tone-edge: rgba(174, 189, 255, 0.36);
  --tone-veil: rgba(174, 189, 255, 0.05);
}
html:has(link[rel="canonical"][href*="/tools/"]) {
  --tone: var(--cyan);
  --tone-wash: rgba(106, 214, 220, 0.075);
  --tone-edge: rgba(106, 214, 220, 0.36);
  --tone-veil: rgba(106, 214, 220, 0.05);
}
html:has(link[rel="canonical"][href*="/fun/"]),
html:has(link[rel="canonical"][href*="/quiz/"]),
html:has(link[rel="canonical"][href*="/talk/"]) {
  --tone: var(--heather);
  --tone-wash: rgba(233, 170, 227, 0.075);
  --tone-edge: rgba(233, 170, 227, 0.36);
  --tone-veil: rgba(233, 170, 227, 0.05);
}
:root { --tone-veil: rgba(223, 191, 102, 0.05); }   /* core, and the fallback */

/* ── 2. THE GROUND ───────────────────────────────────────────────────────────
   v2 flattened the body to a single fill, which killed the gradients v1 had
   put there and left the page the same value from the nav to the disclaimer.
   Three layers instead: the section's own veil at the top of the document, a
   cool counterweight low and right so the field is never uniform, and the
   neutral ramp running the page gently darker as it descends. All of it is
   below 6% opacity — it should be felt, not seen.
   ────────────────────────────────────────────────────────────────────────── */
body {
  background:
    radial-gradient(1200px 520px at 50% -8%, var(--tone-veil), transparent 70%),
    radial-gradient(900px 600px at 88% 12%, rgba(133, 206, 249, 0.035), transparent 65%),
    linear-gradient(180deg, var(--ink-800) 0%, var(--ink-800) 42%, var(--ink-900) 100%);
  background-attachment: fixed;
  color: var(--text);
  font-size: var(--fs-base);
  line-height: 1.62;
  text-rendering: optimizeLegibility;
}

/* Selection is a free chance to say which section you are in. */
::selection { background: var(--tone); color: var(--ink-900); text-shadow: none; }

/* The UA scrollbar was the one surface that never got the memo: on a system
   set to light it rendered pale grey against a near-black page. */
html { scrollbar-color: var(--surface-4) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--surface-4); border-radius: 999px;
  border: 3px solid transparent; background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--edge-soft); background-clip: padding-box; }

/* ── 3. TYPOGRAPHY ───────────────────────────────────────────────────────────
   The scale existed in v2 and was then ignored: headings carried literal
   pixel sizes, body copy ran at four different sizes on the same page, and
   the measure was enforced on exactly two selectors. Applied properly here.

   Weights are real, not synthesised — see the @font-face range at the top.
   Cinzel is an all-caps-feeling roman with tight sidebearings, so its
   headings take a little negative tracking at display sizes and none at all
   below 20px, where negative tracking on a serif just closes up the counters.
   ────────────────────────────────────────────────────────────────────────── */
h1, h2, h3, h4 {
  font-family: var(--display);
  color: var(--text-strong);
  line-height: 1.14;
  /* balance, not a max-width: a heading should be held by its column, and a
     character cap turns "Cost, time and bonus at every level" into four short
     stacked lines in the middle of a wide page. */
  text-wrap: balance;
}
h1 { font-size: var(--fs-3xl); font-weight: 700; letter-spacing: -0.015em; margin: 0 0 var(--s3); }
h2 { font-size: var(--fs-2xl); font-weight: 600; letter-spacing: -0.01em; }
h3 { font-size: var(--fs-xl); font-weight: 600; letter-spacing: 0; }
h4 { font-size: var(--fs-lg); font-weight: 600; letter-spacing: 0; }
/* A heading that runs the full width of a wide container is a banner, not a
   heading — but a table caption or a card title must be free to wrap however
   its box allows. */
.card h3, .card-v2 h3, .pillar h3, td h3, th h3, .f-cat-main h3, .f-thread-main h3 { max-width: none; }

/* The 225 generated page titles carry an inline `font-size:38px`, which no
   stylesheet can reach without !important. 38px happens to be exactly the top
   of --fs-2xl, so this changes nothing on a desktop and stops a long title
   from filling half a phone screen. Scoped to inline-styled h1 only. */
h1[style] { font-size: var(--fs-2xl) !important; line-height: 1.1; }

/* RUNNING TEXT NEAR 65 CHARACTERS. Declared once at the top as --measure and
   enforced here on every element that can hold a sentence, rather than on the
   two selectors that had it before. */
p, li, dd, blockquote, figcaption, .lede, .section-sub, .answer, .qa dd { max-width: var(--measure); }
/* The prose column is a little wider than the measure on purpose: the measure
   holds the sentences, and the extra room is what the tables, figures and
   fact sheets inside the column get to use. 760px was too tight for a
   six-column cost ladder and left every table scrolling on a laptop. */
.prose { max-width: 860px; }
/* NO font-size here, deliberately. `body` already sets --fs-base and every
   paragraph inherits it, so declaring it again on `.prose p` buys nothing and
   costs something real: at (0,2,0) it outranks the page-level classes that
   mark text as small on purpose — `.small`, `.db-source`, the caption under a
   generated table — and silently inflated all of them back to body size. */
.prose p, .prose li, .prose dd { max-width: var(--measure); }
.prose li { margin: var(--s1) 0; }
.prose ul, .prose ol { padding-left: 1.35em; }
.prose li::marker { color: var(--tone); }
/* Tables, figures and cards are DATA and may use the full column; only prose
   is held to the measure. Constraining these is what made the old guide pages
   feel like a narrow strip of text with acres of nothing beside it. */
.prose table, .prose .db-table, .grid, .statstrip, .evidence, .guide-figure { max-width: none; }
table p, table li, td, th { max-width: none; }

.lede {
  font-size: var(--fs-lg); line-height: 1.55; color: var(--text);
  margin: 0 0 var(--s5); font-weight: 400;
}
.small { font-size: var(--fs-sm); line-height: 1.65; }
.muted { color: var(--text-dim); }
.muted a { color: var(--text-dim); text-decoration: underline; text-underline-offset: 2px; }
.muted a:hover { color: var(--frost-300); }

/* The eyebrow is where a section says its name. It is the ONLY piece of
   running text that takes the section tone. */
.eyebrow, .tool-kicker, .db-kicker, .prose .db-kicker, .hero .kicker {
  font-family: var(--display); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.2em;
  font-size: var(--fs-xs); color: var(--tone);
  margin: 0 0 var(--s2);
}

/* Links: frost, with a resting underline that is present but quiet. An
   underline that only exists on hover is not available to anybody reading
   without a pointer, and hue alone has never been an accessible way to mark
   a link inside a paragraph. */
.prose a, .answer a, .qa dd a, .lede a, td a, li a {
  color: var(--frost-400);
  text-decoration: underline;
  text-decoration-color: var(--frost-600);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: color .15s, text-decoration-color .15s;
}
.prose a:hover, .answer a:hover, .qa dd a:hover, .lede a:hover, td a:hover, li a:hover {
  color: var(--frost-300); text-decoration-color: var(--frost-300);
}
/* Anything that is already a box — a card, a nav item, a footer column, a
   button — is obviously clickable and must not be underlined as well. */
a.card, a.btn, .nav a, .foot a, .crumbs a, .f-cat, .f-thread, .related li a,
.grid a.card, a:has(> .card), a:has(> article), a:has(> figure) { text-decoration: none; }

/* A HEADING THAT IS A LINK IS STILL A HEADING. The database's section hubs put
   an <a> inside every card's <h3>, and treating those as running-text links
   turned a grid of nine cards into a grid of nine underlined blue phrases —
   the card is the affordance, and the title should look like a title. The
   underline comes back on hover so the target is never in doubt. */
h1 a, h2 a, h3 a, h4 a, .card h3 a, .prose h2 a, .prose h3 a {
  color: inherit; text-decoration: none;
}
h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover {
  color: var(--frost-300); text-decoration: underline; text-underline-offset: 0.16em;
}

/* ── THE PAGE-TITLE RULE ─────────────────────────────────────────────────────
   The one element every page in the site has, given the section's colour. The
   225 generated titles carry an inline style attribute and cannot be reached
   any other way — but a pseudo-element belongs to the stylesheet, not to the
   element's own style, so this reaches all of them without a byte of markup
   changing. It is the strongest section signal on the page and it costs one
   rule. */
.wrap > h1::after,
.prose > h1::after,
.tool-h1::after,
.f-title::after {
  content: ""; display: block;
  width: 64px; height: 3px; border-radius: 2px;
  margin: var(--s3) 0 var(--s5);
  background: linear-gradient(90deg, var(--tone), var(--tone-edge) 60%, transparent);
}
/* Centred page heads want the rule centred under them, not hanging off the
   left of a column that is not there. */
.fun-head h1::after, .quiz-head h1::after {
  content: ""; display: block;
  width: 64px; height: 3px; border-radius: 2px;
  margin: var(--s3) auto var(--s4);
  background: linear-gradient(90deg, transparent, var(--tone), transparent);
}

/* ── 4. THE CHROME ──────────────────────────────────────────────────────────
   The nav and footer markup is owned by scripts/site-chrome.mjs and compared
   byte-for-byte across all pages by siteChrome.test.ts, so everything here is
   CSS reaching existing elements. Nothing below adds, removes or reorders a
   single tag.
   ────────────────────────────────────────────────────────────────────────── */
.nav {
  background: linear-gradient(180deg, rgba(15,19,26,0.92), rgba(15,19,26,0.78));
  border-bottom: 1px solid var(--hair);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  backdrop-filter: blur(14px) saturate(1.3);
  position: relative;
}
/* The section's signature: a two-pixel bar along the bottom of the bar that
   is on the screen at all times. It is the cheapest possible way to make
   moving from the guides to the newsroom feel like moving. */
.nav::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background: linear-gradient(90deg, var(--tone), var(--tone-edge) 32%, transparent 72%);
  opacity: .9; pointer-events: none;
}
.nav.is-scrolled {
  background: linear-gradient(180deg, rgba(9,12,18,0.97), rgba(9,12,18,0.92));
  box-shadow: var(--e2);
}
.brand {
  font-family: var(--fancy); font-weight: 700; color: var(--gold-500);
  letter-spacing: .04em; font-size: 21px;
}
.brand span { color: var(--gold-300); }
.nav-links a:not(.nav-cta) {
  color: var(--text); font-size: var(--fs-sm); font-weight: 500;
  letter-spacing: .005em; transition: color .18s;
}
.nav-links a:not(.nav-cta):hover { color: var(--text-strong); }
.nav-links a:not(.nav-cta)::after,
.nav-links a::after { background: var(--tone); height: 2px; bottom: -4px; }

/* Breadcrumbs: the trail is quiet, the page you are on is not. Deliberately
   NOT a flex container — the separators are literal " · " text nodes between
   the links, and flex turns each run of text into its own item and eats the
   whitespace around the dots. */
.crumbs {
  color: var(--text-dim); font-size: var(--fs-sm); margin: 0 0 var(--s5);
  line-height: 1.7;
}
.crumbs a { color: var(--text-dim); transition: color .15s; }
.crumbs a:hover { color: var(--frost-300); text-decoration: underline; }

/* ── 5. THE PAGE TITLE BLOCK ─────────────────────────────────────────────── */
/* The rule under a page title is drawn in the section tone and is the second
   place (after the eyebrow) a reader can tell where they are. */
.band-head .rule,
.rule { background: linear-gradient(90deg, var(--tone), transparent); }
.band-head .rule { width: 72px; height: 2px; border-radius: 2px; }
.band-head h2 { font-family: var(--display); color: var(--text-strong); }
.band-head p { color: var(--text-dim); font-size: var(--fs-lg); max-width: var(--measure); }
.band.alt {
  background: linear-gradient(180deg, var(--ink-700), var(--ink-800));
  border-block: 1px solid var(--hair);
}

/* ── 6. CARDS THAT ARE MADE OF SOMETHING ─────────────────────────────────────
   The old card was a 1px gold hairline around the page's own background. Four
   things fix that, and all four are needed: a fill that is lighter than the
   ground and gradient rather than flat, a hairline of light along the top edge
   (--lift) where a real object would catch the room, a drop shadow with a
   contact layer as well as an ambient one, and the section's tone drawn along
   the top border so a grid of cards reads as belonging to this page.
   ────────────────────────────────────────────────────────────────────────── */
.card, .card-v2, .f-cat, .f-thread, .f-post, .fun-card, .quiz-card {
  background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border: 1px solid var(--hair);
  box-shadow: var(--lift), var(--e1);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  transition: transform .22s var(--ease), border-color .22s, box-shadow .22s;
}
.card { border-radius: var(--r-lg); padding: var(--s5); overflow: hidden; }
.card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(90deg, var(--tone), var(--tone-edge) 45%, transparent);
  opacity: 0; transition: opacity .22s;
}
.card:hover, .card-v2:hover, .f-cat:hover, .f-thread:hover {
  transform: translateY(-3px);
  border-color: var(--tone-edge);
  box-shadow: var(--lift), var(--e3);
}
.card:hover::before { opacity: 1; }
.card h3, .card-v2 h3 {
  font-family: var(--display); font-weight: 600; color: var(--text-strong);
  font-size: var(--fs-lg); margin: var(--s3) 0 var(--s2);
}
.card p, .card-v2 p { color: var(--text-dim); font-size: var(--fs-sm); line-height: 1.65; margin: 0; }
a.card:hover h3, a.card:focus-visible h3 { color: var(--text-strong); }

/* The icon well takes the section tone rather than gold — it is the most
   repeated element on a hub page and therefore the cheapest place to spend a
   section signal. */
.card .ico, .ico-well {
  background:
    radial-gradient(120% 120% at 30% 15%, var(--tone-wash), transparent 70%),
    var(--surface-3);
  border: 1px solid var(--hair);
  box-shadow: var(--lift);
  color: var(--tone);
}
.ico-well svg { stroke: var(--tone); }
.card:hover .ico { border-color: var(--tone-edge); }

/* Pillars and the evidence row */
.pillar {
  background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border: 1px solid var(--hair); box-shadow: var(--lift), var(--e1);
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
.pillar h3 { font-family: var(--display); color: var(--text-strong); }
.pillar p { color: var(--text-dim); }
.ev { border-left: 2px solid var(--tone); }
.ev span, .ev small { color: var(--text-dim); }

/* Key numbers stay gold. This is the job gold was narrowed down TO: a figure
   somebody should stop and read. */
.stat b, .ev b, .f-cat-meta b {
  background: linear-gradient(180deg, var(--gold-100) 0%, var(--gold-500) 55%, var(--gold-700) 100%);
  -webkit-background-clip: text; background-clip: text;
  color: var(--gold-500); -webkit-text-fill-color: transparent;
  font-variant-numeric: tabular-nums;
}
.stat span { color: var(--text-dim); }

/* ── 7. TABLES ───────────────────────────────────────────────────────────────
   The database is 147 pages of tables and they were the flattest surface on
   the site: gold header text, hairline rows, nothing else. A header band, a
   tone rule beneath it, zebra striping at 2% and a hover row make a
   fifteen-row cost ladder scannable instead of merely present.
   ────────────────────────────────────────────────────────────────────────── */
.prose table, .db-table table, table {
  border-collapse: separate; border-spacing: 0;
  border: 1px solid var(--hair); border-radius: var(--r-md);
  overflow: hidden; background: var(--surface-1);
  font-variant-numeric: tabular-nums;
}
thead th {
  background: var(--surface-3); color: var(--text-strong);
  font-family: var(--display); font-weight: 600; font-size: var(--fs-sm);
  letter-spacing: .015em; white-space: nowrap;
  border-bottom: 2px solid var(--tone);
}
td, th { border-bottom: 1px solid var(--hair); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:nth-child(even) td { background: rgba(174, 186, 214, 0.022); }
tbody tr:hover td { background: var(--tone-wash); }
caption {
  caption-side: top; text-align: left; color: var(--text-dim);
  font-size: var(--fs-sm); padding: 0 0 var(--s2);
}
/* The database's fact sheet: labels quiet, values strong. The generated pages
   colour <dt> gold from an inline <style> block that loads after this sheet,
   so the override has to out-specify it rather than out-order it. */
.prose .db-facts dt { color: var(--text-dim); font-family: var(--display); font-weight: 600; }
.prose .db-facts dd { color: var(--text); }
.prose .db-facts { border-top-color: var(--hair); }
.prose .db-facts dt, .prose .db-facts dd { border-bottom-color: var(--hair); }
.prose .db-source { color: var(--text-faint); }

/* ── 8. CODE ─────────────────────────────────────────────────────────────────
   142 pages use <code> for game identifiers and formulas — `build_speed`,
   `floor(√(Power ÷ 3,000))` — and this stylesheet had never styled it at all.
   They were rendering as unstyled browser-default monospace at whatever size
   the UA felt like, inside carefully set body copy.
   ────────────────────────────────────────────────────────────────────────── */
code, kbd, samp {
  font-family: var(--mono);
  font-size: 0.875em;
  background: var(--surface-3);
  border: 1px solid var(--hair);
  border-radius: 5px;
  padding: 0.1em 0.38em;
  color: var(--text-strong);
  white-space: nowrap;
  word-break: keep-all;
}
kbd {
  background: linear-gradient(180deg, var(--surface-4), var(--surface-3));
  border-color: var(--edge-soft); box-shadow: 0 1px 0 var(--ink-900);
}
a code { color: inherit; border-color: var(--frost-600); }
pre {
  font-family: var(--mono); font-size: var(--fs-sm); line-height: 1.6;
  background: var(--ink-900); border: 1px solid var(--hair);
  border-left: 3px solid var(--tone);
  border-radius: var(--r-md); padding: var(--s4) var(--s5);
  overflow-x: auto; margin: var(--s5) 0; color: var(--text);
  max-width: 100%;
}
pre code {
  background: none; border: 0; padding: 0; font-size: inherit;
  white-space: pre; color: inherit;
}

/* ── 9. CALLOUTS AND NOTES ─────────────────────────────────────────────────── */
.callout { box-shadow: var(--lift); }
.callout strong { color: var(--text-strong); }
/* The stage note is a WARNING and now says so in the semantic colour
   rather than in a slightly different gold from everything around it. */
.prealpha-note {
  background: var(--warn-wash);
  border: 1px solid rgba(246, 180, 92, 0.28);
  border-left: 3px solid var(--warn);
  border-radius: var(--r-sm);
  box-shadow: var(--lift);
  font-size: var(--fs-sm);
  color: var(--text);
  max-width: none;
}
.prealpha-note strong { color: var(--warn); }

.quick-answers {
  background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
  border: 1px solid var(--hair);
  border-left: 3px solid var(--tone);
  border-radius: var(--r-md);
  box-shadow: var(--lift), var(--e1);
}
.quick-answers > h2 {
  font-family: var(--display); font-size: var(--fs-sm); font-weight: 600;
  letter-spacing: .18em; color: var(--tone); max-width: none;
}
.qa dt { color: var(--text-strong); font-weight: 650; max-width: var(--measure); }

.related > h2 { color: var(--text-dim); max-width: none; }
.related li { border-bottom-color: var(--hair); }
.related li a { color: var(--frost-400); font-weight: 600; }
.related li a:hover { color: var(--frost-300); text-decoration: underline; }
.related li span { color: var(--text-dim); }

.guide-figure { border-color: var(--hair); background: var(--surface-1); box-shadow: var(--lift), var(--e1); }
.guide-figure figcaption { color: var(--text-dim); border-top-color: var(--hair); max-width: none; }

.sc-list li::before { color: var(--tone); }
.shot figcaption b { color: var(--text-strong); }
.sc-body h3 { color: var(--text-strong); }
.sc-media, .split-media { border-color: var(--hair); }

/* ── 10. BUTTONS ─────────────────────────────────────────────────────────────
   Three ranks, and each one has a job. PRIMARY is gold and there is at most
   one on a screen — it is the thing the page wants you to do. SECONDARY is
   the neutral surface with a real 3:1 border, for everything else that is an
   action. GHOST is frost on nothing, for an action beside a primary that must
   not compete with it.
   ────────────────────────────────────────────────────────────────────────── */
.btn {
  font-family: var(--display); font-weight: 700; font-size: var(--fs-base);
  letter-spacing: .02em; padding: 12px 24px; border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--surface-3), var(--surface-2));
  border: 1px solid var(--edge); color: var(--text-strong);
  box-shadow: var(--lift), var(--e1);
  transition: transform .15s var(--ease), background .18s, border-color .18s, box-shadow .18s;
}
.btn:hover {
  transform: translateY(-1px);
  background: linear-gradient(180deg, var(--surface-4), var(--surface-3));
  border-color: var(--frost-600);
  box-shadow: var(--lift), var(--e2);
}
.btn:active { transform: translateY(0); box-shadow: var(--lift), var(--e1); }

.btn.primary, .nav-cta {
  background: linear-gradient(180deg, var(--gold-300) 0%, var(--gold-500) 52%, var(--gold-600) 100%);
  color: var(--gold-ink) !important;
  border: 1px solid var(--gold-700);
  text-shadow: 0 1px 0 rgba(255, 250, 235, 0.35);
  box-shadow:
    inset 0 1px 0 rgba(255, 252, 240, 0.55),
    inset 0 -1px 0 rgba(86, 70, 20, 0.4),
    var(--e2);
}
.btn.primary:hover, .nav-cta:hover {
  filter: none;
  background: linear-gradient(180deg, var(--gold-100) 0%, var(--gold-300) 52%, var(--gold-500) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 252, 240, 0.7),
    inset 0 -1px 0 rgba(86, 70, 20, 0.4),
    var(--e2), 0 0 34px rgba(223, 191, 102, 0.20);
}
.btn.ghost {
  background: transparent; border-color: var(--frost-600); color: var(--frost-300);
  box-shadow: none;
}
.btn.ghost:hover { background: rgba(133, 206, 249, 0.10); border-color: var(--frost-400); color: var(--frost-300); }
/* .lg is the hero pair. It stays narrow enough that "Play free in your
   browser" and "Start the tutorial" sit on ONE line inside the 620px
   masthead column — a stacked pair of hero buttons reads as two afterthoughts
   rather than as a choice. */
.btn.lg { font-size: var(--fs-base); padding: 14px 26px; border-radius: var(--r-lg); }
.btn[disabled], .btn[aria-disabled="true"] {
  opacity: .5; cursor: not-allowed; transform: none; box-shadow: none;
}
.nav-cta { font-size: var(--fs-sm); padding: 9px 17px; border-radius: 10px; }

/* ── 11. FORM CONTROLS ───────────────────────────────────────────────────────
   Inputs need a border that meets the 3:1 non-text floor, because for a text
   field the border IS the affordance — there is nothing else on screen to say
   where you may type.
   ────────────────────────────────────────────────────────────────────────── */
input[type="text"], input[type="search"], input[type="email"], input[type="number"],
select, textarea, .f-composer input[type="text"], .f-composer textarea {
  background: var(--ink-900);
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  color: var(--text);
  font: inherit; font-size: var(--fs-base);
  padding: 10px 12px;
  transition: border-color .15s, background .15s;
}
input::placeholder, textarea::placeholder { color: var(--text-faint); }
input:hover, select:hover, textarea:hover { border-color: var(--edge-soft); }
.f-composer input[type="text"]:focus, .f-composer textarea:focus { border-color: var(--frost-400); }

/* ── 12. FOCUS ───────────────────────────────────────────────────────────────
   Every interactive element, one ring, always visible. Frost is 10.8:1 on the
   ground — but 1.03:1 against a gold button, so the two gold controls get a
   dark halo between the button and the ring. Without it the focus state on the
   single most important control on the site would be invisible.
   ────────────────────────────────────────────────────────────────────────── */
:where(a, button, summary, input, select, textarea, [tabindex], .btn, .card,
       .tool-chip, .tool-btn, .fun-option, .quiz-option):focus-visible {
  outline: 2px solid var(--frost-300);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}
.btn.primary:focus-visible, .nav-cta:focus-visible {
  outline: 2px solid var(--frost-300);
  outline-offset: 3px;
  box-shadow:
    inset 0 1px 0 rgba(255, 252, 240, 0.55),
    0 0 0 3px var(--ink-900);
}
summary:focus-visible { outline-offset: -2px; }

/* ── 13. MISCELLANY THAT WAS STILL WEARING THE OLD ACCENT ─────────────────── */
.f-badge {
  background: var(--tone-wash); color: var(--tone);
  border: 1px solid var(--tone-edge); font-weight: 600;
}
.f-badge.lock { background: rgba(174,186,214,.07); color: var(--text-dim); border-color: var(--hair); }
.f-title, .fun-head h1, .quiz-head h1, .tool-h1 { color: var(--gold-500); }
.f-cat-main h3, .f-thread-main h3, .f-author, .f-composer summary { color: var(--text-strong); }
.f-composer { background: var(--surface-1); border-color: var(--hair); }
.f-post-head { border-bottom-color: var(--hair); }

.site-banner {
  background: var(--tone-wash); color: var(--text-strong);
  border-bottom: 1px solid var(--tone-edge);
  font-size: var(--fs-sm);
}
.site-banner.warn { background: var(--warn-wash); color: var(--warn); border-bottom-color: rgba(246,180,92,.45); }
.site-banner.good { background: var(--ok-wash); color: var(--ok); border-bottom-color: rgba(103,216,156,.45); }

/* The podium keeps gold, because a leaderboard's top three IS a "number worth
   reading" and that is precisely the job gold was narrowed to. */
#lb-claimants tr.lb-top td, #lb-standings tr.lb-top td { background: rgba(223,191,102,0.06); }
#lb-claimants tr.lb-top:first-child td, #lb-standings tr.lb-top:first-child td {
  background: rgba(223,191,102,0.13); color: var(--gold-100);
}

.pop-dot { background: var(--ok); box-shadow: 0 0 0 0 rgba(103,216,156,.6); }
@keyframes pop-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(103,216,156,.55); }
  70%  { box-shadow: 0 0 0 9px rgba(103,216,156,0); }
  100% { box-shadow: 0 0 0 0 rgba(103,216,156,0); }
}
.mast-trust svg { stroke: var(--gold-500); }
.mast-kicker { color: var(--gold-500); }
.masthead h1 .g { color: var(--gold-500); }
.cta-final h2 { color: var(--gold-500); }

/* ── 14. MOTION ──────────────────────────────────────────────────────────────
   One catch-all, so a component added later cannot forget to honour it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .card:hover, .card-v2:hover, .f-cat:hover, .f-thread:hover, .btn:hover { transform: none; }
}

/* ── 15. NARROW SCREENS ──────────────────────────────────────────────────────
   Nothing here is new behaviour; it is the v4 sizes repeated at the widths
   where the desktop values are wrong. Verified at 390px: no page on the site
   scrolls sideways. */
@media (max-width: 640px) {
  .wrap { padding: var(--s5) var(--s4) var(--s7); }
  .foot .cols { gap: var(--s5); }
  .foot-inner { padding: var(--s6) var(--s4) var(--s4); }
  .disclaimer { padding: 0 var(--s4) var(--s5); }
  .btn { padding: 12px 20px; }
  .btn.lg { font-size: var(--fs-base); padding: 13px 24px; }
  h1, h2, h3, h4 { max-width: none; }
  pre { padding: var(--s3) var(--s4); }
  /* A long identifier inside <code> is the one thing that can still push a
     390px page sideways, because nowrap was the right call for `build_speed`
     and the wrong one for a 60-character formula. */
  code { white-space: pre-wrap; word-break: break-word; }
}

/* ── 16. TOUCH ───────────────────────────────────────────────────────────────
   Keyed to the pointer, never to the viewport — a 1024px tablet is a finger
   and a narrow desktop window is a mouse. */
@media (pointer: coarse) {
  .nav-links a:not(.nav-cta) { padding: 10px 2px; }
  .crumbs a { padding: 4px 0; }
  .related li a { padding: 2px 0; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   17. THE LIGHT THEME
   ───────────────────────────────────────────────────────────────────────────
   There was not one. The sheet was dark-only and declared no color-scheme, so
   a visitor whose system is set to light was served a near-black page with a
   pale scrollbar and white form fields — the browser rendering its own widgets
   for a theme the page was not using.

   Only the TOKENS are redefined. Not one component rule is repeated, which is
   the whole return on having spent the effort on a token layer: the light
   theme is 60 lines, and any component added later gets it for free.

   The neutral ramp is the same OKLCH hue (265°) run the other way, so the
   light theme is cool paper rather than white. Accent HUES are identical; only
   their lightness and chroma change, because a #dfbf66 gold that reads at
   10.4:1 on near-black reads at 1.6:1 on near-white and would be unusable.
   ═══════════════════════════════════════════════════════════════════════════ */
/* THE LIGHT THEME LIVED HERE AND WAS DELIBERATELY REMOVED.

   It was a complete, careful palette — every token redefined, same roles, same
   accent hues with lightness inverted and chroma raised to hold 4.5:1 on a pale
   ground — and it was wired to `prefers-color-scheme: light`, which flipped the
   ENTIRE SITE for anybody whose laptop happened to be in light mode. The dark
   ground is this game's brand rather than a visitor preference, so that trade
   was wrong however good the palette was.

   It is not deleted so much as parked: the full block is in the commit that
   added it ("A palette with jobs, and a light theme that is actually a light
   theme"). If a theme toggle is ever built, restore it there and scope every
   selector under `:root[data-theme="light"]` — PREFIXED, not nested. Nesting is
   what broke it on the first attempt at this change: the light nav gradient
   escaped its scope and painted the bar pale in both themes. */


/* THE FAN ZONE DOOR ON THE LANDING PAGE. Deliberately the quietest of the three
   and the only one that carries a second line: it leads AWAY from the game, so
   a visitor should be able to tell that at a glance rather than after a click.
   The violet edge is the Fan Zone's own accent reaching back through the door —
   the one place on the game's site where that palette appears. */
.mast-fanzone {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.15rem;
  line-height: 1.25;
  border-color: #6c4a86;
}
.mast-fanzone:hover { border-color: #8f68ad; }
.mast-fanzone-sub {
  display: block;
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.01em;
  opacity: 0.72;
  text-transform: none;
}


/* ── COMMENTS UNDER A PAGE ───────────────────────────────────────────────────
   Quiet by design. A comment section that shouts sits above the thing the
   reader actually came for; this one reads as a footnote until somebody
   chooses to look at it. */
.sc { max-width: 760px; margin: 40px auto 0; padding: 0 16px 8px; }
.sc h2 { font-family: var(--display); color: var(--gold); font-size: 20px; margin: 0 0 4px; }
.sc-count { color: var(--muted); font-size: 13px; margin: 0 0 12px; }
.sc-empty { color: var(--muted); font-size: 14px; margin: 0 0 12px; }
.sc-item { border-top: 1px solid var(--line); padding: 12px 0; }
.sc-head { margin: 0 0 4px; font-size: 13.5px; }
.sc-when { color: var(--muted); font-size: 12.5px; }
.sc-text { margin: 0; font-size: 14.5px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; }
.sc-note { color: var(--muted); font-size: 13.5px; margin: 12px 0 0; }
.sc-form { margin: 14px 0 0; }
.sc-input { width: 100%; box-sizing: border-box; font: inherit; font-size: 14.5px; padding: 9px 11px;
  border: 1px solid var(--line); border-radius: 6px; background: rgba(0,0,0,.14); color: inherit; resize: vertical; }
.sc-bar { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.sc-send { font: inherit; font-size: 14px; padding: 7px 16px; border: 1px solid var(--line);
  border-radius: 6px; background: rgba(0,0,0,.2); color: var(--gold-bright); cursor: pointer; }
.sc-send[disabled] { opacity: .55; cursor: default; }
.sc-msg { color: var(--muted); font-size: 13px; }

/* ── THE PAGE BANNER ─────────────────────────────────────────────────────────
 *
 * Fourteen of the sixteen pages in the game wing carried NO imagery at all —
 * the front page and the media page had it, and everything else was wall-to-
 * wall prose. That is not an aesthetic complaint: a strategy game's About,
 * World and Guides pages are exactly where a reader decides whether the thing
 * looks worth their evening, and a page of text says nothing about a game whose
 * whole pitch is that it is rendered in 3D in a browser.
 *
 * A BANNER RATHER THAN A SCREENSHOT PLATE, and the distinction is the point.
 * It sits under the heading, it is a wide strip rather than a 16:9 frame, and
 * it is capped in height so it never pushes the first paragraph below the fold
 * on a phone. It is furniture, not an exhibit — a page that opens with a
 * full-bleed hero is a landing page, and only one of these is.
 *
 * EVERY PAGE GETS A DIFFERENT FRAME. keyArt.test.ts exists because six cards
 * turned out to be three pictures; the same rule applies here from the start,
 * and pageArt.test.ts holds it.
 *
 * The gradient mask at the foot is what stops the strip fighting the prose
 * under it — the images are bright daylight renders and the page is dark, so
 * an untreated edge reads as a hard seam across the column. */
.page-hero {
  margin: 14px 0 22px;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid rgba(198, 164, 92, .22);
  line-height: 0;
}
.page-hero img {
  display: block;
  width: 100%;
  height: auto;
  /* The cap is what keeps it furniture. A 1600x420 strip on a 1200px column is
     315px tall, which is already most of a phone's first screen — so on narrow
     viewports it crops further rather than growing. */
  max-height: 260px;
  object-fit: cover;
  object-position: center 42%;
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 78%, rgba(0,0,0,.55) 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 78%, rgba(0,0,0,.55) 100%);
}
@media (max-width: 720px) {
  /* Shorter still on a phone, where the heading and the first sentence matter
     more than the picture and the fold arrives much sooner. */
  .page-hero { margin: 10px 0 16px; }
  .page-hero img { max-height: 150px; }
}
@media (prefers-reduced-motion: no-preference) {
  /* The one flourish, and it is on LOAD rather than on scroll: a strip that
     fades up once as the page settles reads as intentional, where a
     scroll-triggered reveal on every page of a text-heavy site reads as a
     template. */
  .page-hero { animation: pageHeroIn .5s ease-out both; }
  @keyframes pageHeroIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
}
