/* ============================================================
   DYNASTIES — public site stylesheet
   The community-facing skin of the design system: same dark, mission-control
   base as the GM console, but the accent is imperial GOLD/CREAM (the console
   stays teal). Mirrors design-system/styles/tokens.css. Server-rendered pages
   (SiteHtml.cs) reference these classes.
   ============================================================ */
@import url("/vendor/fonts/fonts.css");

:root {
  --bg-0:#0a0c10; --bg-1:#0f1217; --bg-2:#14181f; --bg-3:#1b2029; --bg-4:#232a35;
  --line:rgba(255,255,255,.07); --line-2:rgba(255,255,255,.11); --line-strong:rgba(255,255,255,.17);
  --tx-hi:#e7eaf0; --tx-mid:#9aa3b3; --tx-lo:#69707e; --tx-faint:#474d58;
  /* imperial gold/cream = the public accent */
  --accent:#e6b13e; --accent-2:#f1e7cd; --accent-deep:#c8912e; --accent-ink:#1a1205;
  /* The BRAND aliases. `.brand .wm` below has always read var(--brand-cream), which was defined
     only in the CONSOLE's tokens.css -- a file the public site never imports. An undefined custom
     property is invalid-at-computed-value-time, so the wordmark silently inherited body text and
     the site's own name rendered off-brand, with no warning anywhere (CSS has no such thing as an
     unresolved-variable error). Same values as the console, so the two now agree by definition
     rather than by coincidence. */
  --brand-gold:#e6b13e; --brand-gold-deep:#c8912e; --brand-cream:#f1e7cd;
  --accent-bg:rgba(230,177,62,.12); --accent-bg-2:rgba(230,177,62,.18); --accent-line:rgba(230,177,62,.34);
  --ok:#52d39b; --ok-bg:rgba(82,211,155,.13); --warn:#e7c14b; --crit:#e0664a; --crit-bg:rgba(224,102,74,.14);
  --sans:"IBM Plex Sans",system-ui,-apple-system,sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,Menlo,monospace;
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:20px; --s6:24px; --s8:32px; --s10:40px; --s12:48px; --s16:64px;
  --r-sm:4px; --r-md:6px; --r-lg:9px; --r-xl:14px; --r-pill:999px;
  --maxw:1080px;
  --glow:0 0 28px -6px rgba(230,177,62,.55);
  --shadow:0 12px 32px -8px rgba(0,0,0,.66),0 0 0 1px var(--line-2);
  --focus:0 0 0 1px var(--bg-0),0 0 0 3px var(--accent-line);
}

* { margin:0; padding:0; box-sizing:border-box; }
html { scroll-behavior:smooth; scroll-padding-top:76px; } /* keep anchor jumps clear of the 60px sticky topbar */
body { background:var(--bg-0); color:var(--tx-hi); font-family:var(--sans); font-size:15px; line-height:1.6; -webkit-font-smoothing:antialiased; }
a { color:inherit; text-decoration:none; }
.mono { font-family:var(--mono); font-variant-numeric:tabular-nums; }
.muted { color:var(--tx-mid); } .faint { color:var(--tx-lo); }
.wrap { max-width:var(--maxw); margin:0 auto; padding:0 var(--s6); }
.accent { color:var(--accent); }

/* ---- buttons ---- */
.btn { display:inline-flex; align-items:center; justify-content:center; gap:var(--s2); height:42px; padding:0 var(--s6);
  border-radius:var(--r-md); border:1px solid var(--line-2); background:var(--bg-2); color:var(--tx-hi);
  font-family:var(--sans); font-size:14px; font-weight:600; cursor:pointer; transition:.13s; white-space:nowrap; }
.btn:hover { background:var(--bg-3); border-color:var(--line-strong); }
.btn:focus-visible { outline:none; box-shadow:var(--focus); }
.btn.primary { background:var(--accent); border-color:transparent; color:var(--accent-ink); }
.btn.primary:hover { background:var(--accent-2); box-shadow:var(--glow); }
.btn.ghost { background:transparent; border-color:transparent; color:var(--tx-mid); }
.btn.ghost:hover { color:var(--tx-hi); background:var(--bg-2); }

/* ---- "Continue with Discord": a handoff, drawn as one --------------------------------------
   Blurple on the left where the Discord mark is, our gold on the right where you are going. The
   gradient means the transaction, which is why it runs that direction and not the other.

   THE TRAP: #5865F2 and #e6b13e sit almost opposite each other, so interpolating them in sRGB
   passes through dead grey-brown at the midpoint -- the blend looks dirty exactly in the middle,
   where the eye lands. Two defences, in order:
     1. a hand-placed violet midpoint, so even the sRGB path stays saturated;
     2. the same gradient re-declared `in oklch`, which arcs through a clean magenta instead.
   Browsers that do not understand the second declaration keep the first, so both look deliberate.

   THE OTHER TRAP: neither end can carry label text. White on gold is ~2.2:1 and dark text dies on
   blurple. So the colour lives in the BORDER and a low-opacity wash, and the label sits on a near
   -black ground at full contrast -- the blend stays loud, the text stays legible. */
.btn.discord { position:relative; border:1px solid transparent; color:var(--tx-hi); font-weight:600;
  background:
    linear-gradient(var(--bg-1), var(--bg-1)) padding-box,
    linear-gradient(100deg, #5865F2 0%, #9b6ad0 46%, #e6b13e 100%) border-box;
  background-size:100% 100%, 220% 100%;
  background-position:0 0, 0 0;
  transition:background-position .5s ease, box-shadow .2s ease, transform .12s ease; }
@supports (background:linear-gradient(in oklch, red, blue)) {
  .btn.discord { background:
      linear-gradient(var(--bg-1), var(--bg-1)) padding-box,
      linear-gradient(100deg in oklch, #5865F2 0%, #e6b13e 100%) border-box;
    background-size:100% 100%, 220% 100%; }
}
/* The wash: the same blend at low strength across the face, so the button reads as blended rather
   than as a dark pill someone outlined. Sits under the label via a stacking context, not opacity
   on the parent, which would fade the text too. */
.btn.discord::before { content:""; position:absolute; inset:0; border-radius:inherit; z-index:0;
  background:linear-gradient(100deg, rgba(88,101,242,.30) 0%, rgba(155,106,208,.16) 46%, rgba(230,177,62,.26) 100%);
  transition:opacity .2s ease; }
.btn.discord > * { position:relative; z-index:1; }
/* Hover slides the border gradient across -- the "handoff" completing. 220% background-size is what
   gives it somewhere to travel. */
.btn.discord:hover { background-position:0 0, 100% 0; transform:translateY(-1px);
  box-shadow:0 6px 22px -8px rgba(88,101,242,.55), 0 6px 22px -8px rgba(230,177,62,.45); }
.btn.discord:hover::before { opacity:.75; }
.btn.discord:active { transform:translateY(0); }
.btn.discord .dc-mark { color:#fff; flex:none; }
@media (prefers-reduced-motion:reduce) {
  .btn.discord { transition:box-shadow .2s ease; }
  .btn.discord:hover { background-position:0 0, 0 0; transform:none; }
}
.btn.sm { height:34px; padding:0 var(--s4); font-size:13px; }
.btn.lg { height:50px; padding:0 var(--s8); font-size:15px; }
.btn[disabled] { opacity:.55; cursor:not-allowed; }

/* ---- top bar ---- */
.topbar { position:sticky; top:0; z-index:50; height:60px; display:flex; align-items:center;
  background:rgba(10,12,16,.82); backdrop-filter:blur(10px); border-bottom:1px solid var(--line); }
.topbar .wrap { display:flex; align-items:center; gap:var(--s5); width:100%; }
.brand { display:flex; align-items:center; gap:9px; }
.brand .mark { height:32px; width:auto; flex:none; display:block;
  filter:drop-shadow(0 0 8px rgba(230,177,62,.4)); animation:markGlow 4s ease-in-out infinite; }
@keyframes markGlow { 0%,100%{filter:drop-shadow(0 0 5px rgba(230,177,62,.32));} 50%{filter:drop-shadow(0 0 12px rgba(230,177,62,.6));} }
.brand .wm { font-weight:800; font-size:16px; letter-spacing:.015em; color:var(--brand-cream); }
/* Build-stage tag, pinned to the wordmark so it reads as part of the name rather than as a notice
   somebody can dismiss. Deliberately quiet -- outlined, not filled -- because it must be legible on
   every page without competing with the Sign In button it sits across from. */
.phase-tag { font-family:var(--mono); font-size:10px; font-weight:600; letter-spacing:.12em;
  text-transform:uppercase; color:var(--accent); padding:3px 7px; border-radius:var(--r-sm);
  border:1px solid var(--accent-line); background:var(--accent-bg); white-space:nowrap;
  align-self:center; transition:background .15s, color .15s;
  /* Cancel most of the topbar's flex gap so the tag sits ON the wordmark rather than floating in
     the middle of the header looking like a nav item. */
  margin-left:calc(var(--s2) - var(--s5)); }
.phase-tag:hover { background:var(--accent-bg-2); color:var(--accent-2); }

.nav-links { display:flex; align-items:center; gap:var(--s2); margin-left:auto; }
.nav-links a { padding:8px 12px; border-radius:var(--r-md); color:var(--tx-mid); font-size:14px; font-weight:500; }
.nav-links a:hover,.nav-links a.active { color:var(--tx-hi); background:var(--bg-2); }
/* Discord: an ICON, not another word. It sits beside Sign In because that is where somebody looks
   when they need us -- and it stays legible next to the gold CTA by taking Discord's own blurple on
   hover rather than competing for the accent. */
.nav-links a.nav-icon { display:inline-flex; align-items:center; justify-content:center;
  padding:7px 9px; color:var(--tx-lo); }
.nav-links a.nav-icon:hover { color:#fff; background:#5865F2; }
.nav-links a.nav-icon svg { display:block; }

/* ---- hero ---- */
.hero { position:relative; overflow:hidden; padding:var(--s16) 0 var(--s12); text-align:center;
  background:radial-gradient(900px 440px at 50% -8%,var(--accent-bg) 0%,transparent 64%),linear-gradient(180deg,var(--bg-0),var(--bg-1)); }
.hero::before { content:""; position:absolute; inset:0; opacity:.35; pointer-events:none;
  background-image:linear-gradient(var(--line) 1px,transparent 1px),linear-gradient(90deg,var(--line) 1px,transparent 1px);
  background-size:46px 46px; -webkit-mask-image:radial-gradient(70% 62% at 50% 0%,#000,transparent 76%); mask-image:radial-gradient(70% 62% at 50% 0%,#000,transparent 76%); }
.hero-inner { position:relative; max-width:760px; margin:0 auto; }
.eyebrow { font-family:var(--mono); font-size:12px; letter-spacing:.22em; text-transform:uppercase; color:var(--accent); }
.hero h1 { font-size:clamp(38px,7vw,72px); font-weight:800; line-height:1.02; letter-spacing:-.02em; margin:var(--s4) 0; }
.hero h1 b { color:var(--accent); font-weight:800; }
.hero .sub { font-size:clamp(16px,2.2vw,20px); color:var(--tx-mid); max-width:600px; margin:0 auto var(--s6); }
.cta { display:flex; gap:var(--s3); justify-content:center; flex-wrap:wrap; }
.status-chip { display:inline-flex; align-items:center; gap:var(--s3); margin-top:var(--s8); padding:8px 16px;
  border:1px solid var(--line); border-radius:var(--r-pill); background:var(--bg-1); font-size:13px; color:var(--tx-mid); }
.status-chip .dot { width:8px; height:8px; border-radius:50%; background:var(--ok); box-shadow:0 0 0 3px var(--ok-bg); }
/* RED MEANS BROKEN, and nothing else. `.off` is for a server we genuinely cannot reach.
   An empty world is not an error -- it is 4am -- so it gets a quiet neutral dot instead, which is
   also why `.idle` does not pulse: there is no heartbeat to show. Flying the critical colour beside
   the words "world online" made a healthy server look like a failing one. */
.status-chip .dot.off { background:var(--crit); box-shadow:0 0 0 3px var(--crit-bg); }
/* GREEN MEANS UP, and the world being up does not depend on anybody being in it -- so an empty
   server keeps the green and simply stops pulsing. The pulse is the only thing carrying "somebody is
   playing", which is what the number beside it already says in words. Grey here was the same mistake
   as red in a quieter costume: encoding population in a control that reports health. */
.status-chip .dot.quiet { animation:none; box-shadow:0 0 0 3px var(--ok-bg); }
.status-chip b { color:var(--tx-hi); font-family:var(--mono); }

/* ---- sections ---- */
section.block { padding:var(--s16) 0; border-top:1px solid var(--line); }
.section-h { font-size:clamp(24px,3.4vw,34px); font-weight:700; letter-spacing:-.01em; text-align:center; }
.section-sub { color:var(--tx-mid); text-align:center; margin:var(--s2) auto var(--s10); max-width:560px; }

/* ---- feature cards ---- */
.features { display:grid; gap:var(--s4); grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); }
.card { background:var(--bg-1); border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--s6); transition:.14s; }
.card.hover:hover { border-color:var(--accent-line); transform:translateY(-2px); box-shadow:var(--shadow); }
.card .ic { width:40px; height:40px; border-radius:var(--r-md); display:grid; place-items:center;
  background:var(--accent-bg); color:var(--accent); margin-bottom:var(--s4); font-size:20px; }
.card h3 { font-size:17px; margin-bottom:var(--s2); }
.card p { color:var(--tx-mid); font-size:14px; }

/* ---- how-to-play steps ---- */
.steps { display:grid; gap:var(--s4); grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); counter-reset:step; }
.step { position:relative; background:var(--bg-1); border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--s6); }
.step .n { font-family:var(--mono); font-size:13px; font-weight:600; color:var(--accent-ink); background:var(--accent);
  width:26px; height:26px; border-radius:var(--r-pill); display:grid; place-items:center; margin-bottom:var(--s3); }
.step h4 { font-size:15px; margin-bottom:4px; } .step p { color:var(--tx-mid); font-size:13px; }
.step code { font-family:var(--mono); font-size:12px; color:var(--accent-2); background:var(--bg-3); padding:1px 6px; border-radius:var(--r-sm); }

/* ---- ladder table ---- */
.ladder { width:100%; border-collapse:collapse; background:var(--bg-1); border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden; }
.ladder th,.ladder td { padding:11px 16px; text-align:left; border-bottom:1px solid var(--line); font-size:14px; }
.ladder th { font-family:var(--mono); font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--tx-lo); background:var(--bg-2); }
.ladder tr:last-child td { border-bottom:none; }
.ladder tr:hover td { background:var(--bg-2); }
.ladder .rank { font-family:var(--mono); color:var(--tx-lo); width:48px; }
.ladder .rank.top { color:var(--accent); font-weight:600; }
.ladder .pname { font-weight:600; } .ladder .num { font-family:var(--mono); text-align:right; }
.ladder .on { color:var(--ok); } .ladder .off { color:var(--tx-faint); }
.tablewrap { overflow-x:auto; }

/* ---- footer ---- */
footer { border-top:1px solid var(--line); padding:var(--s10) 0; color:var(--tx-lo); font-size:13px; }
footer .wrap { display:flex; align-items:center; gap:var(--s5); flex-wrap:wrap; }
footer a { color:var(--tx-mid); } footer a:hover { color:var(--accent); }
footer .sp { margin-left:auto; }

/* ---- auth (register) ---- */
.auth-wrap { min-height:calc(100vh - 60px); display:grid; place-items:center; padding:var(--s10) var(--s4);
  background:radial-gradient(700px 360px at 50% 0%,var(--accent-bg) 0%,transparent 60%); }
.auth-card { width:100%; max-width:420px; background:var(--bg-1); border:1px solid var(--line); border-radius:var(--r-xl); padding:var(--s8); box-shadow:var(--shadow); }
.auth-card h1 { font-size:22px; margin-bottom:4px; } .auth-card .lead { color:var(--tx-mid); font-size:14px; margin-bottom:var(--s6); }
.field { display:flex; flex-direction:column; gap:6px; margin-bottom:var(--s4); }
.field label { font-family:var(--mono); font-size:10px; letter-spacing:.07em; text-transform:uppercase; color:var(--tx-lo); }
.input { height:42px; padding:0 var(--s4); border-radius:var(--r-md); background:var(--bg-2); border:1px solid var(--line-2); color:var(--tx-hi); font:15px var(--sans); }
.input::placeholder { color:var(--tx-faint); }
.input:focus-visible { outline:none; border-color:var(--accent-line); box-shadow:var(--focus); }
.field .hint { font-size:12px; color:var(--tx-lo); }
.hp { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; } /* honeypot */
.banner { padding:11px 14px; border-radius:var(--r-md); font-size:14px; margin-bottom:var(--s5); }
.banner.err { background:var(--crit-bg); border:1px solid rgba(224,102,74,.34); color:#f0a18d; }
.banner.ok { background:var(--ok-bg); border:1px solid rgba(82,211,155,.34); color:#8fe3bf; }
.auth-card .alt { text-align:center; margin-top:var(--s5); font-size:13px; color:var(--tx-lo); }
.captcha-slot { height:66px; border:1px dashed var(--line-strong); border-radius:var(--r-md); display:grid; place-items:center;
  color:var(--tx-faint); font:12px var(--mono); margin-bottom:var(--s4); }

/* ---- wiki ---- */
.wiki-layout { display:grid; grid-template-columns:230px 1fr; gap:var(--s8); padding:var(--s10) 0; align-items:start; }
/* THE RAIL MUST FIT THE VIEWPORT, or `sticky` is a no-op AND the rail sets the page height.
   A sticky grid item travels inside its GRID AREA, and this row's area is as tall as the taller of
   the two items. The rail lists every article (59 links + 12 group headings ≈ 2.7k px), so on a
   600px article the area was exactly the rail's own height: zero distance to travel, nothing ever
   stuck, and 2.1k px of empty column that all 58 articles had to be as tall as. `align-items:start`
   above is necessary -- a stretched item fills its area and cannot move either -- but it was never
   sufficient on its own.
   Capping the height fixes both halves at once: the rail is now shorter than any area it sits in,
   so it sticks, and it can no longer be the tallest thing in the row, so the PAGE follows the
   ARTICLE. It scrolls itself once the list outgrows the screen -- a pinned rail that hides its own
   last links is just a different bug. 96px = the 76px sticky offset + breathing room at the bottom;
   the dvh line repeats it for mobile browsers whose toolbars make 100vh taller than the screen. */
.wiki-rail { position:sticky; top:76px;
  max-height:calc(100vh - 96px);
  max-height:calc(100dvh - 96px);
  /* visible, not auto: the search dropdown hangs out of the rail, and a folded rail fits the viewport */
  overflow:visible; scrollbar-width:thin; scrollbar-color:var(--line-strong) transparent; }
.wiki-rail .grp > summary { font-family:var(--mono); font-size:10px; letter-spacing:.08em; text-transform:uppercase; color:var(--tx-lo); margin:var(--s5) 0 var(--s2); }
.wiki-rail a { display:block; padding:6px 10px; border-radius:var(--r-sm); color:var(--tx-mid); font-size:14px; }
.wiki-rail a:hover,.wiki-rail a.active { color:var(--tx-hi); background:var(--bg-2); }
.wiki-search { width:100%; height:38px; padding:0 var(--s3); border-radius:var(--r-md); background:var(--bg-2); border:1px solid var(--line-2); color:var(--tx-hi); font:14px var(--sans); margin-bottom:var(--s4); }
.wiki-cards { display:grid; gap:var(--s4); grid-template-columns:repeat(auto-fill,minmax(260px,1fr)); }
.wiki-cards .card h3 { font-size:16px; } .wiki-cards .card .cat { font:10px var(--mono); letter-spacing:.07em; text-transform:uppercase; color:var(--accent); }
.meta-line { color:var(--tx-lo); font-size:12px; margin-top:var(--s8); padding-top:var(--s4); border-top:1px solid var(--line); }

/* On-page section nav (SiteHtml.SectionBar). Sticky under the 76px header, same offset the
   left rail uses, so the two pin in line with each other. It JUMPS rather than hiding panels --
   see the C# for why tabs were the wrong build for a reference page. */
.wsec { position:sticky; top:76px; z-index:5; display:flex; gap:var(--s3); align-items:center;
  flex-wrap:wrap; padding:10px 0; margin:0 0 var(--s5);
  background:linear-gradient(var(--bg-0) 78%, transparent); border-bottom:1px solid var(--line); }
.wsec-find { height:32px; width:180px; flex:none; padding:0 10px; border-radius:var(--r-sm);
  background:var(--bg-2); border:1px solid var(--line-2); color:var(--tx-hi); font:13px var(--sans); }
.wsec-find:focus { outline:none; border-color:var(--accent); }
.wsec-list { display:flex; gap:6px; flex-wrap:wrap; min-width:0; }
.wsec-chip, .wsec-sub { padding:5px 10px; border-radius:999px; font-size:13px; line-height:1;
  color:var(--tx-mid); background:var(--bg-2); border:1px solid transparent; white-space:nowrap; }
.wsec-chip:hover, .wsec-sub:hover { color:var(--tx-hi); border-color:var(--line-strong); }
.wsec-chip.on { color:var(--accent-ink); background:var(--accent); }
/* A sub-heading match looks like what it is: a finer, quieter target than a section. */
.wsec-sub { background:transparent; border-color:var(--line-2); font-size:12px; color:var(--tx-lo); }
.wsec.searching .wsec-chip { background:transparent; border-color:var(--line-2); }
.md h2[id], .md h3[id] { scroll-margin-top:120px; }
@media (max-width:720px){ .wsec-find { width:100%; } }

/* markdown content */
.md { max-width:760px; } .md > *+* { margin-top:var(--s4); }
.md h1 { font-size:30px; font-weight:800; letter-spacing:-.01em; }
.md h2 { font-size:22px; font-weight:700; margin-top:var(--s8); padding-bottom:6px; border-bottom:1px solid var(--line); }
.md h3 { font-size:17px; font-weight:600; margin-top:var(--s6); }
.md p,.md li { color:var(--tx-mid); } .md strong { color:var(--tx-hi); }
.md a { color:var(--accent); border-bottom:1px solid var(--accent-line); }
.md ul,.md ol { padding-left:var(--s6); } .md li+li { margin-top:4px; }
.md code { font-family:var(--mono); font-size:.88em; color:var(--accent-2); background:var(--bg-3); padding:2px 6px; border-radius:var(--r-sm); }
.md pre { background:var(--bg-1); border:1px solid var(--line); border-radius:var(--r-md); padding:var(--s4); overflow-x:auto; }
.md pre code { background:none; color:var(--tx-hi); padding:0; }
.md img { max-width:100%; border-radius:var(--r-md); border:1px solid var(--line); }
.md table { border-collapse:collapse; width:100%; } .md th,.md td { border:1px solid var(--line); padding:8px 12px; text-align:left; font-size:14px; }
.md th { background:var(--bg-2); font-family:var(--mono); font-size:12px; }
.md blockquote { border-left:3px solid var(--accent-line); padding:6px 16px; color:var(--tx-mid); background:var(--accent-bg); border-radius:0 var(--r-md) var(--r-md) 0; }

/* ---- "Dynasty boot" intro ---- */
#boot { position:fixed; inset:0; z-index:9999; display:grid; place-items:center; background:var(--bg-0); font-family:var(--mono); color:var(--tx-mid); }
#boot.done { animation:boot-out .5s ease forwards; }
#boot .lines { width:min(520px,86vw); font-size:13px; }
#boot .ln { opacity:0; transform:translateY(4px); animation:boot-in .25s ease forwards; }
#boot .ln .ok { color:var(--ok); } #boot .ln .ac { color:var(--accent); }
#boot .ln:nth-child(1){animation-delay:.1s} #boot .ln:nth-child(2){animation-delay:.45s}
#boot .ln:nth-child(3){animation-delay:.8s} #boot .ln:nth-child(4){animation-delay:1.15s}
#boot .skip { position:fixed; bottom:20px; right:24px; font-size:11px; color:var(--tx-faint); cursor:pointer; }
@keyframes boot-in { to { opacity:1; transform:none; } }
@keyframes boot-out { to { opacity:0; visibility:hidden; } }

@media (max-width:760px) {
  /* Stacked, there is no grid area to stick inside, so the rail goes back to a plain block above
     the article -- but it must stay BOUNDED or it is the desktop bug wearing a phone's clothes: at
     full height it puts ~2600px of link list in front of every article (measured: a 933px article
     on a 3888px page). Capped, it reads as one pane you scroll or scroll past. */
  .wiki-layout { grid-template-columns:1fr; }
  .wiki-rail { position:static; max-height:40vh; overflow-y:auto;
    border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--s2) var(--s3); }

  /* THE NAV USED TO DELETE ITSELF HERE. It `display:none`d Hiscores and Wiki (and, signed in, My
     Characters) behind no menu at all, so a phone got a nav of Discord + Sign In -- the links a
     stuck player actually reaches for were exactly the ones removed, with nothing to open. It is
     replaced by a nav that SHRINKS to fit in two stages and never grows past the one 60px row.
     Stage one, down to ~640px: tighter spacing, same words.
     Deliberately NOT `a:not(.btn)`: `.btn.sm` (0,2,0) and `.nav-icon` (0,2,1) already out-specify a
     plain `.nav-links a` (0,1,1), so the Sign In button and the Discord mark keep their own metrics
     without an exception here -- and the icon rules below then out-specify this one. */
  .nav-links { gap:var(--s1); }
  /* `My Characters` is the only nav label containing a space, so it is the only one that can wrap --
     and a wrapped label means a two-row topbar, which this project does not allow. */
  .nav-links a { padding:8px 10px; font-size:13.5px; white-space:nowrap; }
}

/* Stage two -- below ~640px the labels stop fitting (signed in, the bar carries Hiscores, Wiki, My
   Characters, Discord and Sign out), so the words become ICONS instead of vanishing. Same
   destinations, same single row at the same height, no JavaScript and no markup change: a true
   <details>/<summary> disclosure would need markup this file does not own, and an icon row costs
   the player one fewer tap anyway. The label text stays in the DOM at font-size 0, so it is still
   the accessible name, and `.active` still highlights the current section for free.
   Selectors match the shell's nav hrefs (SiteHtml.Shell) -- change those paths and change these.
   Two things pay for the room, both cheap: the wordmark (the emblem beside it is the brand and
   still links home) and the phase tag (a notice, not a destination -- and its only link is the
   Discord invite, which is the icon two places to its right). */
@media (max-width:640px) {
  .wrap { padding:0 var(--s4); }            /* topbar and page content stay on the same gutter */
  .brand .wm, .phase-tag { display:none; }
  .btn.sm { padding:0 var(--s3); }
}
/* Guarded: without mask support the ::before would paint a solid square, so browsers that lack it
   keep the words rather than gaining a row of blocks. */
@supports ((-webkit-mask-image:url("i.svg")) or (mask-image:url("i.svg"))) {
  @media (max-width:640px) {
    .nav-links a[href^="/hiscores"], .nav-links a[href^="/wiki"], .nav-links a[href^="/account"] {
      display:inline-flex; align-items:center; justify-content:center; padding:7px 9px; font-size:0; }
    /* No `color` here on purpose: inheriting `.nav-links a` keeps the existing hover/.active rules
       (equal specificity, declared earlier) in charge of the colour. */
    .nav-links a[href^="/hiscores"]::before,
    .nav-links a[href^="/wiki"]::before,
    .nav-links a[href^="/account"]::before {
      content:""; display:block; width:18px; height:18px; background:currentColor;
      -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
      -webkit-mask-position:center; mask-position:center;
      -webkit-mask-size:18px 18px; mask-size:18px 18px; }
    /* Stroke-only SVGs used as MASKS, not images, so each glyph is painted in `currentColor` and
       picks up the same rest/hover/active colours as the Discord mark beside it. */
    /* A trophy, not a bar chart: rendered at 18px the chart's bars turn to mush, while the trophy
       silhouette survives (checked side by side at 18px and 54px). */
    .nav-links a[href^="/hiscores"]::before {
      -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 4h10v6a5 5 0 0 1-10 0ZM7 5H4v1a4 4 0 0 0 3 3.9M17 5h3v1a4 4 0 0 1-3 3.9M12 15v4M8 21h8'/%3E%3C/svg%3E");
      mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 4h10v6a5 5 0 0 1-10 0ZM7 5H4v1a4 4 0 0 0 3 3.9M17 5h3v1a4 4 0 0 1-3 3.9M12 15v4M8 21h8'/%3E%3C/svg%3E"); }
    .nav-links a[href^="/wiki"]::before {
      -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20'/%3E%3C/svg%3E");
      mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20'/%3E%3C/svg%3E"); }
    .nav-links a[href^="/account"]::before {
      -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3Cpath d='M4 21v-2a5 5 0 0 1 5-5h6a5 5 0 0 1 5 5v2'/%3E%3C/svg%3E");
      mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3Cpath d='M4 21v-2a5 5 0 0 1 5-5h6a5 5 0 0 1 5 5v2'/%3E%3C/svg%3E"); }
  }
}

/* ============ motion / "alive" ============ */
html.js-anim .reveal { opacity:0; transform:translateY(18px);
  transition:opacity .6s cubic-bezier(.2,.7,.2,1), transform .6s cubic-bezier(.2,.7,.2,1); }
html.js-anim .reveal.in { opacity:1; transform:none; }

/* hero: drifting gold light + glowing accent word + floating status chip */
.hero::after { content:""; position:absolute; inset:-25% -15% 35% -15%; pointer-events:none; z-index:0;
  background:radial-gradient(48% 60% at 35% 35%, var(--accent-bg-2) 0%, transparent 62%);
  animation:heroDrift 16s ease-in-out infinite alternate; }
.hero-inner { position:relative; z-index:1; }
@keyframes heroDrift { from{transform:translate(-5%,-3%) scale(1);} to{transform:translate(7%,4%) scale(1.18);} }
.hero h1 b { animation:throneGlow 3.6s ease-in-out infinite; }
@keyframes throneGlow { 0%,100%{text-shadow:0 0 22px rgba(230,177,62,.28);} 50%{text-shadow:0 0 40px rgba(230,177,62,.6);} }
.status-chip { animation:floaty 5s ease-in-out infinite; }
@keyframes floaty { 0%,100%{transform:translateY(0);} 50%{transform:translateY(-4px);} }
.status-chip .dot { animation:dotPulse 2.2s ease-in-out infinite; }
@keyframes dotPulse { 0%,100%{box-shadow:0 0 0 3px var(--ok-bg);} 50%{box-shadow:0 0 0 8px transparent;} }

/* primary button: light sweep on hover */
.btn.primary { position:relative; overflow:hidden; }
.btn.primary::after { content:""; position:absolute; top:0; left:-140%; width:55%; height:100%;
  transform:skewX(-18deg); background:linear-gradient(100deg,transparent,rgba(255,255,255,.4),transparent); }
.btn.primary:hover::after { animation:sweep .7s ease; }
@keyframes sweep { to{ left:150%; } }

/* card / step hover lift + gold glow */
.card.hover:hover { box-shadow:var(--shadow), 0 0 26px -10px var(--accent); }
.step { transition:transform .18s, border-color .18s; }
.step:hover { transform:translateY(-3px); border-color:var(--accent-line); }

/* the imperial emblem as a subtle breathing watermark behind the hero */
/* THE SHOWCASE STRIP. The front page carried no game imagery at all -- a landing page for a game
   whose only picture was its own logo. These are real captures, so they are treated as evidence
   rather than decoration: full-bleed inside their frame, captioned with what the picture PROVES,
   and each one linking to the page that explains it.
   Sized by aspect-ratio, not a fixed height, so a 400x292 GIF and a 1080x470 diagram sit in the
   same row without either being cropped or letterboxed. */
.shots { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:var(--s6); margin-top:var(--s8); }
.shots .shot { margin:0; }
.shots .shot a { display:block; border:1px solid var(--line); border-radius:var(--r-2,8px); overflow:hidden;
  background:#0b1014; line-height:0; transition:border-color .18s ease, transform .18s ease; }
.shots .shot a:hover { border-color:var(--accent-line); transform:translateY(-2px); }
.shots .shot img { width:100%; height:auto; display:block; aspect-ratio:auto; }
.shots figcaption { margin-top:var(--s3); font-size:14px; line-height:1.6; color:var(--tx-mid); }
@media (prefers-reduced-motion:reduce){ .shots .shot a:hover{ transform:none; } }

.hero-emblem { position:absolute; top:50%; left:50%; width:min(540px,82vw); transform:translate(-50%,-54%);
  opacity:.07; pointer-events:none; z-index:0; filter:drop-shadow(0 0 40px rgba(230,177,62,.28));
  animation:emblemBreath 9s ease-in-out infinite; }
@keyframes emblemBreath { 0%,100%{opacity:.06;transform:translate(-50%,-54%) scale(1);} 50%{opacity:.11;transform:translate(-50%,-54%) scale(1.04);} }

@media (prefers-reduced-motion:reduce){
  html.js-anim .reveal{opacity:1;transform:none;transition:none;}
  .hero::after,.hero h1 b,.status-chip,.status-chip .dot,.brand .mark,.hero-emblem{animation:none;}
}

/* ---- the pulse strip: next event leads, totals support ----
   One band replacing what used to be two (a five-tile stats row, then a separate scrolling
   events strip). The countdown is the headline because it is the only element here that says
   something worth acting on when the server is young and every total reads zero. The totals
   keep their place at a supporting rank -- present, scannable, no longer the pitch. */
.pulse { padding:22px 0; border-top:1px solid var(--line); border-bottom:1px solid var(--line);
  background:linear-gradient(180deg, var(--accent-bg), transparent 85%), var(--bg-1); }
.pulse-row { display:flex; align-items:center; justify-content:center; flex-wrap:wrap;
  gap:clamp(18px,4vw,44px); }
/* The divider belongs to the LEAD, so it vanishes with it -- a rule floating beside a centred
   row of numbers with nothing on its left is the "centred tiles, left-aligned footnote" defect
   in another costume. */
.pulse-next { display:flex; align-items:baseline; flex-wrap:wrap; gap:clamp(8px,1.6vw,14px);
  padding-right:clamp(18px,4vw,44px); border-right:1px solid var(--line); }
.pn-lead { font-family:var(--mono); font-size:11px; letter-spacing:.11em; text-transform:uppercase;
  color:var(--accent); }
/* THE EVENT NAME IS A LINK AND DID NOT LOOK LIKE ONE. Two separate jobs, because motion alone is
   not an affordance: something has to say "clickable" while the page is still, and something has to
   catch the eye while it is not being looked at.
     · at rest: a faint gold underline, so it reads as a link with no animation at all;
     · periodically: a bright band sweeps the letters like light crossing polished gilt. */
.pn-ev { display:inline-flex; align-items:baseline; gap:10px; text-decoration:none;
  border-bottom:1px solid rgba(230,177,62,.28); transition:border-color .15s; }
.pn-ev b { color:var(--tx-hi); font-weight:600; font-size:clamp(17px,2.2vw,21px); letter-spacing:-.01em; }
.pn-ev .ev-t { font-family:var(--mono); font-variant-numeric:tabular-nums; color:var(--accent);
  font-size:clamp(17px,2.2vw,21px); }
.pn-ev:hover { border-bottom-color:var(--accent); }
.pn-ev .pn-go { color:var(--accent); font-size:.8em; opacity:.55;
  transition:transform .18s ease, opacity .18s ease; }
.pn-ev:hover .pn-go { opacity:1; transform:translateX(3px); }

/* The gleam. GUARDED: painting text with a clipped background means the glyphs are drawn by the
   background, so if background-clip:text is unsupported, `color:transparent` would erase the words
   entirely. Only inside @supports does the text hand itself over. */
@supports ((-webkit-background-clip:text) or (background-clip:text)) {
  .pn-ev b {
    background-image:linear-gradient(100deg,
      var(--tx-hi) 0%, var(--tx-hi) 38%, #fff4d6 48%, #ffe9b0 52%, var(--tx-hi) 62%, var(--tx-hi) 100%);
    background-size:280% 100%;
    background-position:120% 0;
    -webkit-background-clip:text; background-clip:text;
    color:transparent;
    animation:gleam 6s ease-in-out infinite;
  }
  /* Long pause, quick pass: a shimmer that never rests reads as a broken GIF rather than as metal.
     The sweep occupies the last ~25% of the cycle, so it catches the eye and then leaves it alone. */
  @keyframes gleam { 0%,72%{background-position:120% 0;} 100%{background-position:-20% 0;} }
  .pn-ev:hover b { animation-duration:1.6s; }
}
@media (prefers-reduced-motion:reduce) {
  .pn-ev b { animation:none; background-position:120% 0; }
  .pn-ev .pn-go { transition:none; }
}
.pn-ev:focus-visible { outline:2px solid var(--accent); outline-offset:3px; border-radius:2px; }
.pn-then { font-family:var(--mono); font-size:11.5px; color:var(--tx-lo); letter-spacing:.02em; }

.pulse-mini { display:flex; gap:clamp(16px,3.4vw,38px); flex-wrap:wrap; text-align:center; }
.pstat-n { font-family:var(--mono); font-variant-numeric:tabular-nums; font-size:clamp(19px,2.6vw,25px);
  font-weight:600; color:var(--accent); line-height:1; display:flex; align-items:center;
  justify-content:center; gap:7px; }
.pstat-l { font-family:var(--mono); font-size:10.5px; letter-spacing:.11em; text-transform:uppercase;
  color:var(--tx-lo); margin-top:6px; }
/* A heartbeat when someone is actually in world, cold and still when nobody is. The state is
   readable without parsing the digit, and an empty world says so honestly rather than pulsing
   green over a zero. */
/* The mini live-dot is GONE: the status chip above is the one live indicator, and a second one
   here could only repeat it somewhere it cannot mean server health. */

/* Narrow: the trailing "then …" hint goes first, then the lead stops being a side-by-side
   column and becomes a stacked header with a rule underneath instead of beside. */
@media (max-width:820px){ .pn-then { display:none; } }
@media (max-width:640px){
  .pulse-row { flex-direction:column; gap:18px; }
  .pulse-next { padding-right:0; padding-bottom:16px; border-right:0; border-bottom:1px solid var(--line);
    width:100%; justify-content:center; }
  .pulse-mini { gap:clamp(14px,7vw,30px); }
}

.classcard { text-align:center; } .classcard h3 { color:var(--tx-hi); }
.classcard .ic { margin-left:auto; margin-right:auto; }

/* ---- wiki index: category sections ---- */
.wiki-cat { margin-bottom:var(--s10); }
.wiki-cat-h { font-family:var(--mono); font-size:12px; letter-spacing:.12em; text-transform:uppercase;
  color:var(--accent); margin:0 0 var(--s4); padding-bottom:8px; border-bottom:1px solid var(--line); }

/* The old `.events-strip` (a separate band below the stats, with a sweeping sheen and a row of
   pill-shaped countdowns) is GONE -- the pulse strip above absorbed it. Two adjacent bands each
   competing for the same attention was the reason neither landed. */

/* ================= /stats — "the realm in numbers" =================
   Hierarchy over uniformity: four headline tiles, then proportion bars where a SHARE is the story and
   compact stat rows where an exact count is. Everything scrolls inside its own box so the page never
   scrolls sideways on a phone. */
.stats-page{padding:44px 24px 72px;max-width:1080px}
.stats-head h2{margin:0;font-size:30px;letter-spacing:-.01em}
.stats-head p{margin:6px 0 26px}

.stat-tiles{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin-bottom:26px}
.stat-tile{padding:18px 20px;border:1px solid rgba(255,255,255,.09);border-radius:12px;
  background:linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,.015))}
.stat-tile .v{display:block;font:600 30px/1.05 var(--mono,ui-monospace,monospace);color:var(--accent);
  letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.stat-tile .k{display:block;margin-top:7px;font-size:13px;font-weight:600}
.stat-tile .s{display:block;font-size:11px;opacity:.55;margin-top:2px}

.panel{padding:20px 22px;border:1px solid rgba(255,255,255,.09);border-radius:12px;
  background:rgba(255,255,255,.022);margin-bottom:18px}
.panel h3{margin:0 0 4px;font-size:16px}
.panel > .faint{margin:0 0 14px;font-size:12px}
.stats-cols{display:grid;gap:18px;grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}

/* proportion bar */
.prop{display:flex;height:14px;border-radius:7px;overflow:hidden;background:rgba(255,255,255,.05)}
.prop i{display:block;height:100%}
.prop-key{list-style:none;margin:14px 0 0;padding:0;display:grid;gap:6px;
  grid-template-columns:repeat(auto-fit,minmax(190px,1fr))}
.prop-key li{display:flex;align-items:center;gap:8px;font-size:12.5px}
.prop-key li i{width:9px;height:9px;border-radius:2px;flex:none}
.prop-key li b{margin-left:auto;font-variant-numeric:tabular-nums}
.prop-key li span{min-width:44px;text-align:right;font-size:11px}

/* compact count grid */
.minis{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(130px,1fr))}
.mini{padding:12px 14px;border-radius:10px;background:rgba(255,255,255,.035);
  border:1px solid rgba(255,255,255,.06)}
.mini .n{display:block;font:600 19px/1.1 var(--mono,ui-monospace,monospace);
  font-variant-numeric:tabular-nums;color:var(--accent)}
.mini .l{display:block;font-size:11.5px;opacity:.72;margin-top:3px}

.stats-page .tablewrap{overflow-x:auto;margin-top:12px}
.stats-page table.ladder{width:100%}
.stats-page table.ladder td{padding:6px 10px}
.stats-foot{margin-top:22px}

/* ---- wiki search (every wiki page), the right TOC, the folded rail, the category strip ---- */
.wsearch { position:relative; margin:0 0 var(--s4); }
.wsearch input { width:100%; height:38px; padding:0 var(--s3); border-radius:var(--r-md); background:var(--bg-2); border:1px solid var(--line-2); color:var(--tx-hi); font-size:14px; }
.wsearch input:focus { outline:none; border-color:var(--accent); }
.wsearch.wsearch-lg input { height:44px; font-size:16px; }
.wsearch-list { position:absolute; left:0; right:0; top:calc(100% + 4px); z-index:30; background:var(--bg-1); border:1px solid var(--line-strong); border-radius:var(--r-md); box-shadow:0 14px 36px rgba(0,0,0,.5); max-height:70vh; overflow:auto; min-width:min(420px, 90vw); }
.wsearch-hit { display:block; padding:10px 12px; border-bottom:1px solid var(--line-2); color:var(--tx-mid); }
.wsearch-hit:last-of-type { border-bottom:0; }
.wsearch-hit:hover, .wsearch-hit.on { background:var(--bg-2); color:var(--tx-hi); }
.wsearch-t { font-size:14px; line-height:1.3; } .wsearch-t b { color:var(--tx-hi); }
.wsearch-cat { font:10px var(--mono); letter-spacing:.07em; text-transform:uppercase; color:var(--accent); margin-left:8px; }
.wsearch-sec { color:var(--tx-lo); font-size:13px; margin-left:8px; }
.wsearch-s { font-size:13px; color:var(--tx-lo); margin-top:3px; line-height:1.45; }
.wsearch mark, .wsearch-results mark { background:transparent; color:var(--accent); font-weight:600; }
.wsearch-all, .wsearch-none { display:block; padding:9px 12px; font-size:13px; color:var(--tx-lo); }
.wsearch-all:hover { color:var(--tx-hi); }
.wsearch-results .wsearch-hit-lg { border:1px solid var(--line-2); border-radius:var(--r-md); margin-bottom:var(--s3); padding:14px 16px; }
.wsearch-results .wsearch-t { font-size:16px; } .wsearch-results .wsearch-s { font-size:14px; margin-top:6px; }
.wiki-cats { display:flex; flex-wrap:wrap; gap:6px; margin:0 0 var(--s6); }
.wiki-cats a { padding:5px 11px; border-radius:999px; font-size:13px; border:1px solid var(--line-2); color:var(--tx-mid); }
.wiki-cats a span { color:var(--tx-lo); margin-left:6px; font-family:var(--mono); font-size:11px; }
.wiki-cats a:hover { color:var(--tx-hi); border-color:var(--line-strong); }
.wiki-rail details.grp summary { cursor:pointer; list-style:none; font-family:var(--mono); font-size:10px; letter-spacing:.08em; text-transform:uppercase; color:var(--tx-lo); margin:var(--s4) 0 var(--s1); padding:4px 10px; user-select:none; }
.wiki-rail details.grp summary::-webkit-details-marker { display:none; }
.wiki-rail details.grp summary::before { content:'\25B8  '; } .wiki-rail details.grp[open] summary::before { content:'\25BE  '; }
.wiki-rail details.grp summary:hover { color:var(--tx-hi); }
.wtoc { display:none; }
@media (min-width:1180px) {
  .wrap.wiki-wrap { max-width:1320px; }
  .wiki-layout.has-toc { grid-template-columns:220px minmax(0,1fr) 200px; }
  .wtoc { display:block; position:sticky; top:76px; font-size:13px; max-height:calc(100vh - 100px); overflow-y:auto; }
  .wtoc-h { font-family:var(--mono); font-size:10px; letter-spacing:.08em; text-transform:uppercase; color:var(--tx-lo); margin:0 0 var(--s2); }
  .wtoc a { display:block; padding:4px 0 4px 10px; border-left:2px solid var(--line-2); color:var(--tx-lo); line-height:1.35; }
  .wtoc a.l3 { padding-left:22px; font-size:12px; }
  .wtoc a:hover { color:var(--tx-hi); } .wtoc a.on { color:var(--accent); border-color:var(--accent); }
  .has-toc .wsec { display:none; }
}
