/* Arzach — landing. One dark register, deliberately: the instrument reads the
   night sky, so the page is one. Palette lifted from the console's tokens
   (warm graphite, cream ink, cardinal accent) with an aged-brass line for the
   instrument itself.

   The page has one moving part: `--scroll`, a 0→1 value written to the root by
   astrolabe.js. Everything atmospheric reads from it. At 0 the sky is crowded
   and the ground glows; at 1 the field has cleared, the ground is flat, and the
   instrument has resolved into a small fixed mark behind the argument. The page
   is about making a thing legible, so it performs that. */

/* --- fonts, self-hosted --------------------------------------------------- *
 * 116 KB for three families, less than one of the screenshots. Served from our
 * own origin: a render-blocking request to a third party is both a slower first
 * paint and, for EU readers, a personal-data question we do not need to have.
 * Variable axes, so one file covers every weight we use.
 */
@font-face {
  font-family: 'Fraunces';
  src: url(fonts/fraunces-latin.woff2) format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Archivo';
  src: url(fonts/archivo-latin.woff2) format('woff2');
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Chivo Mono';
  src: url(fonts/chivomono-latin.woff2) format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  --ground:    #14120F;
  --ground-2:  #1B1916;

  /* The ink ramp is set by measurement against --ground, the way the console's
     is: 15.1 / 10.6 / 7.3 / 5.1 against #14120F. --ink-4 is the floor for
     anything a human reads and clears AA (4.5) on both grounds — it was 3.3
     here, which made the footer and the signup subline unreadable to anyone who
     needs contrast. There is no step below it. */
  --ink:       #EDE6DA;   /* 15.08 */
  --ink-2:     #C9C2B4;   /* 10.56 */
  --ink-3:     #A8A18D;   /*  7.25 */
  --ink-4:     #8A8577;   /*  5.08 — the floor */

  --brass:     #D8C9A3;
  --brass-dim: rgba(216, 201, 163, 0.38);
  --accent:    #F0562E;
  --accent-ink: #16130F;  /* text that sits on a filled accent */
  --ok:        #8FBE5E;
  --bad:       #F0564F;
  --hairline:  rgba(237, 230, 218, 0.10);

  --font-display: 'Fraunces', Georgia, serif;
  --font-sans:    'Archivo', system-ui, sans-serif;
  --font-mono:    'Chivo Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  --scroll: 0;            /* 0 at the top, 1 one viewport down. Set by JS. */
}

* { box-sizing: border-box; margin: 0; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  position: relative;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 15.5px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* --- the atmosphere ------------------------------------------------------- *
 * Four layers under the content, in depth order: the glow the page opens on,
 * the crayon doodles, the far starfield, and the instrument itself.
 */

/* The warm cast behind the first screen. It drains as you descend — by the
   manifesto the ground is flat graphite and the text has nothing behind it. */
.glow {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(120vmax 90vmax at 50% -20%, #241F17 0%, var(--ground) 62%);
  opacity: calc(1 - var(--scroll) * 0.92);
}

.sky {
  position: fixed;
  inset: 0;
  z-index: 2;
  overflow: hidden;
  pointer-events: none;
  opacity: calc(1 - var(--scroll) * 0.85);   /* the field thins as you descend */
}
.star {
  position: absolute;
  border-radius: 50%;
  background: var(--ink);
  /* Parallax: the far field lags the page, so descending past it reads as
     depth rather than a texture pinned to the glass. */
  transform: translateY(calc(var(--scroll) * var(--depth, 1) * -34px));
  animation: twinkle 5s ease-in-out infinite;
}
@keyframes twinkle {
  0%, 100% { opacity: var(--lo, 0.12); }
  50%      { opacity: var(--hi, 0.55); }
}
@media (prefers-reduced-motion: reduce) {
  .star { animation: none; opacity: var(--lo, 0.2); transform: none; }
}

/* The instrument is a fixed, full-viewport canvas: it stays with you the whole
   page instead of scrolling away after the first screen. Where it sits inside
   that canvas is scroll's business, not CSS's. */
.instrument {
  position: fixed;
  inset: 0;
  z-index: 3;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: pan-y;
  cursor: grab;
}
.instrument.grabbing { cursor: grabbing; }

/* --- crayon stars --------------------------------------------------------- *
 * Doodled stars scattered the whole way down the page, the way you'd fill a
 * margin while thinking. Each one is a wobbling path pushed through a noise
 * displacement, which is what makes a clean vector line read as wax. The stroke
 * width stays constant no matter the star's size — a crayon tip does not get
 * thicker because you drew bigger.
 *
 * These belong to the page rather than the viewport, so unlike the starfield
 * they scroll with it: they are marginalia, not weather.
 */
.crayon-sky {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
  opacity: calc(1 - var(--scroll) * 0.72);   /* part of the field clearing */
}
.crayon-defs { position: absolute; width: 0; height: 0; }

/* Centred by margin rather than a translate, which leaves `transform` free for
   the spin — so rotation and the twinkle can run as two independent animations
   on two different properties instead of fighting over one. */
.cstar {
  position: absolute;
  overflow: visible;
  margin-left: calc(var(--box) / -2);
  margin-top: calc(var(--box) / -2);
  filter: url(#crayon);
  animation: crayon-spin var(--spin, 30s) linear infinite,
             crayon-twinkle var(--tw, 6s) ease-in-out infinite;
}
/* Half of them turn the other way, so the field never drifts as one sheet. */
.cstar.ccw { animation-direction: reverse, normal; }

.cstar path {
  fill: none;
  stroke: var(--brass);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.cstar.is-accent path { stroke: var(--accent); }

@keyframes crayon-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
@keyframes crayon-twinkle {
  0%, 100% { opacity: calc(var(--op, 0.25) * 0.28); }
  50%      { opacity: calc(var(--op, 0.25) * 1.5); }
}
@media (prefers-reduced-motion: reduce) {
  .cstar { animation: none; opacity: var(--op, 0.25); }
}

/* --- shared --------------------------------------------------------------- */

.mono { font-family: var(--font-mono); font-size: 11.5px; letter-spacing: 0.04em; }
.dim  { color: var(--ink-4); }
.ok   { color: var(--ok); }
.bad  { color: var(--bad); }

/* One focus ring for the whole page, in brass at 11.4:1. The email field used
   to suppress its outline and signal focus with a 1px border tint instead,
   which came to 2.6:1 against the field — below the 3:1 WCAG 1.4.11 asks of a
   focus indicator, and only a 2.05:1 change from the unfocused state. */
:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 3px;
  border-radius: 4px;
}

/* --- the button ----------------------------------------------------------- *
 * One, at the foot of the page. The hero asks for nothing: like the site this
 * borrows its shape from, it states what the company is and lets you scroll.
 */
.btn {
  display: inline-flex;
  align-items: center;
  padding: 13px 24px;
  font: 500 14px var(--font-sans);
  color: var(--accent-ink);
  background: var(--accent);
  border: none;
  border-radius: 8px;
  text-decoration: none;
  cursor: pointer;
  transition: transform 160ms var(--ease), filter 160ms var(--ease);
}
.btn:hover  { filter: brightness(1.08); }
.btn:active { transform: scale(0.97); }

/* --- corner nav ----------------------------------------------------------- */

/* Offset by the link's own padding so the wordmark sits where it always did.
   The padding is the point: at 55×20 this was the page's only navigation and an
   unhittable tap target (WCAG 2.5.8 asks 24×24). It is 67×44 now. */
.corner {
  position: fixed;
  top: 10px; right: 20px;
  z-index: 10;
  display: flex; gap: 10px;
}
.corner a {
  padding: 12px 6px;
  /* The page's own reading face, not the mono used for data. Sans uppercase
     sets tighter and smaller on the eye than mono at the same size, so it
     takes a point more size and a touch less tracking to sit at the weight
     the mono did. */
  font-family: var(--font-sans);
  font-size: 12.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-decoration: none;
  transition: color 160ms var(--ease);
}
.corner a:hover { color: var(--ink); }

/* --- content over the atmosphere ------------------------------------------ *
 * The content sits above the fixed canvas, so by default it would swallow every
 * pointer event before the instrument saw one. It passes them through, and the
 * blocks that need a cursor take theirs back — which means the whole empty top
 * of the page is grabbable.
 */
.content {
  position: relative;
  z-index: 4;
  pointer-events: none;
}
.manifesto, .shots, .signup, footer { pointer-events: auto; }

/* --- the first screen ----------------------------------------------------- *
 * Pushed to 66svh so the opening screen is instrument and sky, with the name
 * and the claim arriving at the bottom edge — you meet the object first and the
 * sentence second.
 */
.overlay {
  /* Padding, not margin. A top margin here collapses out through .content and
     body, which drags body's own box 66svh down the page and takes the
     absolutely-positioned crayon layer with it — the doodles then measure
     themselves against text that is 594px from where they think it is. */
  margin: 0 auto 18vh;
  /* 66svh is the composition; the second term is the floor that keeps it one.
     Reserve enough for the name, the claim and the button and the hero can't
     push its own point off the bottom of a short laptop. One expression
     instead of a stack of height breakpoints. */
  padding: min(72svh, calc(100svh - 230px)) 24px 0;
  max-width: 40em;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.overlay h1 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(46px, 7vw, 84px);
  letter-spacing: -0.018em;
  line-height: 1.02;
}

.tagline {
  margin-top: 16px;
  font-size: clamp(15px, 1.6vw, 18px);
  color: var(--ink-2);
  text-wrap: balance;
}
.tagline em { font-style: italic; color: var(--accent); }

/* --- the gate ------------------------------------------------------------- *
 * The one word on the page you can push. The site this borrows its shape from
 * puts its own interaction inside the sentence rather than beside it — the
 * word "stealth" is the button — and that is the right instinct: a claim you
 * can operate is a different kind of claim.
 *
 * It runs the gate. Pending in mono and dim, verdict in sage, then back. Sage
 * and not the accent, because rendering a pass in cardinal makes every good
 * outcome read as an alarm. Nothing moves for longer than 300ms, and the box
 * holds its own width through all three states so a two-line centred tagline
 * does not reflow under the reader.
 */
.gate {
  /* All three states are four characters wide — gate / run… / pass — so the
     box that holds them is barely wider than the word it replaces and the
     sentence does not open a gap around it. Reserving room for "passed"
     instead cost 1.4em of air on both sides, which read as a typo. */
  display: inline-block;
  min-width: 2.3em;
  padding: 0;
  font: inherit;
  /* `font: inherit` resets line-height to the tagline's 1.7, which the button
     would otherwise apply to its own box and push the second line down. */
  line-height: inherit;
  vertical-align: baseline;
  font-style: italic;
  color: var(--accent);
  background: none;
  border: none;
  border-bottom: 1px dashed var(--brass-dim);
  cursor: pointer;
  text-align: center;
  transition: color 160ms var(--ease), border-color 160ms var(--ease);
}
.gate:hover { border-bottom-color: var(--accent); }
.gate .gate-word { display: inline-block; transition: opacity 140ms var(--ease); }
.gate[data-state="running"] {
  color: var(--ink-3);
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 0.82em;
  border-bottom-color: transparent;
}
.gate[data-state="passed"] {
  color: var(--ok);
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 0.82em;
  border-bottom-color: transparent;
}
@media (prefers-reduced-motion: reduce) {
  .gate, .gate .gate-word { transition: none; }
}

/* --- the manifesto -------------------------------------------------------- *
 * One argument, but read at two depths. Every paragraph opens with its own
 * thesis in bold, so the six leads alone carry the page for anyone who skims
 * and the prose is still continuous for anyone who does not. The bold used to
 * fall at word 35, 56 and 64 — a punchline. A punchline reads well and skims
 * to nothing.
 *
 * Left-aligned in a centred measure of ~34em, which is where the eye stops
 * hunting for the next line. Broken once, by the console captures, so the
 * proof arrives 131 words in rather than 514.
 */
main { display: flex; flex-direction: column; align-items: center; }

.manifesto {
  width: min(34em, 100% - 48px);
  padding: 2vh 0 4vh;
  text-align: left;
}
/* The second block opens straight out of the screenshots it follows and needs
   no lead-in of its own; the first has the hero's 18vh above it already. */
.manifesto + .shots { padding-top: 2vh; }
.shots + .manifesto { padding-top: 5vh; }
.manifesto p {
  margin-bottom: 1.5em;
  font-size: clamp(15.5px, 1.35vw, 17.5px);
  color: var(--ink-2);
}
.manifesto p:last-child { margin-bottom: 0; }
.manifesto strong { color: var(--ink); font-weight: 500; }
.manifesto a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.manifesto a:hover { color: var(--ink); }

/* The reference is a target, not decoration. A superscript is 13x19, which is
   a fiddly thing to hit even where WCAG 2.5.8 exempts it for being inline;
   the pseudo-element grows the hit area to about 25x35 without moving a
   pixel of the sentence it sits in. */
.fnref { position: relative; text-decoration: none; padding: 0 3px; }
.fnref::after {
  content: "";
  position: absolute;
  left: -6px; right: -6px; top: -9px; bottom: -9px;
}
.fnref sup { font-size: 0.72em; line-height: 0; }
.fnref:hover { text-decoration: underline; }

/* The measured numbers live here now. A compression ratio is evidence for the
   sentence it supports, not a headline in its own right — as a three-up stat
   row it read as trophies, which is the register the rest of the page avoids. */
.footnote-text {
  margin-top: 3rem;
  padding-top: 1.2rem;
  border-top: 1px solid var(--hairline);
  font-size: clamp(13px, 1.1vw, 14px) !important;
  color: var(--ink-3) !important;
  font-variant-numeric: tabular-nums;
}
.footnote-text sup { font-size: 0.8em; }

/* --- screenshots ---------------------------------------------------------- *
 * One per row at full measure. As a three-up grid these were 377px wide — a
 * 2880px console capture at 13%, where the caption did all the work and the
 * image was texture. At full width they are legible, which is the only reason
 * to show a screenshot at all.
 */
.shots {
  width: min(1080px, 92vw);
  padding: 4vh 0 2vh;
  display: flex;
  flex-direction: column;
  gap: 5vh;
}
.shots figure {
  border: 1px solid var(--hairline);
  border-radius: 10px;
  overflow: hidden;
  background: var(--ground-2);
  transition: border-color 300ms var(--ease);
}
.shots figure:hover { border-color: rgba(237, 230, 218, 0.22); }
.shots picture { display: block; }
.shots img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 8 / 5;
  object-fit: cover;
  object-position: center top;
}
.shots figcaption {
  padding: 12px 16px;
  color: var(--ink-3);
  border-top: 1px solid var(--hairline);
}

/* --- signup --------------------------------------------------------------- */

.signup {
  padding: 12vh 24px 6vh;
  text-align: center;
}
.signup h2 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(26px, 4vw, 34px);
  letter-spacing: -0.01em;
}
#signup-form {
  /* Was 26px under a subline that is gone; the heading needs the room the
     subline used to occupy or it sits on top of the field. */
  margin-top: 34px;
  display: inline-flex;
  gap: 10px;
}
#signup-form[hidden] { display: none; }
#signup-form input {
  width: min(320px, 60vw);
  padding: 12px 16px;
  font: 14px var(--font-mono);
  color: var(--ink);
  background: var(--ground-2);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  transition: border-color 160ms var(--ease);
}
#signup-form input:focus { border-color: var(--brass-dim); }
#signup-form input::placeholder { color: var(--ink-4); }

#signup-form button[disabled] { opacity: 0.6; cursor: default; }

/* The honeypot. Off-screen rather than display:none, because some bots skip
   fields they can tell are hidden. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}

#signup-done, #signup-fail { margin-top: 18px; }
#signup-fail a { color: inherit; }

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

footer {
  display: flex;
  justify-content: space-between;
  padding: 26px 30px;
  border-top: 1px solid var(--hairline);
  color: var(--ink-4);
}

/* --- narrower ------------------------------------------------------------- */

@media (max-width: 900px) {
  .overlay { margin-bottom: 12vh; }
  .shots { gap: 3.5vh; }
}

/* The fixed corner link overlapped the reading column on every viewport below
   721px, and it is transparent, so CONTACT sat printed across a sentence for
   the whole scroll. The measure is already at its floor (100% - 48px), so the
   column cannot give the width back.
   Below the crossover the link stops being fixed and scrolls away with the
   hero instead. Nothing is lost: the address is in the footnote, and the form
   it would have carried the reader to is on the same page. */
@media (max-width: 760px) {
  .corner { position: absolute; }
}

@media (max-width: 560px) {
  .corner { right: 12px; }
  /* Stacked, the field had 41px of air above it (to the heading's ink) and 10
     below, so it sat on its own button. Equal now, and the pair is tightened
     rather than pushed apart: a submit button 41px from its field stops
     reading as belonging to it. The 7px is the heading's descender space —
     the margin is measured to a box, the eye measures to the ink. */
  #signup-form {
    flex-direction: column;
    align-items: center;
    margin-top: 20px;
    gap: 27px;
  }
  #signup-form input { width: min(320px, 78vw); }
  .btn { justify-content: center; }
  footer { flex-direction: column; gap: 4px; text-align: center; }
}
