/* Fermi's Ascension — marketing site
   Dark by default (it's a space game), with a light-mode override for anyone
   whose system asks for one. No framework, no client-side JS. */

@font-face {
  font-family: "Jura";
  src: url("/assets/fonts/Jura.ttf") format("truetype");
  font-weight: 300 700;
  font-display: swap;
}

:root {
  --bg: #07090f;
  --bg-raised: #0d111b;
  --bg-panel: #111725;
  --border: #1e2637;
  --text: #dfe5f0;
  --text-dim: #97a2b8;
  --accent: #6ea8ff;
  --accent-warm: #ffb765;
  --focus: #9ec5ff;
  --max: 68rem;
  /* The page gutter. Anything that should line up with the header brand and the
     footer centres a column of --max and insets its content by --gutter. Both
     live here because the journey copy, the newsletter block and the footer
     each hardcoded their own, and had drifted 4rem apart by the time anyone
     looked at them side by side. */
  --gutter: 1.25rem;
  --radius: 10px;

  /* --- in-game UI kit tokens (civ2/src/UIKit.js) -------------------------
     The angular clipped corner is the game's signature shape — notched
     top-left and bottom-right. Ported verbatim so the site and the in-game
     panels read as one product. Keep these in sync with UIKit.js. */
  --fm-accent: #8b96e8;
  --fm-accent-deep: rgba(60,54,140,.55);
  --fm-accent-deep-hover: rgba(78,70,175,.7);
  --fm-surface: rgba(8,10,20,.75);
  --fm-surface-hover: rgba(20,24,44,.85);
  --fm-surface-deep: rgba(6,8,16,.7);
  --fm-border: rgba(120,132,180,.28);
  --fm-border-strong: rgba(139,150,232,.7);
  --fm-clip: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
  --fm-clip-sm: polygon(6px 0, 100% 0, 100% calc(100% - 6px), calc(100% - 6px) 100%, 0 100%, 0 6px);

  --font-display: "Jura", ui-sans-serif, system-ui, "Segoe UI", sans-serif;
  --font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #f7f8fb;
    --bg-raised: #ffffff;
    --bg-panel: #ffffff;
    --border: #dde2ec;
    --text: #161c28;
    --text-dim: #5a6479;
    --accent: #1d5fd0;
    --accent-warm: #a35c00;
    --focus: #1d5fd0;

    /* The kit is dark-native. Light mode keeps the SHAPE language (clipped
       corners, uppercase letterspaced labels) but remaps the surfaces, since
       rgba(8,10,20,.75) panels on a white page would be unreadable. */
    --fm-accent: #4a53c9;
    --fm-accent-deep: #4a53c9;
    --fm-accent-deep-hover: #3a42ab;
    --fm-surface: #ffffff;
    --fm-surface-hover: #f2f4fa;
    --fm-surface-deep: #ffffff;
    --fm-border: rgba(70,84,140,.28);
    --fm-border-strong: rgba(74,83,201,.75);
  }
}

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

/* PAGINATED SCROLLING.
   The hero-to-game crossfade is driven by scroll offset, so any position you can
   rest at is a state you can see — and resting mid-scroll meant sitting in a
   half-dissolved frame with both the nebula and the game visible at once.
   Snapping removes the intermediate rest positions: the fade still plays as you
   move, but you always come to rest on a clean one.

   `mandatory`, not `proximity`: proximity leaves the in-between states reachable,
   which is the whole problem. This was previously removed because it fought the
   old scrubbed camera — that version is gone, and the journey now triggers the
   game's own transitions, so snapping is compatible again. */
html {
  -webkit-text-size-adjust: 100%;
  /* NO `scroll-behavior: smooth` here — its animation competes with snapping and
     leaves the scroll parked between stops (measured: a scroll to 45% settled at
     y=129, mid-fade). Anchor links still scroll smoothly via journey.js. */
  scroll-snap-type: y mandatory;
}
@media (prefers-reduced-motion: reduce) {
  /* No fixed stages and no fading there, so there is nothing to snap away from. */
  html { scroll-behavior: auto; scroll-snap-type: none; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.65;
  /* Faint starfield. Two layers of radial dots — cheap, no image request. */
  background-image:
    radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,.20), transparent),
    radial-gradient(1px 1px at 75% 15%, rgba(255,255,255,.14), transparent),
    radial-gradient(1px 1px at 45% 70%, rgba(255,255,255,.12), transparent),
    radial-gradient(1px 1px at 88% 60%, rgba(255,255,255,.16), transparent),
    radial-gradient(1px 1px at 10% 85%, rgba(255,255,255,.10), transparent),
    radial-gradient(900px 500px at 70% -10%, rgba(110,168,255,.10), transparent);
  background-attachment: fixed;
}

@media (prefers-color-scheme: light) {
  body { background-image: radial-gradient(900px 500px at 70% -10%, rgba(29,95,208,.06), transparent); }
}

.wrap { width: 100%; max-width: var(--max); margin: 0 auto; padding: 0 var(--gutter); }
/* Unused since the feature-panel grid was removed on 2026-07-31. Kept for a
   future block that genuinely needs the extra room — but note that anything
   using it will NOT line up with the header or the footer, which are .wrap. */
.wrap-wide { width: 100%; max-width: 76rem; margin: 0 auto; padding: 0 var(--gutter); }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--text); }

:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 3px; }

.skip {
  position: absolute; left: -9999px;
  background: var(--accent); color: #001028;
  padding: .6rem 1rem; border-radius: 0 0 var(--radius) 0; z-index: 100;
}
.skip:focus { left: 0; top: 0; }

/* --- header --------------------------------------------------------------- */

.site-header {
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 85%, transparent);
  backdrop-filter: blur(8px);
  position: sticky; top: 0; z-index: 20;
}
.header-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; min-height: 4rem; flex-wrap: wrap;
}
.brand { display: flex; align-items: center; gap: .7rem; text-decoration: none; color: var(--text); }
/* The game's own title lockup. Light-mode pages need it inverted — the PNG is
   white artwork with transparency, invisible on a white header. */
.brand-lockup {
  width: 132px; height: auto; display: block; flex: none;
  align-self: center;
}
@media (prefers-color-scheme: light) {
  .brand-lockup { filter: invert(1) brightness(.35); }
}
.brand-sub {
  font-family: var(--font-display);
  font-size: .7rem; letter-spacing: 4px; text-transform: uppercase;
  color: var(--text-dim); white-space: nowrap;
}

.site-header nav { display: flex; gap: 1.35rem; }
.site-header nav a { color: var(--text-dim); text-decoration: none; font-size: .95rem; }
.site-header nav a:hover, .site-header nav a.active { color: var(--text); }
.site-header nav a.active { text-decoration: underline; text-underline-offset: 6px; }

/* Home page: the header floats over the title screen instead of sitting on it.
   A solid bar — white in light mode — butted against the nebula looks like two
   unrelated pages stapled together.
   FIXED, not absolute: the title lockup docks into this header as you scroll, so
   the header has to stay put. (It used to scroll away, which would have carried
   the docking target off-screen.) */
.site-header.over-hero {
  position: fixed; top: 0; left: 0; right: 0;
  background: transparent; backdrop-filter: none; border-bottom: none; z-index: 30;
}

/* Reserves the docked lockup's footprint and gives journey.js a target to
   measure. Deliberately empty — the lockup itself is .brand-dock. */
/* Height MATCHES the docked wordmark (132 x 95/807 = 15.5px), not an arbitrary
   box. The dock aligns its top-left to this slot, so an over-tall slot pushed
   the subtitle below the wordmark instead of beside it. */
.brand-slot { display: block; width: 132px; height: 16px; }
/* Shown only once the lockup has docked. The travelling dock scales uniformly,
   which would render its own 13px tagline at about 2px — illegible. So that one
   fades out and this full-size one fades in beside the docked wordmark. */
.brand-sub--docked { opacity: 0; transition: opacity .18s linear; }
/* Shadows are not decoration here — the nebula has bright star clusters that
   land directly behind this text and destroy contrast without them. */
.site-header.over-hero .brand-lockup {
  filter: drop-shadow(0 1px 6px rgba(0,0,0,.95)) drop-shadow(0 0 14px rgba(139,150,232,.3));
}
.site-header.over-hero .brand-sub {
  color: rgba(198,210,238,.75); text-shadow: 0 1px 4px rgba(0,0,0,.9);
}
.site-header.over-hero nav a { color: rgba(210,220,245,.75); text-shadow: 0 1px 4px rgba(0,0,0,.8); }
.site-header.over-hero nav a:hover,
.site-header.over-hero nav a.active { color: #fff; }

/* --- hero ----------------------------------------------------------------- */

main { padding: 3.5rem 1.25rem 5rem; }
main.bare { padding: 0; max-width: none; }
.below-fold {
  padding: 4rem 1.25rem 2rem;
  /* LOAD-BEARING. This is the escape hatch out of the paginated region, and
     removing it (tried 2026-07-31) makes the footer permanently unreachable.

     journey.js disables snapping once scrollY passes `lastPanel + 0.5vh`, but
     snapping is mandatory on the document until it does. With no snap target
     below the last panel, the browser has nowhere to put you except back on
     that panel — so you never reach the threshold that would switch snapping
     off, and the page dead-ends there with the editor scene at full opacity.
     This target is what lets the browser move you past it. */
  scroll-snap-align: start;

  /* INVARIANT: at least one viewport of content must sit below the journey.
     journey.js fades the game out on the journey root's `rect.bottom`, and that
     only reaches 0 — fully faded — once the journey has been scrolled entirely
     past. With less than a viewport underneath it, the document simply ends
     first: the page bottoms out mid-fade and the newsletter and footer render on
     top of a half-visible editor scene.

     This used to hold by accident, because a grid of feature panels sat here.
     They were removed on 2026-07-31 and the overlap appeared immediately, so the
     height is now stated rather than inherited from whatever prose is present.
     The extra 4rem keeps the fade from finishing on the exact last pixel. */
  min-height: calc(100vh + 4rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* --- hero stage: the game's main-menu look -------------------------------- */
/* Ported from civ2/src/MainMenu.js MENU_CSS. Keep the colour values in sync —
   rgba(150,170,255) is the menu's glow accent, #020308 its background. */

/* The holder supplies the scroll distance the fixed hero no longer occupies. */
.hero-holder {
  position: relative; height: 100vh; height: 100dvh;
  /* `scroll-snap-stop: always` makes each flick advance exactly ONE stop, so a
     fast scroll cannot skip a panel (or land between two). That is what makes
     this read as pagination rather than snapping. */
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

.hero-stage {
  /* Fixed so it can fade in place instead of sliding away, and so the game can
     be revealed underneath it rather than after it. */
  position: fixed; inset: 0;
  z-index: 3;
  will-change: opacity;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 4rem 1.25rem 5rem;
  overflow: hidden;
  background: #020308;
  /* Fallback beneath the canvas — what no-WebGL and reduced-motion visitors get.
     The star layers must TILE: a `radial-gradient(1px 1px at 12% 22%, ...)`
     paints exactly one dot across the whole element, so percentage-positioned
     dots read as a blank gradient at any real viewport size. Each star layer
     therefore gets its own background-size and repeats.
     Last two layers are the nebula's warm core / cool outer band — not tiled. */
  background-image:
    radial-gradient(1.4px 1.4px at 36px 52px, rgba(255,255,255,.75), transparent),
    radial-gradient(1.1px 1.1px at 148px 22px, rgba(215,228,255,.60), transparent),
    radial-gradient(1px 1px   at 88px 168px, rgba(255,255,255,.50), transparent),
    radial-gradient(1.6px 1.6px at 210px 124px, rgba(255,236,214,.55), transparent),
    radial-gradient(1px 1px   at 24px 210px, rgba(255,255,255,.40), transparent),
    radial-gradient(ellipse 90% 42% at 50% 46%, rgba(255,204,140,.12), transparent 70%),
    radial-gradient(ellipse 120% 60% at 50% 46%, rgba(115,153,255,.12), transparent 72%);
  background-size:
    247px 247px, 311px 311px, 197px 197px, 419px 419px, 163px 163px, cover, cover;
  background-repeat:
    repeat, repeat, repeat, repeat, repeat, no-repeat, no-repeat;
  background-position: 0 0, 40px 90px, 120px 10px, 70px 200px, 190px 60px, center, center;
}

/* Light mode keeps the hero dark on purpose: it is the game's title screen,
   not a UI surface. The rest of the page still follows the system theme. */
.nebula {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; border: 0; z-index: 0;
  opacity: 0; transition: opacity 1.2s ease;
  pointer-events: none;
}
.nebula.is-live { opacity: 1; }

/* The title lockup, tagline, vignette and glow all come from the GAME's
   MENU_CSS via assets/game.css. These rules only undo the parts written for a
   fullscreen overlay: `.fermis-menu` is `position:fixed; inset:0; z-index:200`
   in-game, and `.center` is absolutely centred in the viewport. On a scrolling
   page both must sit in normal flow. Everything visual is left to the game. */
.hero-stage .fermis-menu.hero-menu {
  position: relative; inset: auto; z-index: 2;
  pointer-events: auto; color: inherit;
  width: min(92vw, 620px); margin: 0 auto;
}
.hero-stage .fermis-menu.hero-menu .center {
  position: relative; top: auto; left: auto; transform: none;
  width: 100%;
}

.hero-center {
  position: relative; z-index: 2;
  text-align: center; width: min(92vw, 620px);
}

.hero-lede {
  margin: 2rem auto 2.25rem; max-width: 34rem;
  color: rgba(220,226,245,.78); font-size: 1.05rem; line-height: 1.7;
  text-shadow: 0 1px 6px rgba(0,0,0,.9);
}

.hero-version {
  position: absolute; bottom: 14px; left: 20px; z-index: 2; margin: 0;
  font-family: var(--font-display);
  color: rgba(255,255,255,.42); font-size: 10px; letter-spacing: 2px;
  text-shadow: 0 1px 4px rgba(0,0,0,1), 0 0 14px rgba(0,0,0,.95);
}


/* The hero is dark in BOTH themes, so it re-declares the kit tokens locally
   rather than inheriting the light-mode remap. Without this, light mode gives
   the hero white buttons on a black nebula. Scoping the custom properties fixes
   every kit component inside the hero at once, instead of overriding each one. */
.hero-stage {
  --fm-surface: rgba(8,10,20,.75);
  --fm-surface-hover: rgba(20,24,44,.85);
  --fm-surface-deep: rgba(6,8,16,.7);
  --fm-accent: #8b96e8;
  --fm-accent-deep: rgba(60,54,140,.62);
  --fm-accent-deep-hover: rgba(78,70,175,.78);
  --fm-border: rgba(120,132,180,.34);
  --fm-border-strong: rgba(139,150,232,.8);
  --text: #d6dcf0;
}
.hero-stage .btn { color: #d6dcf0; backdrop-filter: blur(3px); }
.hero-stage .btn:hover { color: #fff; }
.hero-stage .btn-primary { color: #fff; }
.hero-stage .btn.is-disabled {
  color: #8089ad; background: rgba(8,10,20,.8); border-color: rgba(120,132,180,.2);
}

@media (max-width: 40rem) {
  .hero-stage { min-height: 88vh; padding: 5rem 1.25rem 4.5rem; }
  /* 92vw overflowed the padded stage and clipped the F and S of the lockup. */
  .hero-center { width: 100%; }
  .hero-title { max-width: 100%; }
  .hero-tagline { letter-spacing: 7px; padding-left: 7px; }
  .hero-lede { font-size: .98rem; margin: 1.5rem auto 1.75rem; }
  .hero-stage .cta-row { flex-direction: column; align-items: stretch; gap: .7rem; }
  .hero-stage .cta-row .btn { text-align: center; }
  .hero-version { font-size: 9px; letter-spacing: 1.5px; left: 14px; }
}

.lede { font-size: 1.2rem; color: var(--text-dim); max-width: 44rem; margin: 0 auto 2rem; }
.eyebrow {
  font-family: var(--font-mono); font-size: .75rem; letter-spacing: .18em;
  text-transform: uppercase; color: var(--accent); margin: 0 0 1rem;
}

.cta-row { display: flex; gap: .85rem; justify-content: center; flex-wrap: wrap; }

/* Buttons — .fm-btn from the in-game kit. Uppercase, 3px tracking, clipped. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  position: relative; padding: 11px 24px;
  font-family: var(--font-display);
  font-size: 13px; letter-spacing: 3px; text-transform: uppercase; font-weight: 500;
  text-decoration: none; cursor: pointer;
  color: var(--text); background: var(--fm-surface);
  border: 1px solid var(--fm-border);
  clip-path: var(--fm-clip);
  transition: color .18s, background .18s, border-color .18s, transform .12s;
}
.btn:hover { color: var(--text); border-color: var(--fm-border-strong); background: var(--fm-surface-hover); }
.btn:active { transform: translateY(1px); }

.btn-primary { background: var(--fm-accent-deep); border-color: rgba(139,150,232,.6); color: #fff; }
.btn-primary:hover { background: var(--fm-accent-deep-hover); color: #fff; }

.btn-ghost { background: var(--fm-surface-deep); }

.btn.is-disabled {
  cursor: not-allowed; pointer-events: none;
  color: #7078a0; border-color: rgba(120,132,180,.18); background: rgba(8,10,20,.85);
}
@media (prefers-color-scheme: light) {
  .btn.is-disabled { color: #8b93ab; background: #eef0f6; border-color: rgba(70,84,140,.18); }
}
.btn.sm { padding: 7px 14px; font-size: 11px; letter-spacing: 2px; }

/* Badge — .fm-badge */
.fm-badge {
  display: inline-block; font-family: var(--font-display);
  font-size: 10px; letter-spacing: 2px; text-transform: uppercase;
  padding: 3px 10px; border: 1px solid var(--fm-border);
  color: var(--text-dim); clip-path: var(--fm-clip-sm);
}
.fm-badge.accent { border-color: var(--fm-accent); color: var(--fm-accent); background: rgba(60,54,140,.2); }
.fm-badge.warn { border-color: rgba(239,159,39,.5); color: #c98a1e; }
@media (prefers-color-scheme: dark) {
  .fm-badge.accent { color: #c3caf5; background: rgba(60,54,140,.35); }
  .fm-badge.warn { color: #fac775; }
}

/* --- scroll-driven journey ------------------------------------------------- */
/* A sticky stage holds the live game while the panels scroll over it. The block
   is tall on purpose: its height minus one viewport IS the camera's travel, so
   more height means a slower, more readable flight between bodies. */

.journey {
  position: relative;
  z-index: 2;
  border-top: none;
}

/* FIXED, not sticky. The hero has to CROSSFADE into the game, which means the
   game must already be rendering behind the hero while the hero fades — and a
   sticky stage only exists once its own block scrolls into view. Fixed keeps it
   available the whole time; opacity (driven in journey.js) decides when it is
   seen. Being out of flow, it adds no height, so the journey block's height is
   just its panels. */
.journey-stage {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  overflow: hidden;
  z-index: 1;
  opacity: 0;
  pointer-events: none;
  /* No CSS transition: journey.js sets this on every scroll event, and a
     transition just adds lag between the scroll position and what is drawn. */
}
.journey-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
/* Second stage, for the spaces editor's own three.js scene. Stacked with the
   game stage; journey.js fades whichever the current stop needs. */
/* `transform` is load-bearing, not cosmetic: it makes this element a containing
   block for `position: fixed` descendants, so the editor's reparented UI panels
   position against the stage instead of the viewport — and inherit its opacity,
   so they fade with the scene rather than floating over the whole page. */
/* INSET, not full-bleed. The editor's own docks run the full height of their
   containing block, so a full-screen stage put them under the header and behind
   the pager. Framing it smaller keeps both clear and reads as a tool window. */
.journey-stage--editor,
.journey-stage--fleet {
  z-index: 1;
  background: #05070e;
  transform: translateZ(0);
  inset: 4.6rem 2rem 6.2rem;
  width: auto; height: auto;
  border: 1px solid var(--fm-border);
  clip-path: var(--fm-clip);
}

/* Inert: the sim has its own drag-orbit, wheel-zoom and key handlers, and the
   page must keep its scroll. `pointer-events: none` also stops the iframe
   swallowing wheel events before the pager sees them. */
.fleet-frame {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0; display: block;
  pointer-events: none;
}
@media (max-width: 48rem) {
  .journey-stage--editor,
  .journey-stage--fleet { inset: 4rem .75rem 5rem; }
}
/* The editor's panels are shown but inert; the scrim would wash them out. */
.journey-stage--editor .journey-scrim,
.journey-stage--fleet .journey-scrim { display: none; }

/* The editor fills both edges of the screen with its docks, so this panel's copy
   cannot sit in the usual left column — it landed underneath the Objects dock.
   Move it into the gap between the docks, along the bottom. */
/* Doubled class for specificity: `.journey-panel` sets align-items:center
   later in this file, and an equal-specificity rule earlier loses. */
.journey-panel.journey-panel--ui { align-items: flex-end; padding-left: 0; }
.journey-panel.journey-panel--ui .journey-copy {
  margin: 0 auto; max-width: 40rem; text-align: center;
  padding: 1.5rem 1.5rem 7rem;
  background: linear-gradient(to top, rgba(3,5,12,.94) 12%, rgba(3,5,12,.72) 62%, rgba(3,5,12,0));
}
.journey-panel.journey-panel--ui .journey-copy p { margin-left: auto; margin-right: auto; }

@media (max-width: 60rem) {
  /* Not enough room between the docks — drop them and use the plain layout. */
  .journey-stage--editor [data-editor-embed="shown"] { display: none; }
  .journey-panel.journey-panel--ui { align-items: center; }
  .journey-panel.journey-panel--ui .journey-copy { text-align: left; background: none; }
}

/* Poster underneath, so the block never looks broken while the game loads or if
   it declines (no WebGL, reduced motion). */
.journey-stage::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    radial-gradient(1.3px 1.3px at 44px 66px, rgba(255,255,255,.55), transparent),
    radial-gradient(1px 1px at 168px 34px, rgba(210,224,255,.4), transparent),
    radial-gradient(1.6px 1.6px at 96px 190px, rgba(255,238,214,.35), transparent),
    radial-gradient(ellipse 90% 55% at 55% 45%, rgba(90,110,190,.14), transparent 70%);
  background-size: 263px 263px, 331px 331px, 197px 197px, cover;
  background-repeat: repeat, repeat, repeat, no-repeat;
}

/* Readability floor for the copy, over whatever the camera is looking at. */
.journey-scrim {
  position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background:
    linear-gradient(to right,
      rgba(3,5,12,.9) 0%, rgba(3,5,12,.74) 30%, rgba(3,5,12,.2) 58%, rgba(3,5,12,0) 78%),
    linear-gradient(to top,
      rgba(3,5,12,.6) 0%, rgba(3,5,12,.2) 22%, rgba(3,5,12,0) 45%);
}

/* Each panel is one viewport of scroll. Panels sit ABOVE the sticky stage and
   are pulled back over it with a negative margin — that overlap is what makes
   the canvas stay put while the text moves. */
.journey-panel {
  position: relative; z-index: 2;
  height: 100vh;
  display: flex; align-items: center;
  pointer-events: none;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  /* Reproduce .wrap's left edge so the copy starts on the same line as the
     header brand and the footer. Deliberately `100%` (the panel's own width,
     which excludes the scrollbar) and not `100vw` — 100vw INCLUDES the
     scrollbar, so a vw-based gutter sits half a scrollbar-width left of every
     .wrap on any browser with classic scrollbars, which is a few px of drift
     that only shows up off this machine. */
  padding-left: max(0px, calc((100% - var(--max)) / 2));
}

.journey-copy {
  pointer-events: auto;
  width: 100%; max-width: 44rem;
  /* The outer half of the gutter is .journey-panel's padding; this is the
     inner half, matching .wrap. Do not reintroduce a margin here. */
  padding: 0 var(--gutter);
  opacity: .28;
  transform: translateY(14px);
  transition: opacity .5s ease, transform .5s ease;
}
.journey-panel.is-current .journey-copy { opacity: 1; transform: none; }

.journey-copy h2 {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 3.4vw, 2.6rem); line-height: 1.15;
  margin: 0 0 1.1rem; color: #eef2fb; font-weight: 600;
  text-shadow: 0 2px 14px rgba(0,0,0,.9);
}
.journey-copy p {
  color: rgba(216,224,242,.85); margin: 0 0 .9rem; max-width: 38rem;
  text-shadow: 0 1px 8px rgba(0,0,0,.95);
}

@media (max-width: 48rem) {
  /* Below --max the panel's padding already computes to 0, so the copy needs
     no override; only the reduced side padding differs from the base rule. */
  .journey-copy { padding: 0 var(--gutter); }
  .journey-scrim {
    background: linear-gradient(to top,
      rgba(3,5,12,.92) 0%, rgba(3,5,12,.72) 34%, rgba(3,5,12,.25) 62%, rgba(3,5,12,0) 88%);
  }
}

@media (prefers-reduced-motion: reduce) {
  /* No fixed stage and no fading: everything flows normally over the poster. */
  .hero-holder { height: auto; scroll-snap-align: none; }
  .hero-stage { position: relative; inset: auto; min-height: 90vh; }
  .journey-panel { scroll-snap-align: none; }
  .journey-stage { position: relative; height: 60vh; opacity: 1; }
  .journey-panel { height: auto; padding: 4rem 0; }
  .journey-copy { opacity: 1; transform: none; }
}

/* --- showcase sections ----------------------------------------------------- */
/* Full-viewport panels, one live scene each. Scroll-snap gives the paged feel;
   `proximity` rather than `mandatory` so a visitor who just wants to reach the
   footer isn't fought by the scroller on every section. */


.showcase {
  position: relative;
  min-height: min(94vh, 900px);
  display: flex; align-items: flex-end;
  overflow: hidden;
  background: #04060d;
  border-top: 1px solid var(--fm-border);
}

.showcase-canvas,
.showcase-poster {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; border: 0; z-index: 0;
}
.showcase-canvas { opacity: 0; transition: opacity 1s ease; }
.showcase-canvas.is-live { opacity: 1; }

/* Shown beneath the canvas while a scene loads, and permanently for the
   not-yet-built section and reduced-motion visitors. */
.showcase-poster,
.showcase[data-scene-state="loading"] .showcase-canvas,
.showcase[data-scene-state="failed"] .showcase-canvas {
  background:
    radial-gradient(1.3px 1.3px at 44px 66px, rgba(255,255,255,.6), transparent),
    radial-gradient(1px 1px at 168px 34px, rgba(210,224,255,.45), transparent),
    radial-gradient(1.6px 1.6px at 96px 190px, rgba(255,238,214,.4), transparent),
    radial-gradient(ellipse 90% 55% at 55% 45%, rgba(90,110,190,.16), transparent 70%);
  background-size: 263px 263px, 331px 331px, 197px 197px, cover;
  background-repeat: repeat, repeat, repeat, no-repeat;
}

/* Copy sits bottom-left over the scene, with a gradient scrim so text stays
   readable no matter what drifts behind it. */
.showcase-copy {
  position: relative; z-index: 2;
  width: 100%; max-width: 46rem;
  padding: 6rem 1.5rem 3.5rem;
  margin: 0 auto 0 max(0px, calc((100% - var(--max)) / 2));
}
/* Two scrims, not one. A bottom-up gradient alone still left asteroids drifting
   directly behind the headline; the horizontal pass darkens the copy side so the
   text always has a floor to sit on, whatever the scene does. */
.showcase::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(to right,
      rgba(3,5,12,.88) 0%, rgba(3,5,12,.72) 28%, rgba(3,5,12,.2) 55%, rgba(3,5,12,0) 76%),
    linear-gradient(to top,
      rgba(3,5,12,.92) 0%, rgba(3,5,12,.7) 24%, rgba(3,5,12,.22) 50%, rgba(3,5,12,0) 76%);
}
@media (max-width: 48rem) {
  /* Narrow screens: the copy fills the width, so a horizontal scrim would just
     dim the whole scene. Bottom-up only. */
  .showcase::after {
    background: linear-gradient(to top,
      rgba(3,5,12,.95) 0%, rgba(3,5,12,.8) 30%, rgba(3,5,12,.3) 58%, rgba(3,5,12,0) 82%);
  }
}

.showcase-eyebrow {
  font-family: var(--font-display);
  font-size: 10px; letter-spacing: 4px; text-transform: uppercase;
  color: var(--fm-accent); margin: 0 0 .85rem;
}
.showcase-copy h2 {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 3.4vw, 2.6rem); line-height: 1.15;
  margin: 0 0 1.1rem; color: #eef2fb; font-weight: 600;
  text-shadow: 0 2px 14px rgba(0,0,0,.85);
}
.showcase-copy p {
  color: rgba(216,224,242,.82); margin: 0 0 .9rem; max-width: 40rem;
  text-shadow: 0 1px 8px rgba(0,0,0,.9);
}
.showcase-note {
  font-size: .85rem; color: rgba(160,174,206,.7) !important;
  font-family: var(--font-display); letter-spacing: 1px;
}

.showcase--pending .showcase-copy h2,
.showcase--pending .showcase-copy p { opacity: .85; }

@media (max-width: 48rem) {
  .showcase { min-height: 86vh; }
  .showcase-copy { padding: 4rem 1.25rem 2.5rem; margin-left: 0; }
}

/* --- panels --------------------------------------------------------------- */
/* UNUSED as of 2026-07-31 — the panel grid and the Early Access callout were
   removed from content/index.html. Kept because the removal was "for now" and
   these mirror the in-game kit (.fm-panel / .fm-card / .fm-notice), so they are
   the styles any future prose block on this site should use. Delete them if
   nothing has claimed them by launch. */

.panel-grid {
  display: grid; gap: 1.1rem;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  margin-bottom: 3.5rem;
}
/* Panels — .fm-panel / .fm-card from the in-game kit. Clipped corners, and
   headings in the kit's small-caps accent style rather than large display type. */
.panel {
  position: relative;
  background: var(--bg-panel); border: 1px solid var(--fm-border);
  clip-path: var(--fm-clip); padding: 20px 22px;
  transition: border-color .15s, background .15s;
}
.panel:hover { border-color: var(--fm-border-strong); }
.panel h2 {
  font-family: var(--font-display);
  font-size: 11px; letter-spacing: 4px; text-transform: uppercase;
  color: var(--fm-accent); margin: 0 0 16px; font-weight: 600;
}
.panel p { margin: 0; color: var(--text-dim); font-size: .95rem; }

/* Notice — .fm-notice. Accent rule down the left, corner clipped bottom-right. */
.callout {
  background: var(--bg-raised); border: 1px solid var(--fm-border);
  border-left: 2px solid var(--fm-accent);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%);
  padding: 1.75rem; margin-bottom: 3.5rem;
}
.callout h2 {
  font-family: var(--font-display); margin: 0 0 .9rem;
  font-size: 11px; letter-spacing: 4px; text-transform: uppercase;
  color: var(--fm-accent); font-weight: 600;
}
.callout p { color: var(--text-dim); margin: 0 0 .75rem; }
.callout p:last-child { margin-bottom: 0; }
.callout strong { color: var(--text); }

/* --- newsletter ----------------------------------------------------------- */

.follow {
  text-align: center; padding: 2.25rem 1.5rem; margin-bottom: 2rem;
  background: var(--bg-panel); border: 1px solid var(--fm-border);
  clip-path: var(--fm-clip);
}
.follow h2 {
  font-family: var(--font-display);
  font-size: 11px; letter-spacing: 4px; text-transform: uppercase;
  color: var(--fm-accent); margin: 0 0 .75rem; font-weight: 600;
}
/* `.follow` prefix is load-bearing: the generic `p.muted, p.small` rule further
   down is specificity 0,1,1 and would otherwise beat a bare `.nl-lede` (0,1,0),
   resetting `margin: 0 auto` to `.35rem 0` and knocking these blocks off-centre. */
.follow .nl-lede { margin: 0 auto 1.5rem; max-width: 34rem; }

.nl-form {
  display: flex; gap: .6rem; justify-content: center;
  max-width: 30rem; margin: 0 auto; flex-wrap: wrap;
}
/* .fm-input comes from the game's kit (game.css); it is dark-native, so light
   mode needs a surface remap here rather than in the generated file. */
.nl-form .fm-input {
  flex: 1 1 15rem; min-width: 0; width: auto;
  font-family: var(--font-body); letter-spacing: normal;
  color: var(--text); background: var(--fm-surface-deep);
  border-color: var(--fm-border);
}
.nl-form .fm-input::placeholder { letter-spacing: normal; color: var(--text-dim); }
.nl-form .btn { flex: 0 0 auto; }
.follow .nl-note { margin: 1rem auto 0; max-width: 32rem; }
.follow .nl-fallback { margin: 0; }

/* Honeypot. Deliberately NOT display:none or type=hidden — a bot filling every
   field must be able to find it, while a person must never see or tab into it.
   Kept in the layout at zero size instead. */
.nl-hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px !important; height: 1px !important;
  opacity: 0 !important; pointer-events: none !important;
}

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

/* --- prose (legal + text pages) ------------------------------------------- */

.prose { max-width: 45rem; margin: 0 auto; }
.prose h1 {
  font-family: var(--font-display); font-size: clamp(1.9rem, 4vw, 2.6rem);
  margin: 0 0 1.25rem; line-height: 1.15;
}
.prose h2 {
  font-family: var(--font-display); font-size: 1.4rem;
  margin: 2.5rem 0 .85rem; padding-top: .5rem; border-top: 1px solid var(--border);
}
.prose h3 { font-family: var(--font-display); font-size: 1.12rem; margin: 1.85rem 0 .6rem; }
.prose p, .prose li { color: var(--text-dim); }
.prose strong { color: var(--text); }
.prose ul, .prose ol { padding-left: 1.35rem; }
.prose li { margin-bottom: .45rem; }
.prose hr { border: 0; border-top: 1px solid var(--border); margin: 2.5rem 0; }
.prose code {
  font-family: var(--font-mono); font-size: .875em;
  background: var(--bg-panel); border: 1px solid var(--border);
  padding: .1em .38em; border-radius: 4px; color: var(--text);
}
.prose pre {
  background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 1rem; overflow-x: auto; font-size: .85rem;
}
.prose pre code { background: none; border: 0; padding: 0; }
.prose blockquote {
  margin: 1.5rem 0; padding: .25rem 0 .25rem 1.15rem;
  border-left: 3px solid var(--accent); color: var(--text-dim);
}
.prose blockquote p:last-child { margin-bottom: 0; }

.factsheet {
  display: grid; grid-template-columns: minmax(7rem, auto) 1fr;
  gap: .5rem 1.25rem; margin: 1.5rem 0 2rem;
  border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem;
  background: var(--bg-panel);
}
.factsheet dt { font-weight: 600; color: var(--text); }
.factsheet dd { margin: 0; color: var(--text-dim); }

.error-page { text-align: center; padding: 3rem 0; }
.error-page .eyebrow { font-size: 1rem; }

/* --- footer --------------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--border); padding: 2.5rem 0;
  background: var(--bg-raised); margin-top: 3rem;
  /* NOT a snap target — tried 2026-07-31 and reverted. The idea was that
     aligning its top with the viewport top resolves past the document end and
     clamps to the true bottom, so End would land on the footer. In practice
     Chromium resolved the unreachable target by snapping all the way back to
     the hero (End from a fresh load went to y=0 instead of the bottom), which
     is far worse than the ~230px shortfall it was meant to fix. */
}
.footer-inner {
  display: flex; flex-wrap: wrap; gap: 1.75rem;
  justify-content: space-between; align-items: flex-start;
}
.footer-brand { font-family: var(--font-display); margin: 0 0 .3rem; font-weight: 600; }
.footer-links { display: flex; flex-direction: column; gap: .45rem; }
.footer-links a { color: var(--text-dim); text-decoration: none; font-size: .92rem; }
.footer-links a:hover { color: var(--text); text-decoration: underline; }

.muted { color: var(--text-dim); }
.small { font-size: .88rem; }
p.muted, p.small { margin: .35rem 0; }

@media (max-width: 40rem) {
  main { padding-top: 2.25rem; }
  .header-inner { min-height: auto; padding: .75rem 0; }
  .site-header nav { gap: 1rem; }
  .factsheet { grid-template-columns: 1fr; gap: .1rem .5rem; }
  .factsheet dd { margin-bottom: .6rem; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
  .btn:hover { transform: none; }
}


/* --- travelling title lockup ------------------------------------------------
   Starts centred on the title screen at full size and ends in the header's
   brand slot, small. It is NOT inside .hero-stage: the hero fades out, and this
   must survive that fade to arrive in the header. journey.js interpolates the
   transform from the same scroll progress that drives the crossfade. */

.brand-dock {
  position: fixed;
  left: 50%;
  width: min(92vw, 620px);
  margin-left: calc(min(92vw, 620px) / -2);
  /* Vertically centred on the title screen at rest. `top:50%` alone puts the
     block's TOP at the middle; the negative margin lifts it by half its height
     (wordmark ~0.118 x width, plus the tagline) so it is actually centred. */
  top: 50%;
  margin-top: calc(min(92vw, 620px) * -0.059 - 1.6rem);
  transform-origin: 0 0;
  z-index: 31;                 /* above the header bar, below nothing else */
  pointer-events: none;        /* the header's <a> underneath stays clickable */
  will-change: transform;
}
/* Undo the game menu's fullscreen-overlay positioning, as elsewhere. */
.brand-dock .fermis-menu {
  position: static; inset: auto; z-index: auto; pointer-events: none;
}
.brand-dock .fermis-menu .center {
  position: static; transform: none; width: 100%; top: auto; left: auto;
}
.brand-dock .fermis-menu .center img { width: 100%; height: auto; }
/* Fades as the lockup shrinks; .brand-sub--docked takes over in the header. */
.brand-dock .tagline { transition: opacity .15s linear; }

@media (prefers-reduced-motion: reduce) {
  /* No travel: park it in the header and leave it there. */
  .brand-dock { display: none; }
}


/* --- pager ------------------------------------------------------------------
   Fixed bottom-centre, above both stages. Up/down chevrons plus a dot per stop,
   so a visitor can see how many sections there are and that they are navigable
   — the previous hint was an animated dot with no arrow and no affordance. */

.pager {
  position: fixed; z-index: 32;
  left: 50%; bottom: 1.6rem; transform: translateX(-50%);
  display: flex; align-items: center; gap: .85rem;
  padding: .45rem .6rem;
  background: rgba(8,10,20,.62);
  border: 1px solid var(--fm-border);
  clip-path: var(--fm-clip-sm);
  backdrop-filter: blur(6px);
  transition: opacity .3s ease;
}
.pager[hidden] { display: none; }
.pager.is-faded { opacity: 0; pointer-events: none; }

.pager-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 30px; padding: 0;
  background: none; border: 0; cursor: pointer;
  color: rgba(214,222,242,.8);
  transition: color .15s, transform .15s;
}
.pager-btn svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2.1; stroke-linecap: round; stroke-linejoin: round; }
.pager-btn:hover:not(:disabled) { color: #fff; transform: translateY(-1px); }
.pager-btn:disabled { opacity: .28; cursor: default; }
.pager-btn[data-dir="1"]:hover:not(:disabled) { transform: translateY(1px); }

/* One dot per stop; the current one is filled. */
.pager-dots { display: flex; align-items: center; gap: 6px; }
.pager-dots i {
  display: block; width: 5px; height: 5px; border-radius: 50%;
  background: rgba(190,202,236,.3); transition: background .2s, transform .2s;
}
.pager-dots i.on { background: var(--fm-accent); transform: scale(1.5); }

/* The mouse-wheel hint, back alongside the arrows. Brighter than the original:
   at rgba(...,.35) border and .8 dot it was easy to miss entirely. */
.pager-wheel {
  position: relative; display: block;
  width: 21px; height: 33px; border-radius: 11px;
  border: 1.5px solid rgba(196,210,248,.75);
  flex: none;
}
.pager-wheel span {
  position: absolute; left: 50%; top: 6px;
  width: 3px; height: 7px; border-radius: 2px;
  background: #eef2fb;
  transform: translateX(-50%);
  animation: pager-wheel-hint 1.9s ease-in-out infinite;
}
@keyframes pager-wheel-hint {
  0%, 100% { opacity: 0; transform: translate(-50%, 0); }
  30%      { opacity: 1; }
  70%      { opacity: 0; transform: translate(-50%, 12px); }
}

@media (max-width: 40rem) { .pager { bottom: 1rem; gap: .6rem; } .pager-wheel { display: none; } }
@media (prefers-reduced-motion: reduce) {
  /* Keep the arrows — they are the navigation — but stop the hint animating. */
  .pager-wheel span { animation: none; opacity: .9; }
}
