/* Home page. Loaded after main.css and only by index.html, so every rule here
   is a deliberate deviation for the home page and the other four pages are
   untouched — which is also why main.css's cache-buster does not move.

   Started life as a working copy (index-wip.html + wip.css); promoted once the
   hero landed. Anything in here that should apply site-wide still needs
   folding into main.css, which is a change to all five pages. */

/* ---------- global type scale, +10% ---------- */

/* Everything sized in rem rides this. main.css runs clamp(13px, 10px + 0.24vw,
   18px); each term is multiplied by 1.21 (two 10% steps) so the whole curve
   lifts, not just the ends.

   The terms are rem, not px. On the root element rem resolves against the
   browser's own font-size setting, so a reader who has set 20px default gets a
   proportionally larger site — a px root silently overrides that preference.
   Against a 16px default these land on the same 15.7 / 21.8px as before. */
html { font-size: clamp(0.98rem, 0.76rem + 0.29vw, 1.36rem); }

/* .btn is deliberately sized in px, not rem (main.css:506) — an 8px pixel face
   lands on fractional sizes at some root scales and the stems go uneven — so it
   sits outside the rem scale and has to be stepped by hand. 11 -> 13 is the
   nearest whole pixel to +21%. Pixel faces can only be stepped in whole pixels,
   so these move at breakpoints instead of scaling continuously. */
.btn { font-size: 13px; }
.nav-links a.nav-cta { font-size: 11px; }
.lightbox button { font-size: 13px; }

@media (min-width: 1600px) {
  .btn { font-size: 15px; }
  .nav-links a.nav-cta { font-size: 12px; }
  .lightbox button { font-size: 15px; }
}

@media (max-width: 480px) {
  .btn { font-size: 12px; }
  .lightbox button { font-size: 12px; }
}

/* ---------- hero: left column ---------- */

/* The copy hangs off the same gutter as the wordmark and the build stamp, so
   the headline, the form and the stamp all start on one vertical. */
.hero .wrap { text-align: left; }

/* the stamp and the buttons are flex rows, so text-align never reaches them */
.hero .hero-tag,
.hero .hero-actions { justify-content: flex-start; }

/* the sub-copy is width-capped; keep it hard against the left edge */
.hero .hero-sub { margin-left: 0; margin-right: auto; }

/* ---------- boot screen ---------- */

/* The game's NEW GAME loader (boot_loading_screen.gd), rebuilt in the DOM.
   Everything is a fixed character grid — ModeNine is monospaced at 0.6em per
   cell, so the .gd's pixel coordinates map straight onto columns: BLOCK_W 480
   is 80 columns, status_x 260 is column 43, ROW_H 16 is 1.6em at FONT_BODY 10.
   Hidden without JS, since nothing would ever clear it. */
.boot { display: none; }

/* set in the head on a return visit within the session — see index.html */
.booted .boot { display: none !important; }

.js .boot {
  position: fixed;
  inset: 0;
  z-index: 9999; /* over the nav's 100 */
  display: flex;
  align-items: center;
  justify-content: center;
  /* --void, not --void-deep: the deep tone is the game's near-black screen
     colour, and a full viewport of it reads as a dead monitor rather than as
     this page waking up. This is the body's own ground (main.css:64) and the
     same value the starfield clears to, so the dissolve lands on a matching
     field instead of stepping two shades lighter. */
  background: var(--void);
  overflow: hidden;
}

/* Palette. The .gd's five Color constants map onto the site's tokens by the
   ROLE each one plays, not by hue — the game runs a warmer phosphor with a
   cyan and an orange accent, and dropping those in raw would make the loader
   read as a different product than the page it opens onto.

     BRIGHT   -> --amber       the signature green, as on buttons and links
     PHOSPHOR -> --text        ordinary readout copy
     DIM      -> --text-dim    dot leaders, hex dumps, the standby line
     CYAN     -> --teal        section headers, one step off the body tone
     AMBER    -> --ember       the site's only warm accent, kept for warnings,
                               live digits and the status line */
.boot .c-bright { color: var(--amber); }
.boot .c-phos   { color: var(--text); }
.boot .c-dim    { color: var(--text-dim); }
.boot .c-cyan   { color: var(--teal); }
.boot .c-amber  { color: var(--ember); }

.boot-screen {
  /* 80 columns of ModeNine. The cap keeps it at the game's own 480px block on
     a desktop; the vw term is what shrinks it to fit a phone, since a
     character grid cannot reflow. */
  font-family: var(--font-term);
  font-size: min(13px, 1.5vw);
  line-height: 1.6em;
  width: 80ch;
  /* the tube ignites, then the whole readout flickers up to brightness */
  animation: boot-flicker 0.56s linear both;
}

/* Chromatic fringe, the .gd's _text() recipe. The game splits hard red/blue;
   nothing else on the site does that, so it is pulled toward the page's own
   two accents — a teal lead and an ember trail — and dialled down to where it
   reads as phosphor bloom rather than a broken monitor. */
:root { --boot-fringe: 1px 0 rgba(90, 191, 138, 0.28), -1px 0 rgba(217, 106, 85, 0.2); }

.boot-row {
  margin: 0;
  white-space: pre;
  height: 1.6em;
  text-shadow: var(--boot-fringe);
}

.boot-title { font-size: 1.2em; margin-bottom: 0.5em; }
.boot-head { font-size: 1.1em; margin-top: 0.8em; }
.boot-standby { font-size: 0.8em; }

.boot-tick span + span { margin-left: 4.4ch; } /* col_x + 44px at FONT_BODY */

/* BOOT LOG: a 4-row window the lines scroll up through */
.boot-log {
  height: 5.6em; /* LOG_ROWS_VISIBLE * LOG_ROW_S */
  overflow: hidden;
}

.boot-logline {
  height: 1.4em;      /* LOG_ROW_S 14 */
  line-height: 1.4em;
}

/* BASIC block cursor, one full cell wide — same shape as the site's caret */
.boot-caret {
  display: inline-block;
  width: 0.6em;
  height: 0.85em;
  margin-left: 0.1em;
  background: currentColor;
  vertical-align: -0.06em;
}

/* LOADING: bare segmented gauge, no track and no border — lit cells alone
   mark the progress, exactly as _draw_cell_bar does it */
.boot-bar {
  display: flex;
  align-items: center;
  gap: 1ch;
  height: 1.6em;
}

/* Whole pixels, not fractions of an em. At the readout's font-size a 0.4em
   cell is 5.2px and a 0.2em gap 2.6px; the browser rounds each independently,
   so the gaps drift and the run looks ragged. The cell count is measured from
   this track in home-boot.js rather than fixed, so the row still fills. */
.boot-cells {
  display: flex;
  gap: 2px;          /* CELL_GAP */
  flex: 1 1 auto;
  overflow: hidden;  /* a part-cell remainder is clipped, never half-drawn */
}

.boot-cells i {
  flex: 0 0 auto;
  width: 4px;        /* CELL_W */
  height: 0.9em;
  background: var(--amber);
  opacity: 0;
}

.boot-cells i.on { opacity: 1; }

.boot-pct {
  color: var(--amber);
  text-shadow: var(--boot-fringe);
}

/* CRT power-on: a hot centre line snapping open to full height, under a
   readout that flickers up behind it */
.boot-tube {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 3px;
  margin-top: -1.5px;
  background: var(--amber-hi);
  animation: boot-tube 0.14s ease-out both;
}

@keyframes boot-tube {
  from { transform: scaleY(1); opacity: 1; }
  to   { transform: scaleY(120); opacity: 0; }
}

/* deterministic dips that settle out, matching _intro_alpha's shape */
@keyframes boot-flicker {
  0%   { opacity: 0; }
  25%  { opacity: 0; }
  35%  { opacity: 0.42; }
  45%  { opacity: 0.18; }
  60%  { opacity: 0.74; }
  72%  { opacity: 0.46; }
  100% { opacity: 1; }
}

/* Outro: elements dissolve in a stagger, hints first and the gauge last, so
   the bar holding 100% is the final thing to go. */
.js .boot.boot--out { pointer-events: none; }

.boot--out .boot-screen > * {
  opacity: 0;
  transition: opacity 0.3s ease;
}

.boot--out .boot-standby { transition-delay: 0s; }
.boot--out .boot-log     { transition-delay: 0.15s; }
.boot--out .boot-tick,
.boot--out .boot-status  { transition-delay: 0.05s; }
.boot--out .boot-head    { transition-delay: 0.3s; }
.boot--out .boot-title   { transition-delay: 0.4s; }
.boot--out .boot-bar     { transition-delay: 0.55s; } /* OUTRO_LAST_DELAY */

.boot--out {
  opacity: 0;
  transition: opacity 0.25s ease 0.75s;
}

/* A boot screen that animates against a held frame is worse than none, so
   with reduced motion the script clears it outright — this only makes sure
   nothing flashes in the frame before that happens. */
@media (prefers-reduced-motion: reduce) {
  .js .boot { display: none; }
}

/* ---------- hero: footage held right of the copy ---------- */

/* The copy is back on the left gutter, so the footage clears it again. The
   shift both moves the frame right and narrows its box — object-fit: cover
   centres the source inside that box, so the middle of the shot lands right
   of screen centre instead of behind the headline. Fluid rather than the flat
   480px in main.css: at 1920 it gives up 480px as before, and it shrinks with
   the window instead of eating the whole column on a laptop. */
.hero-bg { --hero-shift: clamp(0px, 25vw, 480px); }

/* .hero starts below the sticky bar, so an inset-0 backdrop starts there too
   and leaves a band of page above the footage — most obvious during the boot
   hold, when the footage is the only thing on screen. Grow the box up by the
   bar's own height so the video runs to the top edge and the bar sits on it.
   .site-nav is z-index 100 on an opaque #020403, so it covers cleanly. */
.hero-bg { top: calc(-1 * var(--nav-h, 71px)); }

/* Its left edge dissolves over most of the copy column rather than in the
   220px main.css allows, so the footage arrives out of the page background
   behind the text instead of starting at a visible seam. Stops are in % of
   the video's own box, so the ramp scales with the shift; the uneven alpha
   steps keep the ramp from reading as a band the way a two-stop one does. */
.hero-bg video {
  margin-left: var(--hero-shift);
  width: calc(100% - var(--hero-shift));
  --hero-edge:
    linear-gradient(
      90deg,
      rgba(0, 0, 0, 0) 0%,
      rgba(0, 0, 0, 0.06) 8%,
      rgba(0, 0, 0, 0.2) 17%,
      rgba(0, 0, 0, 0.45) 27%,
      rgba(0, 0, 0, 0.72) 37%,
      rgba(0, 0, 0, 0.92) 46%,
      rgba(0, 0, 0, 1) 56%
    );
  -webkit-mask-image: var(--hero-edge);
  mask-image: var(--hero-edge);
}

/* Below the crossover the copy runs to full width and there is no room to
   give away, so the footage goes full-bleed and only keeps a short edge fade.
   main.css does this at 860px, but its rule loses to the one above — this
   file loads later — so it is restated here. */
@media (max-width: 860px) {
  .hero-bg { --hero-shift: 0px; }
  .hero-bg video {
    --hero-edge: linear-gradient(90deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 140px);
  }
}

/* Ground under the copy plus an even wash over the rest. The left end is the
   page colour at nearly full strength, which is what the video's own edge fade
   dissolves into, so the two meet on the same value and the transition reads
   as one surface rather than two overlapping dims. */
.hero-shade {
  background:
    linear-gradient(
      90deg,
      rgba(10, 15, 11, 0.86) 0%,
      rgba(10, 15, 11, 0.7) 24%,
      rgba(4, 8, 5, 0.5) 46%,
      rgba(4, 8, 5, 0.45) 62%,
      rgba(4, 8, 5, 0.45) 100%
    );
}

/* Full-bleed footage below the crossover, so the ramp has nothing to clear */
@media (max-width: 860px) {
  .hero-shade { background: rgba(4, 8, 5, 0.45); }
}

/* ---------- hero: newsletter capture ---------- */

.hero-signup {
  margin: 2rem 0 0;
  max-width: 30rem;
  /* The label sits under the field, not over it: by the time anyone reads this
     the input and its button are the obvious thing on screen, and the line
     works better as a note on what was just offered than as a heading for it.

     Done with order rather than by moving the markup — the label has to stay
     ahead of its input in source so assistive tech announces the name before
     the field it belongs to. The honeypot is absolutely positioned and takes
     no part in this. */
  display: flex;
  flex-direction: column;
}

.hero-signup-row { order: 1; }
.form-status { order: 3; }

/* Held from FIRST PAINT, not from when the script gets to it. main.css:378
   hides these off .hero-hold, but that class is stamped by JS at the end of
   the body — so between the hero parsing and the script running there is a
   window where the form, its input and the corner button are on screen at
   full strength. Short on a warm load, very visible on a cold one.

   Keyed off :not(.in) so it releases on exactly the same class the staged
   reveal already adds, and scoped to .js so a scripted release is guaranteed
   to come. Reduced motion opts out: that path never runs the reveal. */
@media (prefers-reduced-motion: no-preference) {
  .js .hero-signup:not(.in),
  .js .hero-corner-cta:not(.in) { opacity: 0; }
}

.hero-signup-label {
  order: 2;
  display: block;
  font-family: var(--font-term);
  font-size: 0.9rem;
  letter-spacing: 0.06em;
  color: var(--teal);
  text-transform: uppercase;
  margin-top: 1.15rem;
  margin-bottom: 0;
}

/* input takes the slack, button stays at its own width */
.hero-signup-row {
  display: flex;
  gap: 0.6rem;
}

.hero-signup input {
  flex: 1 1 auto;
  min-width: 0; /* flex items floor at content width, and an input's is wide */
  background: var(--void-deep);
  border: 2px solid var(--line);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1rem;
  padding: 0.6rem 0.8rem;
}

/* The row stretches both items to the same border box, but .btn draws its
   frame with pseudo-elements inset -2px and sized 100% + 4px, so the button
   *looks* 4px taller. Pulling the input's block margins in by the same 2px
   grows it into that overhang and the two visible boxes line up. */
.hero-signup input { margin-block: -2px; }

.hero-signup input::placeholder { color: var(--text-dim); }

.hero-signup .btn { flex: 0 0 auto; }

/* aligned like the label above it — the two lines read as one block */
.hero-signup .form-status { text-align: left; }


/* the row is already near its cap at 30rem, so it breaks rather than squeezing
   the input down to a few characters */
@media (max-width: 480px) {
  .hero-signup-row { flex-direction: column; }
}

/* ---------- hero: build stamp parked bottom-left ---------- */

/* .hero is already position: relative. The left offset repeats .wrap's gutter
   so the stamp lines up with the copy column instead of the raw viewport. */
.hero .hero-tag {
  position: absolute;
  left: clamp(1rem, 3.5vw, 4rem);
  bottom: 2rem;
  margin-bottom: 0;
  justify-content: flex-start;
  text-align: left;
}

/* short viewports run the hero padding down to 1.5rem, where an absolute stamp
   would sit on top of the buttons, so hand it back to the flow */
@media (max-width: 700px) {
  .hero .hero-tag {
    position: static;
    justify-content: flex-start;
    margin-bottom: 2.2rem;
  }
}

/* section heads had the same bare-vw middle term as the hero title */
.sec-head { font-size: clamp(1.3rem, 0.9rem + 1.6vw, 1.9rem); }

/* The hero lines are nowrap so the headline breaks where the markup says. That
   holds while the line fits; at phone widths "One person, zero experience." is
   28 monospaced cells and cannot, so let it break rather than run off-screen. */
@media (max-width: 700px) {
  .hero-title .hero-line { white-space: normal; }
}

/* ---------- nav: smaller mark ---------- */

/* The nav mark is the Void Galactica wordmark, same as every other page, so
   the sizing and glow overrides that were here for the Atomic Games SVG are
   gone — main.css:231 already handles it. Keeping them would have made the
   home page's bar a different height from the rest of the site, and doubled
   the glow that logo5 already has baked in. */

/* ---------- hero: lighter headline ---------- */

/* h1 defaults to bold and ModeNine ships one weight, so the browser was
   synthesising the bold by smearing each stem. Asking for 400 gets the real
   outline back, which is the thinner stroke. */
.hero-title {
  font-weight: 400;
  /* Was clamp(1.5rem, 5.5vw, 3rem) — a bare vw middle term ignores the reader's
     font-size setting entirely and only answers to window width, which is the
     thing WCAG 1.4.4 is about. Pairing a rem with a smaller vw keeps the fluid
     growth while leaving a floor that still responds to the browser setting.
     Crossover to the 3.5rem cap lands near 890px, as it did before. */
  font-size: clamp(1.75rem, 1rem + 4.5vw, 3.5rem);
  /* the tracking was set for a smaller size; at this one it reads loose */
  letter-spacing: 0;
  /* ModeNine is monospaced, so its space is a full 0.6em cell — the same width
     as a letter. At headline size that opens visible holes between words, so
     pull a third of the cell back out. */
  word-spacing: -0.2em;
}

/* ---------- buttons: ground under the label ---------- */

/* main.css leaves .btn transparent, so over the hero footage the label fights
   the moving image. --void-deep at 50% gives it a floor without going opaque.
   :hover/:focus in main.css are more specific, so the amber fill still wins. */
.btn { background: rgba(6, 9, 6, 0.5); }

/* ---------- caret: re-cut for ModeNine ---------- */

/* main.css sizes the block off PixelOperator8 (cap 0.875em, proportional), so
   under ModeNine it ran tall and sat wrong on the baseline. Measured from the
   TTF at 1000 upem: caps reach 700, descender drops to -200, every advance is
   600. So the cell is 0.6em wide, and the block keeps main.css's proportions
   against the cap band rather than against the em: 1.14x cap tall, dipping
   0.09x cap below the baseline, which clears the caps by a hair the way a
   firmware cursor overshoots its own row. */
.type-caret {
  width: 0.6em;
  height: 0.8em;
  /* +2px nudge off that baseline, by eye — the em term stays so the caret keeps
     tracking the headline's fluid size, and the px rides on top of it. */
  vertical-align: calc(-0.064em + 2px);
  margin-left: 0.12em; /* the block already fills a cell, so it needs less air */
}

/* ---------- hero: corner CTA, bottom-right ---------- */

/* Counterweight to the build stamp: same gutter off its own edge, same
   distance off the bottom, so the two bracket the hero. .btn is inline by
   default and its drawn frame sits 2px proud, hence the block display and the
   2px of extra clearance. */
.hero-corner-cta {
  position: absolute;
  right: clamp(1rem, 3.5vw, 4rem);
  bottom: calc(2rem + 2px);
  display: block;
  text-decoration: none;
}

@media (max-width: 700px) {
  /* The corner button is the desktop hero's last beat, held in the corner
     against the footage. A phone has no corner to hold — it used to drop into
     the flow under the form, where it just repeated the Devlog link the nav
     already carries, two taps from the same thumb. */
  .hero-corner-cta { display: none; }

  /* The whole stamp line goes, version and "Early proof of concept" both. On
     the first screen of a phone it spent a line on something nobody arriving
     here needs yet.

     Hidden, not removed: the staged reveal in index.html keys off .hero-tag,
     taking its hold when the stamp takes one and releasing on the class main.js
     adds to it. All of that is class changes, which display:none does not
     stop — dropping the element from the markup would strand the queue. */
  .hero-tag { display: none; }

  /* The order is set globally now; the phone only wants it smaller. */
  .hero-signup-label { font-size: 0.765rem; } /* 0.9rem less 15% */

  /* This page runs the whole type scale +21%, so its buttons sit a step above
     main.css's — 13 against 11 on the desk, 15 against 14 here. Stated in this
     file because a media query adds no specificity: main.css's mobile .btn
     rule loses to the plain .btn above in this one, which loads later. */
  .btn { font-size: 15px; }

  /* Sign-up sits down by the bottom edge rather than floating in a centred
     block. The hero stops centring its contents, the wrap becomes the column
     that holds them, and auto top margin hands the sign-up every pixel of
     slack above it — which collapses to nothing when the content is taller
     than the screen, so a small phone still just scrolls normally. */
  /* iOS Safari's retracting toolbars. main.css sizes the hero to 100svh — the
     viewport with the bars OUT — so the moment they retract the viewport grows
     past the hero and the next section's heading surfaces at the foot of the
     screen, half under the bar.

     Sizing to 100lvh instead fixes that and breaks something worse: the hero
     would then be taller than the visible screen whenever the bars ARE out,
     and the sign-up pinned to its foot would sit behind them.

     So do both. The BOX takes the large viewport, so nothing can ever surface
     below it; the CONTENT is then held back up into the small viewport by
     padding of exactly the difference. 100lvh - 100svh is that difference —
     the toolbar's height, without having to know what it is. The sign-up
     lands 1.4rem above where the bar begins, wherever that turns out to be.

     Doubled declarations are the fallback: a browser without lvh/svh drops
     those lines and keeps the plain vh above them. */
  .hero {
    justify-content: flex-start;
    min-height: calc(100vh - var(--nav-h, 71px));
    min-height: calc(100lvh - var(--nav-h, 71px));
    padding-bottom: 1.4rem;
    padding-bottom: calc(
      100lvh - 100svh + max(1.4rem, env(safe-area-inset-bottom))
    );
  }

  .hero .wrap {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
  }

  /* An auto above the headline and another below it, so the free space splits
     evenly and the title comes out centred in whatever is left between the
     stamp and the form — the form is pushed to the foot because every spare
     pixel has been claimed above it. One auto on the form alone put the title
     at the top instead. (Both autos sat on the wordmark and the title while
     the mark was still here; it was the top of the same group.) */
  .hero-title { margin-top: auto; margin-bottom: auto; }

  /* Matched to the taller button beside it. 16px exactly, not 1rem: this page
     scales the root to about 15.7px, and iOS zooms the whole viewport in on
     any input it focuses below 16 — which then leaves the reader zoomed into
     a form they only meant to type in. */
  .hero-signup input {
    padding: 1.05rem 0.9rem;
    font-size: 16px;
  }
}

/* ---------- type: the game's face everywhere ---------- */

/* The game sets its UI in ModeNine (26 refs across scripts/ vs 1 for Pixel
   Operator), and body copy here already used it. Terminal and display were the
   two holdouts on PixelOperator8, so point them at the same face. */
:root {
  --font-term: "ModeNine", "VT323", monospace;
  --font-display: "ModeNine", "VT323", monospace;
}
