/* ==========================================================================
   ⭐ THE BRAND FACE — declared here so EVERY page can load it (2026-07-27).
   --------------------------------------------------------------------------
   Chris: "there is a difference on login, the L2Everdream logo is more
   well defined … something on the homepage is correcting the text and making it
   smaller, harder to read."
   MEASURED, it was the reverse of a design choice: both pages set the same 23px
   /600, but the homepage rendered in Cormorant Garamond (119px wide) and the
   account pages in GEORGIA (162px) — because these @font-face rules lived inside
   index.astro's <style>, so nothing else on the site could see them. The account
   pages were silently falling back.
   ⚠️ Georgia is the wider, sturdier face, which is why it read as better defined.
   The ruling on 2026-07-21 was to keep Cormorant and fix legibility with SIZE and
   WEIGHT instead, so the header matches the hero wordmark — that stands, and now
   it actually applies everywhere.
   ========================================================================== */
@font-face{font-family:'Cormorant Garamond'; font-style:normal; font-weight:400;
  font-display:swap; src:url('/assets/fonts/cormorant-garamond-latin-400-normal.woff2') format('woff2');}
@font-face{font-family:'Cormorant Garamond'; font-style:normal; font-weight:500;
  font-display:swap; src:url('/assets/fonts/cormorant-garamond-latin-500-normal.woff2') format('woff2');}
@font-face{font-family:'Cormorant Garamond'; font-style:normal; font-weight:600;
  font-display:swap; src:url('/assets/fonts/cormorant-garamond-latin-600-normal.woff2') format('woff2');}

/* ============================================================================
   L2Everdream — shared design system
   ----------------------------------------------------------------------------
   One stylesheet, linked by index.html / about.html / devlog.html.
   Palette is PINNED by Chris: Discord Blurple on a blurple-biased dark theme,
   with one restrained warm lantern-amber as "the lone player's light".
   Voice + naming: docs/brand/BRAND.md · palette lineage: docs/brand/visual-identity.md
   Pages keep their own <style> only for the hero / canvas.
   ========================================================================== */

/* ---------- Tokens ---------- */
:root {
  /* Surfaces — blurple-biased near-blacks (dark theme, twilight register) */
  --void:       #0B0A18;
  --field:      #12102A;
  --surface:    #1A1740;
  --surface-2:  #221E52;

  /* Discord Blurple family (the pinned accent) */
  --blurple:      #5865F2;
  --blurple-lit:  #8B94F7;
  --blurple-hi:   #6B78FF;
  --light-blurple:#E0E3FF;

  /* Text */
  --starlight:  #F5F6FF;
  --body:       #C9CCEA;
  --greyple:    #9096B8;
  --greyple-dim:#6A6E90;

  /* The lone player's light — used sparingly, never as a second brand color */
  --lantern:    #F0B67A;

  /* Living / "alive" whisper (a hair of sage, never a highlighter) */
  --verdance:   #93C0AC;

  /* Lines & wash */
  --line:       rgba(136, 148, 247, 0.14);
  --line-soft:  rgba(136, 148, 247, 0.09);
  --wash:       rgba(88, 101, 242, 0.06);

  /* Type */
  /* ⚠️ CORMORANT FIRST. This listed Georgia first, so every page reading this
     token rendered the wordmark in Georgia while the homepage — which declares its
     own token with Cormorant first — rendered the real face. Same bug index.astro
     already documented for itself in 2026-07-21: "it was listed after Georgia
     here, so Georgia always won". It was fixed there and not here. */
  --serif: 'Cormorant Garamond', Georgia, 'Spectral', 'Times New Roman', serif;
  --sans:  system-ui, 'Segoe UI', Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  --mono:  'JetBrains Mono', 'SFMono-Regular', Consolas, ui-monospace, 'Courier New', monospace;

  /* Rhythm */
  /* ⚠️ 1360, MATCHING THE PAGES (Chris 2026-07-27: "the dream log also uses a
     different content width than homepage, so the nav basically shrinks visually
     on the move — it should be seamless"). This said 1080 while index/features/
     devlog each declared 1360 in their own <style>, so any page relying on the
     shared token — the account manager — was 280px narrower than the rest of the
     site and the bar visibly resized as you navigated. */
  --wrap:     1360px;
  --measure:  62ch;
  --radius:   14px;
  --radius-sm:11px;
}

/* ---------- Reset / base ---------- */
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--void);
  color: var(--starlight);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img, svg, canvas { max-width: 100%; }
a { color: var(--blurple-lit); text-decoration-color: rgba(139,148,247,0.4); text-underline-offset: 3px; }
a:hover { color: var(--light-blurple); }
:focus-visible { outline: 2px solid var(--blurple-lit); outline-offset: 3px; border-radius: 3px; }

.wrap    { max-width: var(--wrap); margin: 0 auto; padding: 0 28px; }
.measure { max-width: var(--measure); }
.center  { text-align: center; }

/* Skip link (accessibility) */
.skip {
  position: absolute; left: -999px; top: 0; z-index: 100;
  background: var(--blurple); color: #fff; padding: 10px 16px;
  border-radius: 0 0 8px 0; font-weight: 600; font-size: 14px;
  /* WCAG 2.5.5 (AAA) / Apple HIG: 44px minimum touch target. The skip link is
     keyboard-first, but it IS focusable and tappable once revealed, so it is
     held to the same floor as every other standalone control. Growing the box
     via min-height + centring changes nothing about how it looks when hidden. */
  min-height: 44px; display: inline-flex; align-items: center;
}
.skip:focus { left: 0; }

/* ---------- Top navigation (shared across all pages) ---------- */
.nav {
  position: sticky; top: 0; z-index: 40;
  background: rgba(11,10,24,0.72);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line-soft);
}
.nav-in {
  max-width: var(--wrap); margin: 0 auto;
  padding: 13px 28px;
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
}
/* The 44px floor (WCAG 2.5.5 AAA / Apple HIG) applies to every standalone
   control in the bar. Reached with min-height + flex centring rather than
   padding so the bar's visual rhythm — type size, spacing, the 13px .nav-in
   padding — is untouched; only the hit area grows. */
.nav-mark {
  font-family: var(--serif); font-size: 21px; font-weight: 500;
  color: var(--starlight); text-decoration: none; letter-spacing: -0.01em;
  /* align-items stays `baseline` — it is what sits the italic "L2" on the same
     baseline as the wordmark (and why .spark carries its own align-self:center).
     The 44px floor comes from min-height alone, which does not disturb it. */
  display: inline-flex; align-items: baseline; gap: 1px;
  min-height: 44px;
}
/* THE L2 GLYPH MATCHES THE HOMEPAGE — lantern amber gradient, not flat blurple.
   The homepage (index.astro) wordmark uses this exact gradient; the shared nav
   was a generation behind on flat --blurple-lit, so account/about/devlog read as
   a slightly different brand. One gradient here brings every shared-nav page in
   line. Chris 2026-07-21: use the homepage header as the reference across the
   site. Upright (not italic) to match the homepage mark. */
.nav-mark .l2 {
  font-size: 0.82em; margin-right: 1px;
  background: linear-gradient(180deg, #F6C67A, #E8A24C);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.nav-mark:hover { color: var(--starlight); }
.nav-mark .spark {
  width: 7px; height: 7px; border-radius: 50%; margin-right: 9px;
  background: var(--lantern); box-shadow: 0 0 9px 1px rgba(240,182,122,0.6);
  align-self: center;
}
.nav-links { display: flex; align-items: center; gap: 6px; }
.nav-links a {
  font-family: var(--sans); font-size: 14.5px; font-weight: 500;
  color: var(--greyple); text-decoration: none;
  padding: 8px 12px; border-radius: 8px; transition: color .15s ease, background .15s ease;
  /* 44px floor — see .nav-mark. inline-flex + centring keeps the label optically
     where it already was; the hit area (and the hover pill) grow around it. */
  min-height: 44px; display: inline-flex; align-items: center;
}
.nav-links a:hover { color: var(--light-blurple); background: rgba(136,148,247,0.07); }
.nav-links a[aria-current="page"] { color: var(--light-blurple); }
.nav-links a.discord {
  color: #fff; background: var(--blurple);
  box-shadow: 0 8px 22px -12px rgba(88,101,242,0.8);
}
.nav-links a.discord:hover { background: var(--blurple-hi); color: #fff; }
/* SITE-3 — the PENDING Discord control (no public invite configured yet). It
   already cannot navigate: community.mjs emits no href at all, so the anchor is
   not focusable and clicking it does nothing in the DOM sense. These rules stop
   it LOOKING clickable, which is the half of the defect CSS can fix — a control
   that lights up under the cursor is promising a click it cannot honour. Keyed
   off an attribute that is not emitted once PUBLIC_DISCORD_INVITE is set, so the
   live control needs no CSS change and these rules cost nothing then. */
.nav-links a.discord[data-discord-pending],
.nav-links a.discord[data-discord-pending]:hover {
  background: rgba(88,101,242,0.30); color: rgba(255,255,255,0.72);
  box-shadow: none; cursor: default;
}
.btn.primary[data-discord-pending],
.btn.primary[data-discord-pending]:hover {
  background: rgba(136,148,247,0.07); color: rgba(136,148,247,0.62);
  border-color: rgba(136,148,247,0.25); cursor: default; transform: none;
}
.foot-links a[data-discord-pending],
.foot-links a[data-discord-pending]:hover {
  opacity: 0.55; cursor: default; background: transparent;
}
.nav-links a .di { width: 16px; height: 16px; vertical-align: -3px; margin-right: 6px; }

/* THE LAUNCHER CTA — matches the homepage nav's "Get the launcher" pill: a
   blurple-tinted chip with a hairline, NOT a solid fill. ⚠️ HOVER GOES DARKER,
   not lighter — --blurple-hi (#6B78FF) fails WCAG AA with white text at 3.65:1,
   so hovering to it (as .discord does above) breaks contrast mid-interaction.
   This uses --light-blurple ink on a tint (passes) and deepens the tint on hover
   with #4752C4 (Discord's own hover, 6.42:1). Chris 2026-07-21: basic ADA
   compliance is a standing rule; the homepage's contrast gate asserts it. */
.nav-links a.nav-cta {
  color: var(--light-blurple);
  background: rgba(88,101,242,0.18);
  border: 1px solid rgba(136,148,247,0.38);
}
.nav-links a.nav-cta:hover { background: #4752C4; color: #fff; border-color: transparent; }

/* ---------- Section bands ---------- */
section.band { padding: 104px 0; border-top: 1px solid var(--line); }
.band.alt {
  background:
    radial-gradient(90% 60% at 50% 0%, var(--wash), transparent 70%),
    var(--field);
}
.band.tight { padding: 76px 0; }

/* ---------- Atmospheric map backgrounds ----------
   Real zone geodata + runner traffic (assets/traffic-*.png), re-colored to the site
   palette, dropped behind a section as ambient texture. Usage:
     <section class="band has-map"> ... <div class="wrap">content</div>
       <img class="map-bg" src="assets/traffic-giran.png" alt="" aria-hidden="true"
            width="738" height="664" loading="lazy" decoding="async"
            onerror="this.style.display='none'">
     </section>
   The .map-veil pseudo-layer keeps text at full AA contrast over the art.
   Place the <img> as the LAST child so the stacking (content above) is simple. */
.has-map { position: relative; isolation: isolate; overflow: hidden; }
/* lift every child EXCEPT the map image above the art + veil.
   (the map itself is pinned to z-index 0 below; excluding it here avoids a
    specificity clash where this rule would otherwise override that.) */
.has-map > *:not(.map-bg) { position: relative; z-index: 2; }

.map-bg {
  position: absolute; z-index: 0; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  /* Chris: the runner maps were too dark to see — show them off. Lifted from ~0.16 so the real geodata
     lanes actually READ as a feature of the section, not a barely-there texture. */
  opacity: 0.38;
  pointer-events: none; user-select: none;
  mix-blend-mode: screen;            /* amber linework glows on the dark ground */
  filter: saturate(1.0) brightness(1.15);
  /* very slow, tiny drift — parallax feel without motion sickness */
  animation: mapdrift 46s ease-in-out infinite alternate;
  will-change: transform;
}
/* per-section framing so each map reads differently / crops interestingly */
.map-bg.pos-right  { object-position: 78% 42%; }
.map-bg.pos-left   { object-position: 22% 46%; }
.map-bg.pos-top    { object-position: 50% 18%; }
.map-bg.dim        { opacity: 0.28; }
.map-bg.bright     { opacity: 0.48; }

/* The veil: a dark gradient sitting ABOVE the map, BELOW the content, so copy stays legible.
   z-index 1 = between the map (0) and content (2). */
.has-map::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  /* Lightened (Chris: the maps were too dark). A softer veil lets the runner lanes show while keeping
     text legible — the linear pass is stronger toward the text side, lighter toward the map's open side. */
  background:
    radial-gradient(120% 120% at 50% 40%, rgba(11,10,24,0.16) 0%, rgba(11,10,24,0.42) 72%, rgba(11,10,24,0.66) 100%),
    linear-gradient(180deg, rgba(11,10,24,0.34) 0%, rgba(11,10,24,0.14) 42%, rgba(11,10,24,0.4) 100%);
}
/* on the alt (lighter field) band, deepen the veil a touch to hold contrast */
.band.alt.has-map::after {
  background:
    radial-gradient(120% 120% at 50% 40%, rgba(18,16,42,0.22) 0%, rgba(18,16,42,0.5) 74%, rgba(11,10,24,0.7) 100%),
    linear-gradient(180deg, rgba(18,16,42,0.4) 0%, rgba(18,16,42,0.2) 42%, rgba(11,10,24,0.46) 100%);
}

@keyframes mapdrift {
  from { transform: scale(1.06) translate(-1.2%, -0.8%); }
  to   { transform: scale(1.06) translate(1.2%, 0.8%); }
}

.kicker {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.3em;
  text-transform: uppercase; color: var(--blurple-lit);
  margin: 0 0 20px;
}
.kicker .spark {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--lantern); box-shadow: 0 0 9px 1px rgba(240,182,122,0.6);
  margin-right: 9px; vertical-align: middle;
}

h1, h2, h3 { text-wrap: balance; }
h2 {
  font-family: var(--serif); font-weight: 500;
  font-size: clamp(30px, 5vw, 46px); line-height: 1.08;
  letter-spacing: -0.01em; margin: 0 0 22px; max-width: 20ch; color: var(--starlight);
}
h3 { font-family: var(--serif); font-weight: 500; color: var(--starlight); }
.lede { font-size: 20px; color: var(--light-blurple); max-width: 60ch; margin: 0; }
p { color: var(--body); }
p.measure { margin: 0 0 18px; }
strong { color: var(--starlight); font-weight: 600; }
em.q { color: var(--blurple-lit); font-style: italic; }
.dim { color: var(--greyple); }

/* ---------- The "simulation, not generation" reframe pull-quote ---------- */
.reframe {
  margin: 40px 0 8px;
  padding: 30px 34px;
  border-left: 2px solid var(--blurple);
  background: linear-gradient(180deg, rgba(88,101,242,0.08), rgba(88,101,242,0.02));
  border-radius: 0 var(--radius) var(--radius) 0;
}
.reframe > p {
  margin: 0; font-family: var(--serif); font-style: italic;
  font-size: clamp(20px, 2.8vw, 26px); line-height: 1.4; color: var(--starlight);
}
.reframe .sub {
  font-family: var(--sans); font-style: normal; font-size: 15.5px;
  color: var(--greyple); margin-top: 14px;
}

/* ---------- "What makes it alive" cards ---------- */
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-top: 44px; }
.card {
  padding: 26px 24px 28px;
  background: linear-gradient(180deg, var(--surface), rgba(18,16,42,0.6));
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.card .n {
  font-family: var(--mono); font-size: 12px; color: var(--blurple-lit);
  letter-spacing: 0.1em; margin-bottom: 14px;
}
.card h3 { font-size: 22px; margin: 0 0 10px; }
.card p { font-size: 15px; margin: 0; color: var(--greyple); }

/* ---------- Devlog rows (teaser on home; list on devlog page) ---------- */
.logrow {
  display: grid; grid-template-columns: 132px 1fr; gap: 22px;
  padding: 24px 0; border-top: 1px solid var(--line); align-items: baseline;
}
.logrow:last-child { border-bottom: 1px solid var(--line); }
.logrow .when {
  /* --greyple-dim is 3.96:1 on the void — fails WCAG AA for content text (axe P1). Dates are content. */
  font-family: var(--mono); font-size: 12.5px; color: var(--greyple);
  letter-spacing: 0.04em;
}
.logrow .what h3, .logrow .what h4 {
  font-family: var(--sans); font-weight: 600; font-size: 18px;
  margin: 0 0 7px; color: var(--starlight); line-height: 1.35;
}
.logrow .what p { margin: 0; font-size: 15px; color: var(--greyple); }
.logrow .what p + p { margin-top: 10px; }
.logrow .what .metric {
  font-family: var(--mono); color: var(--verdance); font-size: 0.92em;
}

/* Tag pill */
.tag {
  font-family: var(--mono); font-size: 10.5px; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--blurple-lit);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 2px 9px; margin-left: 8px; white-space: nowrap;
  display: inline-block; line-height: 1.5;
}

/* ---------- Buttons ---------- */
.btns { display: flex; gap: 14px; flex-wrap: wrap; }
.btn {
  font-family: var(--sans); font-weight: 600; font-size: 15.5px;
  text-decoration: none; padding: 14px 26px; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; gap: 10px;
  transition: transform .15s ease, box-shadow .2s ease, background .2s ease;
}
/* ⚠️ OPTION C (Chris 2026-07-27, chosen from /button-lab): amber and Discord-blue
   are the ONLY filled buttons on the site. Everything else is an outline.
   THE RULE, in his words: "the one real action on whatever page you're on — it's
   primary." Amber marks that one action; an outline marks everything else.

   This was `background: var(--blurple)` = #5865F2 — Discord Blurple EXACTLY —
   with a #4752C4 hover, which is Discord's darker blurple. Measured: the Discord
   button beside it renders rgb(93,95,239), so "Read the wiki" was wearing
   Discord's brand primary MORE precisely than the actual Discord link.

   ⚠️ Do not refill this with a colour. If an action needs more pull than an
   outline gives, it is either the page's one amber action or it is not primary.
   (The old note here warned that hover must go DARKER because --blurple-hi fails
   AA with white text at 3.65:1. Moot now — there is no white-on-blurple state
   left — but the lesson stands for any future filled button.) */
.btn.primary {
  background: rgba(136,148,247,0.10); color: var(--light-blurple);
  border: 1px solid rgba(136,148,247,0.45);
}
@media (hover: hover) {
  .btn.primary:hover { background: rgba(136,148,247,0.18);
    border-color: rgba(136,148,247,0.7); transform: translateY(-2px); }
}
.btn.ghost {
  background: rgba(136,148,247,0.06); color: #EDEFFF;
  border: 1px solid var(--line);
}
.btn.ghost:hover { background: rgba(136,148,247,0.18); color: #fff; transform: translateY(-2px); }
.btn .di { width: 18px; height: 18px; }

/* ---------- CTA band ---------- */
.cta-band {
  text-align: center; padding: 116px 0 124px;
  background:
    radial-gradient(80% 70% at 50% 30%, rgba(88,101,242,0.12), transparent 70%),
    var(--void);
}
.cta-band h2 { margin: 0 auto 14px; }
.cta-band .lede { margin: 0 auto 34px; }
.cta-band .btns { justify-content: center; }

/* ---------- Map gallery (About: maps shown AS content, not background) ---------- */
.map-gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 18px; margin: 34px 0 8px; }
.map-fig {
  margin: 0; position: relative; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--void);
}
.map-fig img {
  display: block; width: 100%; height: 220px;
  object-fit: cover; object-position: center;
  opacity: 0.94;
}
.map-fig figcaption {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 22px 16px 12px;
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.04em;
  color: var(--light-blurple);
  background: linear-gradient(180deg, rgba(11,10,24,0) 0%, rgba(11,10,24,0.85) 60%);
}
.map-fig figcaption b { color: var(--starlight); font-weight: 600; }
.map-fig figcaption .z { color: var(--greyple); }
@media (max-width: 640px) { .map-gallery { grid-template-columns: 1fr; } }

/* ============================================================================
   LIVING WINDOWS — the gallery of animated zones ("See the world")
   ----------------------------------------------------------------------------
   Each window = a real zone map (assets/zone-*.png / traffic-*.png) as a dim
   substrate, animated role-coloured dots ON TOP (canvas), a caption, and a
   streaming world-log rail. Extends the hero's single .worldwin to many zones.
   Shared here so index.html and world.html both use it. JS lives per-page.
   Role palette matches the hero: green tank / blue dps / violet heal / amber mob.
   ========================================================================== */
.livewin-intro { max-width: 60ch; }

/* the responsive tile grid — windows reflow from 1-up (mobile) to 2-up (wide) */
.livewins {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: 22px; margin-top: 46px; align-items: start;
}
@media (max-width: 860px) { .livewins { grid-template-columns: 1fr; } }

/* one window: map + canvas stacked, caption + log below, in a bordered card */
.livewin {
  position: relative; margin: 0;
  border: 1px solid rgba(136,148,247,0.28); border-radius: 16px;
  background: #0A0917; overflow: hidden;
  box-shadow: 0 30px 70px -40px rgba(0,0,0,0.9),
    0 0 0 1px rgba(11,10,24,0.6) inset, 0 0 48px -22px rgba(88,101,242,0.3);
}
/* the stage: the map image + the canvas, same box, dots over map.
   EVERY window shares one aspect ratio for a tidy, uniform grid. */
.lw-stage {
  position: relative; display: block; width: 100%;
  aspect-ratio: 16 / 10;
  background: radial-gradient(140% 120% at 50% 30%, #14122E 0%, #0B0A18 72%);
  overflow: hidden;
}
.lw-map {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  /* the maps now share ONE aesthetic: DARK buildings on a muted dark-blue walkable
     surface (the runner-traffic look). Composite them normally (not screen — that
     was for the old amber-glow render) and lift them a touch so the structure reads
     while staying quiet under the bright sim dots. */
  opacity: 0.85;
  mix-blend-mode: normal;
  filter: saturate(1.05) brightness(1.18) contrast(1.05);
  pointer-events: none; user-select: none;
}
/* the Giran window has NO overlay dots — the real routed-runner traffic IS the picture, so show it
   fuller and let its orange lanes glow (screen blend), rather than dimming it as a substrate. */
.lw-map.lw-map-full {
  object-fit: cover; opacity: 0.96;
  mix-blend-mode: screen;
  filter: saturate(1.08) brightness(1.12) contrast(1.06);
}
.lw-stage canvas {
  position: absolute; inset: 0; display: block; width: 100%; height: 100%;
}
/* a faint vignette so dots near the edges stay legible over busy maps */
.lw-stage::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(120% 100% at 50% 45%, transparent 55%, rgba(10,9,23,0.55) 100%);
}

/* live badge, top-left of each stage */
.lw-badge {
  position: absolute; top: 11px; left: 12px; z-index: 3;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 10px; border-radius: 999px;
  background: rgba(10,9,23,0.66); border: 1px solid rgba(136,148,247,0.18);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--light-blurple);
}
.lw-badge .live-dot { width: 6px; height: 6px; }

/* compact per-window legend, top-right */
.lw-legend {
  position: absolute; top: 11px; right: 12px; z-index: 3;
  display: flex; gap: 10px; flex-wrap: wrap; justify-content: flex-end;
  padding: 6px 9px; border-radius: 9px; max-width: 62%;
  background: rgba(10,9,23,0.58); border: 1px solid rgba(136,148,247,0.14);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.03em; color: var(--greyple);
}
.lw-legend span { display: inline-flex; align-items: center; gap: 5px; }
.lw-legend i { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
/* role swatches for the window legends (mirror the JS COL palette).
   town = a soft periwinkle-blue walker, distinct from dps-blurple — ambient adventurers, not a party. */
.lw-legend .k-tank { background: #5BE0A8; box-shadow: 0 0 6px #5BE0A8; }
.lw-legend .k-dps  { background: #5865F2; box-shadow: 0 0 6px #5865F2; }
.lw-legend .k-heal { background: #C9A0FF; box-shadow: 0 0 6px #C9A0FF; }
.lw-legend .k-mob  { background: #F0B67A; box-shadow: 0 0 6px #F0B67A; }
.lw-legend .k-town { background: #96B4F0; box-shadow: 0 0 6px #96B4F0; }

/* the caption strip under the stage */
.lw-cap {
  padding: 12px 15px 11px; border-top: 1px solid rgba(136,148,247,0.14);
  background: rgba(10,9,23,0.55);
}
.lw-cap .lw-zone {
  font-family: var(--serif); font-size: 18px; color: var(--starlight);
  margin: 0; line-height: 1.2;
}
.lw-cap .lw-desc {
  font-family: var(--sans); font-size: 13.5px; color: var(--greyple);
  margin: 4px 0 0; line-height: 1.45;
}

/* the streaming world-log rail — a terminal feed under the caption */
.lw-log {
  border-top: 1px solid rgba(136,148,247,0.10);
  background:
    linear-gradient(180deg, rgba(9,8,20,0.85), rgba(7,6,16,0.92));
  padding: 10px 14px 12px; min-height: 96px;
  font-family: var(--mono); font-size: 11.5px; line-height: 1.75;
  color: var(--light-blurple);
  overflow: hidden;
}
.lw-log .lw-log-head {
  display: flex; align-items: center; gap: 8px; margin-bottom: 6px;
  font-size: 9.5px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--greyple-dim);
}
.lw-log .lw-log-head .live-dot { width: 6px; height: 6px; background: var(--verdance); }
.lw-log .ln { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lw-log .t   { color: var(--greyple-dim); }        /* timestamp */
.lw-log .hl  { color: var(--starlight); }          /* actor / count */
.lw-log .amber { color: var(--lantern); }          /* place / target */
.lw-log .verd  { color: var(--verdance); }         /* a "good" event (rez, recover) */
.lw-log .cursor {
  display: inline-block; width: 7px; height: 13px; background: var(--blurple-lit);
  vertical-align: -2px; margin-left: 1px; animation: blink 1s steps(2) infinite;
}

/* the section wants a touch more headroom than a default band on wide screens */
.band.livewins-band { padding-bottom: 92px; }

@media (prefers-reduced-motion: reduce) {
  .lw-log .cursor, .lw-badge .live-dot, .lw-log-head .live-dot { animation: none !important; }
}

/* ---------- Footer ---------- */
footer {
  /* base text was --greyple-dim (3.96:1 = AA fail, axe P1); --greyple clears 6.7:1 on the void. */
  padding: 40px 0 60px; border-top: 1px solid var(--line);
  color: var(--greyple); font-size: 13.5px;
}
.foot-credit {
  max-width: var(--wrap); margin: 22px auto 0; padding: 0 28px;
  font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.04em;
  /* was --greyple-dim (#6A6E90 = 3.97:1 on the void — fails WCAG AA); --greyple (#9096B8) is 6.76:1,
     comfortably readable. The credit line names the real-data authenticity, so it should be legible. */
  color: var(--greyple);
}
.foot-credit .spark {
  display: inline-block; width: 5px; height: 5px; border-radius: 50%;
  background: var(--lantern); box-shadow: 0 0 8px 1px rgba(240,182,122,0.5);
  margin-right: 8px; vertical-align: middle;
}
.foot-in { display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; align-items: center; }
.foot-mark { font-family: var(--serif); font-size: 20px; color: var(--greyple); }
.foot-mark i { font-style: italic; color: var(--blurple-lit); }
footer .mono { font-family: var(--mono); font-size: 12px; letter-spacing: 0.06em; }
footer .foot-links { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* Footer links were 22px tall = a cramped tap target. Pad them to a full 44px touch height (WCAG 2.5.5)
   without changing the look — negative horizontal margin keeps the left edge aligned + the visual gap tight. */
footer .foot-links a {
  /* ⚠️ --body, NOT --greyple (Chris 2026-07-27: "the footer navigation links are
     a little different colour, maybe darker than the homepage — maybe I'm wrong,
     but worth a check"). He was right. Measured: homepage rgb(201,204,234), auth
     pages rgb(144,150,184).
     Same SiteFooter component both places — the difference was THIS rule. It is
     older and more specific (`footer .foot-links a`) than the `.foot-links a`
     rule further down that declares --body, so it won wherever the page did not
     inline its own footer CSS. The homepage does; the account pages do not, so
     only they went dark.
     The 44px tap target below is the reason this block exists and is kept; only
     the colour was wrong. */
  color: var(--body); text-decoration: none;
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 8px; margin: 0 -8px; border-radius: 8px;
}
footer .foot-links a:hover { color: var(--light-blurple); background: rgba(136,148,247,0.06); }

/* ---------- Inner-page hero (about / devlog share this calmer header) ---------- */
.page-hero {
  position: relative; overflow: hidden;
  padding: 96px 0 70px;
  border-bottom: 1px solid var(--line);
  background:
    radial-gradient(120% 140% at 50% -10%, rgba(88,101,242,0.14), transparent 55%),
    linear-gradient(180deg, var(--field), var(--void));
}
.page-hero .eyebrow {
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.32em;
  text-transform: uppercase; color: var(--blurple-lit); margin: 0 0 20px;
}
.page-hero .eyebrow .spark {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--lantern); box-shadow: 0 0 10px 1px rgba(240,182,122,0.7);
  margin-right: 10px; vertical-align: middle;
}
.page-hero h1 {
  font-family: var(--serif); font-weight: 500;
  font-size: clamp(38px, 7vw, 68px); line-height: 1.02; letter-spacing: -0.01em;
  margin: 0; color: var(--starlight); max-width: 16ch;
}
.page-hero .sub {
  font-family: var(--serif); font-style: italic;
  font-size: clamp(18px, 2.6vw, 24px); color: var(--light-blurple);
  margin: 20px 0 0; max-width: 46ch;
}

/* ---------- Prose (about page long-form) ---------- */
.prose { max-width: 66ch; }
.prose h2 { font-size: clamp(26px, 4vw, 36px); margin: 56px 0 18px; }
.prose h2:first-child { margin-top: 0; }
.prose h3 { font-size: 22px; margin: 34px 0 10px; }
.prose p { margin: 0 0 18px; font-size: 17.5px; }
.prose ul { margin: 0 0 20px; padding-left: 22px; }
.prose li { margin: 0 0 10px; color: var(--body); }
.prose li::marker { color: var(--blurple-lit); }
.prose .lead { font-size: 20px; color: var(--light-blurple); }

/* A small labelled "how it works" step list */
.steps { list-style: none; margin: 30px 0 8px; padding: 0; counter-reset: step; }
.steps li {
  position: relative; padding: 0 0 22px 52px; margin: 0;
  border-left: 1px solid var(--line); margin-left: 15px;
}
.steps li:last-child { border-left-color: transparent; padding-bottom: 0; }
.steps li::before {
  counter-increment: step; content: counter(step);
  position: absolute; left: -15px; top: -2px;
  width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: 13px; color: var(--light-blurple);
  background: var(--surface); border: 1px solid var(--line);
}
.steps li h3 { margin: 0 0 6px; font-family: var(--sans); font-weight: 600; font-size: 18px; color: var(--starlight); }
.steps li p { margin: 0; font-size: 15.5px; color: var(--greyple); }

/* ---------- "In development" status pill ---------- */
.status-pill {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--light-blurple);
  padding: 6px 14px 6px 12px; border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(88,101,242,0.08);
}
.status-pill .beacon {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--lantern); box-shadow: 0 0 0 0 rgba(240,182,122,0.6);
  animation: beacon 2.6s ease-out infinite;
}
@keyframes beacon {
  0%   { box-shadow: 0 0 0 0 rgba(240,182,122,0.55); }
  70%  { box-shadow: 0 0 0 7px rgba(240,182,122,0); }
  100% { box-shadow: 0 0 0 0 rgba(240,182,122,0); }
}
.nav .status-pill { padding: 4px 11px 4px 9px; font-size: 10.5px; letter-spacing: 0.14em; }

/* ---------- Small utilities ---------- */
.eyebrow-note { font-family: var(--mono); font-size: 12px; letter-spacing: 0.06em; color: var(--greyple-dim); }
.hr { height: 1px; background: var(--line); border: 0; margin: 0; }

/* ---------- Motion ---------- */
@keyframes rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
@keyframes blink { 0%,50% { opacity: 1; } 51%,100% { opacity: 0; } }
@keyframes live {
  0%   { box-shadow: 0 0 0 0 rgba(88,101,242,0.55); }
  70%  { box-shadow: 0 0 0 8px rgba(88,101,242,0); }
  100% { box-shadow: 0 0 0 0 rgba(88,101,242,0); }
}

/* ---------- Responsive ---------- */
@media (max-width: 760px) {
  body { font-size: 16px; }
  .cards { grid-template-columns: 1fr; }
  .logrow { grid-template-columns: 1fr; gap: 6px; }
  section.band { padding: 72px 0; }
  .nav-links a:not(.discord) { padding: 8px 9px; }
  .nav-mark { font-size: 19px; }
  .cta-band { padding: 88px 0 96px; }
}
/* 480px, not 420px. MEASURED, not guessed: the wordmark + 3 text links + the
   Discord CTA need 473px to sit on one row (nav-mark 128 + nav-links scrollWidth
   + the 18px .nav-in gap + 2×18px padding). Below that they overflow — which is
   exactly what 430px (iPhone Pro Max, a supported width) was doing: the bar ran
   to 445px and `body{overflow-x:hidden}` silently ate the difference. Collapsing
   at the width the content actually stops fitting fixes the whole 421–479 band,
   not just the one tested viewport. */
@media (max-width: 480px) {
  .nav-in { padding: 11px 18px; }
  .wrap { padding: 0 20px; }
  /* On a phone the wordmark + 3 text links + the CTA can't fit one row (they overflowed past the
     viewport and squished the buttons). Collapse to the two things that matter: the brand mark and the
     single primary action (Join the Discord — the page's whole job). The secondary links (See the
     world / About / Devlog) are reachable by scrolling + the footer, so hiding them here costs nothing
     and keeps the bar clean + tappable. No hamburger needed for one CTA. */
  .nav-links { gap: 2px; }
  .nav-links a:not(.discord) { display: none; }
  .nav-links a.discord { padding: 8px 14px; font-size: 13.5px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; }
  html { scroll-behavior: auto; }
  [style*="animation"] { opacity: 1 !important; }
  .map-bg { transform: scale(1.02) !important; }   /* hold still, no drift */
}

/* ==========================================================================
   ⭐ THE SHARED CHROME — nav + footer, lifted here 2026-07-27.
   --------------------------------------------------------------------------
   Chris: "universal nav and footer at the least — but we need a site that shares
   bodies with the subsystems like account manager, so it's not styled completely
   different."
   These rules used to live INSIDE index.astro's <style>, which meant the account
   pages could not see them and carried their own older copy instead: the pre-
   gradient `<span class="spark">` wordmark, "Devlog" after the rename, a dead
   "#" launcher CTA, and an `<i>L2</i>` footer mark. Hand-copied chrome drifts.
   Now `src/components/SiteNav.astro` + `SiteFooter.astro` render the markup and
   this file styles it, so every page and every subsystem share one chrome.
   ⚠️ index.astro still carries its own copy of these rules for its hero-specific
   overrides; they are identical here. If you change one, change both — or better,
   delete the page-local copy.
   ========================================================================== */
/* ---------------------------------------------------------------- NAV ---- */
.nav{position:sticky; top:0; z-index:40; backdrop-filter:blur(12px);
  background:rgba(11,10,24,.82); border-bottom:1px solid var(--line-soft);}
/* ⚠️ THE BAR TRACKS `--wrap` EXCEPT ON THE HOMEPAGE. The 1408/24 pairing exists
   so the bar meets the HOMEPAGE's hero card, which bleeds past --wrap; every other
   page has no such card, and inheriting that tuning put their wordmark 26px off
   (measured 2026-07-27: home 40, features 66). `.nav--hero` opts the homepage back
   in; everything else aligns to the wrap like its own content does. */
.nav-in{max-width:var(--wrap); margin:0 auto; padding:0 26px;
  display:flex; align-items:center; gap:16px; min-height:60px;}
/* Cormorant is a DISPLAY face — beautiful at the hero's 100px+, genuinely thin
   and hard to read at nav size. Chris: "the text is tiny thin hard to read."
   It was 21px at the font's DEFAULT weight (400). Fixed by going up in size AND
   weight rather than swapping the face — the header has to stay Cormorant to
   match the hero wordmark. 600 is a weight we already bundle, so no extra
   download. */
.nav-mark{font-family:var(--serif); font-size:23px; font-weight:600;
  letter-spacing:.005em; color:var(--starlight);
  text-decoration:none; display:inline-flex; align-items:center; min-height:44px;}
/* THE WORDMARK IS ONE MARK. The header used a blurple italic L2 while the hero
   used the launcher's amber-gold gradient — two different lockups for the same
   brand on the same page. The hero is the reference (it matches the launcher's
   --wm-gold/--wm-gold-deep), so the header follows it: same amber gradient,
   same upright L2. Chris, 2026-07-21: "we need to adopt our header styling as
   the golden rule."
   ⚠️ If the hero wordmark changes, change this with it. */
.nav-mark .l2{font-size:.82em; margin-right:2px;
  background:linear-gradient(180deg,#F6C67A,#E8A24C);
  -webkit-background-clip:text; background-clip:text; color:transparent;}
/* `dream` carries the same blurple gradient as the hero, not a flat colour —
   all three parts of the mark match the hero exactly. */
/* match the HERO wordmark's 'dream' blurple (Chris likes it): the hero uses the
   tighter #8f93f5->#8188ff range, not the nav's original lighter near-white gradient. */
.nav-mark .dream{background:linear-gradient(180deg,#a9adfa,#8f93f5);
  -webkit-background-clip:text; background-clip:text; color:transparent;}
.nav-links{display:flex; align-items:center; gap:4px; flex-wrap:nowrap; white-space:nowrap;}
/* The vertical pipe between the brand and the menu (Chris: "our logo, and then
   maybe a vertical pipe, then list out the menu items"). */
.nav-pipe{width:1px; height:22px; background:rgba(136,148,247,.28); margin:0 14px 0 16px; flex:none;}
/* RIGHT GROUP — reserved for the two buttons (desktop): Account + the launcher. */
.nav-right{margin-left:auto; display:flex; align-items:center; gap:12px;}
.nav-right .nav-cta{color:#fff; background:var(--blurple);
  border:1px solid transparent; font-weight:600; padding:12px 18px;
  /* tighter glow — the old 30px/-14px halo bled up past the sticky nav and read as
     a glitch in Chrome (Chris QA). Smaller, downward-only. */
  box-shadow:0 6px 18px -10px rgba(88,101,242,.7);}
.nav-right .nav-cta .dl-os{color:#fff; background:rgba(10,9,23,.34);}
.nav-right .nav-cta:hover{background:#4752C4; color:#fff; border-color:transparent;}
.nav-right .nav-cta:hover .dl-os{background:rgba(10,9,23,.42); color:#fff;}
/* THE ACCOUNT CHIP — outlined, secondary to the launcher CTA; STAYS VISIBLE on
   mobile (Chris: the launcher collapses into the menu, Account does not). */
/* WEIGHT (Chris 2026-07-23): "the 600 weight just looks strange ... theres no
   inbetween its either super big and bold or if i go to like 500 it snaps to
   regular text ... lets go with non bold for it." That is the font, not taste:
   this button resolves to Arial, which ships only two weights — measured,
   400 and 500 render IDENTICALLY (50.59px) and 600 and 700 render IDENTICALLY
   (56px). So 600 already WAS bold and there is no middle step to reach.
   Going regular. */
.nav-account{display:inline-flex; align-items:center; gap:7px;
  color:var(--body,#e8e9f2); text-decoration:none; font-size:14px; font-weight:400;
  padding:10px 15px; border-radius:11px; min-height:42px;
  border:1px solid transparent; background:transparent;
  transition:background .16s ease, border-color .16s ease, color .16s ease;}
/* DESKTOP chip styling (mobile stays bare so no load-flash):
   - the ACCOUNT button gets the gold/ember FILL (Chris: like the old How-to-connect),
     dark ink, no sheen (clean filled, not the flashy hero button).
   - the DOWNLOAD chip stays the quiet blurple OUTLINE. */
@media (min-width:961px){
  .nav-account.nav-dl-chip{border-color:rgba(136,148,247,.38); background:rgba(136,148,247,.06); color:var(--body,#e8e9f2);}
  /* ACCOUNT = the gold ember fill (like the old How-to-connect): dark ink #2a1c07. */
  .nav-account:not(.nav-dl-chip){border:0; color:#2a1c07; font-weight:400;
    background:linear-gradient(180deg,var(--ember),var(--lantern));
    box-shadow:0 0 0 1px rgba(255,246,230,.22) inset, 0 8px 22px -12px rgba(246,198,122,.55);}
}
.nav-account .acct-i{width:15px; height:15px; flex:none;}
/* ACCOUNT FLYOUT (Chris): a dropdown with Log In / Sign Up. */
.acct-flyout{position:relative;}
.acct-flyout .nav-account{cursor:pointer;}
.acct-caret{width:13px; height:13px; flex:none; margin-left:-1px; transition:transform .18s ease;}
#acct-btn[aria-expanded="true"] .acct-caret{transform:rotate(180deg);}
.acct-menu{position:absolute; top:calc(100% + 8px); right:0; z-index:60;
  min-width:170px; padding:6px; border-radius:12px;
  background:rgba(18,16,31,.97); border:1px solid rgba(217,169,106,.24);
  box-shadow:0 18px 40px -16px rgba(0,0,0,.8);
  display:none; flex-direction:column; gap:2px;}
#acct-btn[aria-expanded="true"] + .acct-menu{display:flex;}
/* Chris: open on HOVER (real pointers only). A padding bridge on the menu's top
   keeps it open while the cursor travels from the button down into it. */
@media (hover:hover){
  .acct-flyout:hover .acct-menu, .acct-flyout:focus-within .acct-menu{display:flex;}
  .acct-flyout:hover #acct-btn .acct-caret{transform:rotate(180deg);}
  .acct-menu{margin-top:0;}
  .acct-flyout::after{content:""; position:absolute; top:100%; right:0; width:100%; height:10px;}
}
.acct-menu a{display:flex; align-items:center; gap:10px; text-decoration:none;
  padding:10px 12px; border-radius:8px; font-size:14px; font-weight:600;
  color:var(--body,#e8e9f2); white-space:nowrap;}
.acct-menu a svg{width:16px; height:16px; flex:none; color:var(--lantern);}
@media (hover:hover){ .acct-menu a:hover{background:rgba(217,169,106,.12); color:#fff;} }
/* HOVER only on real pointers. The gold ACCOUNT keeps its dark ink and just
   brightens the gold (matching connect-btn); the blurple DOWNLOAD chip keeps its
   blurple hover. (The old shared blurple hover was turning the gold button blue.) */
@media (hover:hover){
  .nav-account.nav-dl-chip:hover{color:var(--light-blurple); background:rgba(88,101,242,.22);
    border-color:rgba(136,148,247,.62);}
  .nav-account:not(.nav-dl-chip):hover{color:#2a1c07;
    background:linear-gradient(180deg,#FFD79A,var(--ember));}
}
/* mobile-drawer-only copies hidden on desktop — !important because the
   .nav-links a display rules otherwise win the cascade. */
a.nav-flyoutonly{display:none !important;}
/* keep the right CTA on one line */
.nav-right .nav-cta{white-space:nowrap;}
/* ADA (Chris 2026-07-23): "nav buttons like how to connect cant be gray they
   need to prolly be white for ada compliance." Measured: --greyple #9096b8 on the
   nav's dark ink is 6.76:1 — it does pass WCAG AA (4.5:1), but it sits under AAA
   (7:1) and reads washed out. --body #c9ccea measures 12.43:1 — AAA with room to
   spare. Used the palette's existing off-white rather than pure #fff (19.6:1),
   which is harsher on this dark theme for no real accessibility gain. */
.nav-links a{color:var(--body); text-decoration:none; font-size:14.5px; font-weight:500;
  padding:11px 13px; border-radius:9px; min-height:44px; display:inline-flex; align-items:center;}
.nav-links a:hover{color:var(--light-blurple); background:rgba(136,148,247,.09);}
.nav-links a.nav-cta{color:var(--light-blurple); background:rgba(88,101,242,.18);
  border:1px solid rgba(136,148,247,.38);}
/* ⚠️ HOVER GOES DARKER, NOT LIGHTER. --blurple-hi (#6B78FF) fails WCAG AA with
   white text at 3.65:1; --blurple itself passes at 4.61:1 and #4752C4 (Discord's
   own hover) clears comfortably at 6.42:1. Lightening a blurple button on hover
   therefore BREAKS contrast at the exact moment the user is interacting with it.
   tools/contrast.mjs asserts this in every state. */
.nav-links a.nav-cta:hover{background:#4752C4; color:#fff; border-color:transparent;}

/* The download CTA. `gap:8px` and the icon size are what make it read as an
   action rather than another nav word. */
.nav-dl{gap:8px;}
.nav-dl .dl-i{width:15px; height:15px; flex:none;}
/* The platform, stated in the control itself rather than in a tooltip or a
   footnote — a Mac visitor learns what this is before clicking, not after.
   It is a quieter weight so the button still reads as one label. */
.nav-dl .dl-os{font-family:var(--mono); font-size:9px; letter-spacing:.12em;
  text-transform:uppercase; font-weight:700;
  color:var(--light-blurple); background:rgba(136,148,247,.16);
  border-radius:20px; padding:2px 6px; line-height:1.5;}
.nav-dl:hover .dl-os{background:rgba(255,255,255,.2); color:#fff;}

/* ---- Mobile nav: the hamburger + drawer -----------------------------------
   MEASURED, not guessed: the wordmark + 4 links need 451px of bar to sit on one
   row (128px mark + 273px of links + 12px of inter-link gaps + the .nav-in gap
   and padding). Below ~560px that no longer fits, and "Read the wiki" wrapped to
   a second row and collided with the wordmark — Chris's own bug report from his
   iPhone Pro.

   ⚠️ RE-MEASURED 2026-07-21 when the nav CTA changed from "Read the wiki" to
   "Get the launcher · WINDOWS" (a wider control: it carries an icon and a
   platform pill). The link row grew, so the breakpoint was re-derived rather
   than assumed still valid — see the measurement in the report. The wiki link
   left the nav entirely (it now lives under the hero cards as a quiet aside),
   which returns most of the width the download button costs.

   Full collapse, not partial. Keeping one or two links visible would buy ~60px
   and re-break the moment a label changes; the destinations are equal-weight
   (Wiki / About / Devlog), and the download CTA must never be the ONE thing
   left on a cramped bar — a button that survives alone at 400px reads as the
   only thing the site wants from you. The button is hidden above the breakpoint
   and the drawer is display:none, so desktop is unchanged. */
.nav-toggle{display:none; margin-left:auto; align-items:center; justify-content:center;
  width:44px; height:44px; flex:none; padding:0; cursor:pointer;
  /* styled ON LOAD to match the Account chip beside it (Chris: the button looked
     plain until clicked — the old faint --line-soft border was barely visible; the
     nice look was only the hover state, which then stuck on touch). */
  background:transparent; border:0; border-radius:10px;
  color:var(--starlight);}
@media (hover:hover){ .nav-toggle:hover{background:rgba(136,148,247,.12); border-color:rgba(136,148,247,.5);} }
.nav-toggle .bars{display:block; width:18px; height:14px; position:relative;}
.nav-toggle .bars::before, .nav-toggle .bars::after, .nav-toggle .bars span{
  content:""; position:absolute; left:0; width:100%; height:2px; border-radius:2px;
  background:currentColor; transition:transform 180ms ease, opacity 140ms ease;}
.nav-toggle .bars::before{top:0;}
.nav-toggle .bars span{top:6px;}
.nav-toggle .bars::after{bottom:0;}
/* Open state morphs the three bars into an X — the affordance that the same
   control closes the menu. */
.nav-toggle[aria-expanded="true"] .bars::before{transform:translateY(6px) rotate(45deg);}
.nav-toggle[aria-expanded="true"] .bars span{opacity:0;}
.nav-toggle[aria-expanded="true"] .bars::after{transform:translateY(-6px) rotate(-45deg);}

@media (max-width:960px){
  .nav-toggle{display:inline-flex;}
  .nav-pipe{display:none;}
  /* Chris 2026-07-23: on mobile the DOWNLOAD button collapses into the drawer (its
     .nav-flyoutonly copy shows there); the ACCOUNT icon STAYS visible in the bar.
     So .nav-right keeps only the account chip. */
  .nav-right .nav-cta, .nav-right .nav-dl-chip{display:none;}
  /* Chris 2026-07-23: Account + hamburger sit TOGETHER on the far right. nav-right
     pushes to the right edge; the toggle drops its own margin-left:auto so it sits
     right BESIDE the account icon (was stranded mid-bar with a gap to the toggle). */
  /* Chris 2026-07-23: hamburger FIRST (left), Account SECOND (far right) — swapped
     via flex order so the DOM/a11y order is untouched. The toggle carries margin-
     left:auto to push the PAIR to the right; a 10px gap sits between them. */
  .nav-toggle{order:1; margin-left:auto; margin-right:-6px;}   /* tighten the pair (bare icons, 44px hit-areas overlap-pad) */
  .nav-right{order:2; margin-left:0; margin-right:-4px;}
  /* Chris 2026-07-23: on mobile the ACCOUNT is an ICON, not a big pill — a 44x44
     square matching the hamburger next to it. Hide the label, keep the person
     glyph centered. */
  .nav-account{width:44px; height:44px; padding:0; gap:0; justify-content:center;
    border-radius:10px; font-size:0;}
  .nav-account .acct-i{width:20px; height:20px;}
  /* Chris 2026-07-23: on mobile, menu + account are bare ICONS — no box border/fill. */
  .nav-toggle, .nav-account{border:0; background:transparent;}
  .nav-toggle:hover, .nav-account:hover{background:transparent;}
  a.nav-flyoutonly{display:flex !important;}
  .nav-links{
    display:none;
    position:absolute; left:0; right:0; top:100%;
    flex-direction:column; align-items:stretch; gap:2px;
    margin:0; padding:10px 14px 16px;
    /* OPAQUE, and no backdrop-filter. The sticky BAR is deliberately translucent
       (content scrolls under a 12px blur), but a drawer is a surface you read
       links off — at .97 alpha over the hero the wordmark and the stream text
       bled straight through it and the labels became unreadable. Caught by
       looking at the screenshot; every numeric assertion passed. Use the solid
       token so the panel is the page's own ground colour, not a wash of it. */
    background:var(--void); border-bottom:1px solid var(--line-soft);
    box-shadow:0 18px 40px -22px rgba(0,0,0,.9);
  }
  /* ⚠️ A DRAWER TALLER THAN THE SCREEN MUST SCROLL ITSELF (2026-07-26). The
     wiki's panel hit this for real — 12 links made it 801px on an 852px phone,
     and with no max-height the touch fell THROUGH to the page behind it
     ("it scrolls the actual wiki rather than the menu"). This drawer has only
     four links (218px, measured) so it does not overflow today, but the failure
     is latent the moment a link is added. dvh not vh: on mobile Safari `vh` is
     the tallest viewport and would hide the last item under the URL bar. */
  .nav-links[data-open="true"]{display:flex; max-height:calc(100dvh - 96px);
    overflow-y:auto; overscroll-behavior:contain;}
  .nav-links a{width:100%; font-size:15.5px; padding:12px 14px; border-radius:10px;}
  .nav-links a.nav-cta{margin-top:6px; justify-content:center;}
}
@media (prefers-reduced-motion:reduce){
  .nav-toggle .bars::before, .nav-toggle .bars::after, .nav-toggle .bars span{transition:none;}
}

/* --------------------------------------------------------------- FOOT ---- */
/* ⭐ REBUILT 2026-07-26 to the labelled-row shape (see the markup note). The old
   footer was a space-between row of wordmark + links that wrapped unpredictably;
   this is a stacked sequence of LABELLED GROUPS, which is what made the
   reference read as clean. Type is a step smaller throughout ("we can make our
   text smaller") and the links sit on one line at every width. */
.foot{padding:52px 0 46px; color:var(--greyple); font-size:13px;
  border-top:1px solid var(--line-soft);}

.foot-mark{display:block; font-family:var(--serif); font-size:19px; font-weight:600;
  letter-spacing:.005em; color:var(--starlight); margin-bottom:28px;}
.foot-mark .l2{font-size:.82em; margin-right:2px;
  background:linear-gradient(180deg,#F6C67A,#E8A24C);
  -webkit-background-clip:text; background-clip:text; color:transparent;}
.foot-mark .dream{background:linear-gradient(180deg,#a9adfa,#8f93f5);
  -webkit-background-clip:text; background-clip:text; color:transparent;}

.foot-group{margin:0 0 20px;}
/* the dim label is what turns a bare list into a labelled GROUP — the single
   detail that makes the reference footer read as organised rather than loose. */
/* ⚠️ NO `opacity` HERE. Dimming --greyple to .72 measured 3.98:1 against the
   footer ground - under AA - because opacity composites the TEXT toward the
   background and the contrast rule counts the EFFECTIVE colour, not the declared
   one. --body at full opacity gives the same quiet-label read at 12.43:1. */
.foot-label{margin:0 0 6px; font-size:12.5px; color:var(--greyple);}

/* ⚠️ ONE LINE, ALWAYS. Chris reported "server features breaks to two lines".
   `flex` + `wrap` lets the row reflow on a narrow phone instead of clipping,
   but `white-space:nowrap` on each link guarantees no single LABEL ever splits
   mid-phrase — which was the actual complaint. */
.foot-links{display:flex; flex-wrap:wrap; gap:0 14px;}
/* ⚠️ 44px TAP TARGET, not 32. The check gate caught "Wiki" at 27x35 - a short
   label with no horizontal padding is a tiny hit area on a phone. The row gap
   carries the visual spacing, so the padding here is purely the touch target and
   the negative margin keeps the first link flush with the label above it. */
.foot-links a{color:var(--body); text-decoration:none; font-size:14px;
  white-space:nowrap; padding:11px 10px; min-height:44px; display:inline-flex;
  align-items:center; margin-left:-10px;}
.foot-links a:hover{color:var(--light-blurple);}

.foot-social{display:flex; gap:14px; align-items:center;}
/* 44px tap target via padding, but the MARK itself stays 18px — the reference's
   icons are small and quiet, not chunky buttons. */
/* the icon stays small and quiet like the reference's, but the HIT AREA is a
   full 44px - the gate flagged the 34px box. */
/* ⚠️ THE DISCORD MARK RESTS AT FULL BRIGHTNESS (Chris 2026-07-27: "on mobile
   it's greyed out because you would hover on it generally … there shouldn't be a
   hover state on mobile anywhere anyway, and honestly not at all for the Discord
   logo — it should just be whatever the Discord logo's colour is supposed to be,
   probably white").
   He is right twice over. It rested at --greyple (rgb 144,150,184) and only
   reached full brightness on :hover — a state a touch device can only enter by
   HOLDING a finger down, so on a phone the logo was permanently dimmed and
   flashed brighter when pressed. A brand mark is not a link that needs
   discovering; it is an identity, and it should look like itself at rest. */
/* ⚠️ THE HOVER BOX IS SMALLER THAN THE TAP TARGET (Chris 2026-07-27: "on hover
   border touches upper text in footer, and misaligned — maybe smaller box").
   Both symptoms came from one rule: a 44px box with margin:-8px 0 -8px -13px.
   The -8px top pulled the hover background up into "Community:" so they touched,
   and the -13px left shifted the icon out of line with the labels above it.
   The 44px is a WCAG 2.5.5 tap target and is KEPT — the fix shrinks what you SEE,
   not what you can hit: the element stays 44px, and the visible rounded panel is
   drawn by ::before at 32px, centred, so it can never reach the text above.
   ⚠️ Do not "simplify" this back to a padded box; the two sizes are the point. */
.foot-social a{position:relative; color:var(--starlight); display:inline-flex;
  align-items:center; justify-content:center; width:44px; height:44px;
  margin:-6px 0 -6px -6px;}
/* ⚠️ CENTRED ON THE ICON, NOT ON THE BOX. Measured: the SVG sits at x=26, which
   is exactly where .foot-label and .foot-mark start — the icon was ALREADY
   aligned. The 44px tap target simply extends 13px further left, which is
   invisible until :hover paints it, and that is what read as "misaligned".
   Anchoring the panel to the icon means the hover highlight lines up with the
   text above it while the tap target keeps its full 44px. */
.foot-social a::before{content:""; position:absolute; top:50%;
  left:13px; width:32px; height:32px; transform:translate(-4px,-50%);
  border-radius:9px; background:transparent; transition:background .16s ease;}
.foot-social a svg{position:relative; z-index:1;}
/* ⚠️ HOVER IS GATED ON A REAL POINTER. Without this query a touch device applies
   :hover on tap and STICKS there until you touch elsewhere — which is exactly the
   "it turns white when I push my finger on it" Chris described. `hover: hover`
   matches a mouse/trackpad and never a finger, so the hover affordance survives
   on desktop and simply does not exist on a phone.
   ⚠️ Use this pattern for any future hover state. A hover rule without it is a
   bug on the majority of visits. */
@media (hover: hover) {
  .foot-social a:hover{background:rgba(136,148,247,.08);}
}
.foot-social svg{width:18px; height:14px;}

.foot-note{margin:26px 0 0; font-family:var(--mono); font-size:11px;
  letter-spacing:.04em; line-height:1.7; color:var(--greyple); opacity:.8;}

@media (max-width:760px){
  .foot{padding:40px 0 38px;}
  .foot-mark{font-size:18px; margin-bottom:24px;}
  .foot-links{gap:0 12px;}
  .foot-links a{font-size:13.5px;}
  .foot-note{font-size:10.5px;}
}



/* the homepage's bar, which must meet its wider hero card */
.nav--hero .nav-in{max-width:1408px; padding:0 24px;}
