/* ==========================================================================
   QWITH LLC — main.css
   A text-mode website. Everything is drawn with characters: borders are
   dashed gradients that read as `- - -`, corners are literal `+` glyphs,
   headings are block-font ASCII, imagery is ASCII-converted.

   Contents
     01  tokens
     02  reset + base
     03  CRT chrome (scanlines / vignette / flicker)
     04  layout primitives
     05  ascii boxes
     06  type + block headings
     07  buttons
     08  header, nav, scroll readout
     09  breadcrumbs
     10  hero
     11  marquee + stats
     12  scrollytelling
     13  cards, case studies, reviews
     14  faq
     15  forms
     16  callouts (response-time promise)
     17  sticky mobile cta
     18  footer
     19  utilities + reveal
     20  reduced motion / print
   ========================================================================== */

/* ---- 01 tokens ---------------------------------------------------------- */
:root {
  --bg: #000000;
  --bg-lift: #050807;
  --bg-panel: #030504;

  /* Every foreground token clears WCAG AA against --bg; decorative-only
     tokens clear 3:1. Verified by tools/check_contrast.py — run it after
     touching any of these. */
  --fg: #b9ffd6;      /* 18.3:1 */
  --fg-soft: #7fbb9c; /*  9.5:1 */
  --fg-dim: #529073;  /*  5.0:1 — labels, hints, secondary copy */
  --fg-faint: #497f68; /* 3.8:1 — decorative meta and placeholders only */

  --accent: #00ff9c;
  --amber: #ffb000;
  --cyan: #4de2ff;
  --danger: #ff2e6c;

  --line: #1f6b4d;
  --line-soft: #123a2a;

  --mono: ui-monospace, "SF Mono", SFMono-Regular, "Cascadia Mono", Consolas,
    "Liberation Mono", Menlo, monospace;

  --step--1: clamp(0.72rem, 0.68rem + 0.2vw, 0.82rem);
  --step-0: clamp(0.86rem, 0.8rem + 0.28vw, 1rem);
  --step-1: clamp(1.02rem, 0.93rem + 0.44vw, 1.28rem);
  --step-2: clamp(1.25rem, 1.06rem + 0.9vw, 1.9rem);
  --step-3: clamp(1.6rem, 1.2rem + 1.9vw, 2.9rem);

  --gut: clamp(1rem, 3.5vw, 2.5rem);
  --sect: clamp(4rem, 11vh, 8.5rem);
  --measure: 68ch;
  --wrap: 78rem;

  --ease: cubic-bezier(0.2, 0.7, 0.25, 1);
  --dash: 6px;
  --gap: 5px;
}

/* ---- 02 reset + base ---------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  /* header + breathing room, so anchor targets are never tucked under the bar */
  scroll-padding-top: 6.5rem;
  -webkit-text-size-adjust: 100%;
  /* The page background lives here, not on <body>. That leaves <body>
     transparent so .matrix can sit at z-index:-1 and still be visible. */
  background: var(--bg);
}

body {
  margin: 0;
  background: transparent;
  color: var(--fg-soft);
  font-family: var(--mono);
  font-size: var(--step-0);
  line-height: 1.6;
  font-variant-ligatures: none;
  overflow-x: hidden;
  text-rendering: optimizeLegibility;
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

a {
  color: var(--accent);
  text-decoration: none;
  text-underline-offset: 0.25em;
}

a:hover,
a:focus-visible {
  text-decoration: underline;
  text-decoration-style: dashed;
}

:focus-visible {
  outline: 2px dashed var(--amber);
  outline-offset: 3px;
}

::selection {
  background: var(--accent);
  color: #000;
}

hr {
  border: 0;
  height: 1px;
  background: repeating-linear-gradient(
    90deg,
    var(--line) 0 var(--dash),
    transparent var(--dash) calc(var(--dash) + var(--gap))
  );
  margin: 2.5rem 0;
}

/* thin phosphor scrollbar */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line) #000;
}
*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
*::-webkit-scrollbar-track {
  background: #000;
}
*::-webkit-scrollbar-thumb {
  background: var(--line);
  border: 2px solid #000;
}
*::-webkit-scrollbar-thumb:hover {
  background: var(--accent);
}

.skip-link {
  position: absolute;
  left: 0.5rem;
  top: -4rem;
  z-index: 200;
  padding: 0.6rem 1rem;
  background: #000;
  border: 1px solid var(--accent);
  color: var(--accent);
  transition: top 0.15s var(--ease);
}
.skip-link:focus {
  top: 0.5rem;
}

/* ---- 03 CRT chrome ------------------------------------------------------ */
/* Two fixed overlays: horizontal scanlines + a vignette/bloom. Both are
   pointer-events:none so they never intercept a click. */
.crt-lines,
.crt-glow {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 90;
}

.crt-lines {
  background: repeating-linear-gradient(
    180deg,
    rgba(0, 0, 0, 0) 0 2px,
    rgba(0, 0, 0, 0.42) 2px 4px
  );
  mix-blend-mode: multiply;
  opacity: 0.75;
}

.crt-glow {
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(0, 255, 156, 0.05), transparent 60%),
    radial-gradient(140% 110% at 50% 50%, transparent 55%, rgba(0, 0, 0, 0.85) 100%);
}

/* a slow rolling refresh band — subtle, ~9s */
.crt-glow::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  height: 22vh;
  background: linear-gradient(180deg, transparent, rgba(0, 255, 156, 0.035), transparent);
  animation: roll 9s linear infinite;
}

@keyframes roll {
  from {
    transform: translateY(-30vh);
  }
  to {
    transform: translateY(130vh);
  }
}

/* ---- 04 layout primitives ---------------------------------------------- */
.wrap {
  width: min(100% - var(--gut) * 2, var(--wrap));
  margin-inline: auto;
}

.wrap--narrow {
  width: min(100% - var(--gut) * 2, 58rem);
  margin-inline: auto;
}

.section {
  padding-block: var(--sect);
  position: relative;
}

.stack > * + * {
  margin-top: var(--stack-space, 1rem);
}

.grid {
  display: grid;
  gap: clamp(1rem, 2.4vw, 1.75rem);
}
.grid--2 {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
}
.grid--3 {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
}

.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  align-items: center;
}

/* ---- 05 ascii boxes ----------------------------------------------------- */
/* Border is four repeating-gradients so it renders as a dashed rule rather
   than a solid CSS stroke; corners are injected as real `+` characters by
   main.js (progressive enhancement — the dashes stand alone without JS). */
.box {
  position: relative;
  padding: clamp(1.1rem, 2.6vw, 1.9rem);
  background-color: var(--bg-panel);
  background-image:
    repeating-linear-gradient(90deg, var(--line) 0 var(--dash), transparent var(--dash) calc(var(--dash) + var(--gap))),
    repeating-linear-gradient(90deg, var(--line) 0 var(--dash), transparent var(--dash) calc(var(--dash) + var(--gap))),
    repeating-linear-gradient(180deg, var(--line) 0 var(--dash), transparent var(--dash) calc(var(--dash) + var(--gap))),
    repeating-linear-gradient(180deg, var(--line) 0 var(--dash), transparent var(--dash) calc(var(--dash) + var(--gap)));
  background-size: 100% 1px, 100% 1px, 1px 100%, 1px 100%;
  background-position: 0 0, 0 100%, 0 0, 100% 0;
  background-repeat: no-repeat;
  transition: background-color 0.25s var(--ease), transform 0.25s var(--ease);
}

.box__c {
  position: absolute;
  color: var(--line);
  font-size: var(--step-0);
  line-height: 1;
  transition: color 0.25s var(--ease);
  user-select: none;
}
.box__c--tl { top: -0.52em; left: -0.28em; }
.box__c--tr { top: -0.52em; right: -0.28em; }
.box__c--bl { bottom: -0.52em; left: -0.28em; }
.box__c--br { bottom: -0.52em; right: -0.28em; }

/* legend that sits astride the top rule, like a fieldset caption */
.box__label {
  position: absolute;
  top: 0;
  left: 1.6rem;
  transform: translateY(-50%);
  background: var(--bg);
  padding-inline: 0.6rem;
  font-size: var(--step--1);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-dim);
  white-space: nowrap;
  max-width: calc(100% - 3.2rem);
  overflow: hidden;
  text-overflow: ellipsis;
}

.box--lit {
  --line: var(--accent);
}

.box--hover:hover,
.box--hover:focus-within {
  --line: var(--accent);
  background-color: var(--bg-lift);
  transform: translateY(-3px);
}
.box--hover:hover .box__c,
.box--hover:focus-within .box__c {
  color: var(--accent);
}

/* ---- 06 type + block headings ------------------------------------------ */
h1, h2, h3, h4 {
  color: var(--fg);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.01em;
  margin: 0 0 0.6em;
  text-wrap: balance;
}

h1 { font-size: var(--step-3); }
h2 { font-size: var(--step-2); }
h3 { font-size: var(--step-1); }
h4 { font-size: var(--step-0); }

p {
  margin: 0 0 1em;
  max-width: var(--measure);
}

strong { color: var(--fg); }
em { color: var(--amber); font-style: normal; }

code, kbd {
  color: var(--amber);
  background: #071009;
  padding: 0.1em 0.4em;
  border: 1px solid var(--line-soft);
  font-size: 0.92em;
}

.lede {
  font-size: var(--step-1);
  color: var(--fg);
  max-width: 56ch;
}

/* section eyebrow: `── 03 // PROCESS ──────────` */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  font-size: var(--step--1);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 1.4rem;
}
.eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: repeating-linear-gradient(
    90deg,
    var(--line) 0 var(--dash),
    transparent var(--dash) calc(var(--dash) + var(--gap))
  );
}

.prompt::before {
  content: "> ";
  color: var(--accent);
}

/* Block-font heading. The real text stays in the DOM for screen readers and
   crawlers; main.js paints the ASCII art into the <pre> beside it. */
.ascii-h {
  margin-bottom: 1rem;
}
.ascii-h__art {
  margin: 0;
  font-family: var(--mono);
  font-size: clamp(0.22rem, 1.02vw, 0.62rem);
  line-height: 1.02;
  color: var(--accent);
  white-space: pre;
  overflow: hidden;
  text-shadow: 0 0 12px rgba(0, 255, 156, 0.45);
}
.ascii-h__art:empty {
  display: none;
}

/* Short words (404, SENT) need a bigger cell to carry a hero on their own. */
.ascii-h--xl .ascii-h__art {
  font-size: clamp(0.34rem, 1.9vw, 1.15rem);
}
/* The real heading text is hidden only once JS has actually painted the art,
   so with JS off you still get a normal, readable <h1>. */
.ascii-h.is-painted .ascii-h__text {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

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

/* blinking block cursor */
.caret::after {
  content: "\2588";
  color: var(--accent);
  animation: blink 1.05s steps(1) infinite;
  margin-left: 0.12em;
}
@keyframes blink {
  50% { opacity: 0; }
}

/* ---- 07 buttons --------------------------------------------------------- */
.btn {
  --btn-fg: var(--accent);
  display: inline-flex;
  align-items: center;
  gap: 0.6ch;
  padding: 0.72em 1.35em;
  border: 1px solid var(--btn-fg);
  color: var(--btn-fg);
  background: transparent;
  font: inherit;
  font-size: var(--step-0);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  position: relative;
  transition: color 0.18s var(--ease), background-color 0.18s var(--ease),
    box-shadow 0.18s var(--ease), transform 0.12s var(--ease);
}
.btn::before { content: "["; opacity: 0.55; }
.btn::after  { content: "]"; opacity: 0.55; }

.btn:hover,
.btn:focus-visible {
  background: var(--btn-fg);
  color: #000;
  text-decoration: none;
  box-shadow: 0 0 0 1px var(--btn-fg), 0 0 22px -4px var(--btn-fg);
}
.btn:active {
  transform: translateY(1px);
}

.btn--primary {
  --btn-fg: var(--accent);
  font-weight: 700;
}
.btn--amber { --btn-fg: var(--amber); }
.btn--ghost {
  --btn-fg: var(--fg-dim);
  color: var(--fg-soft);
}
.btn--ghost:hover { --btn-fg: var(--fg); }
.btn--lg { padding: 0.9em 1.7em; font-size: var(--step-1); }
.btn--block { width: 100%; justify-content: center; }

/* ---- 08 header ---------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(0, 0, 0, 0.92);
  backdrop-filter: blur(6px);
  border-bottom: 1px solid var(--line-soft);
}

.site-header__bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  justify-content: space-between;
  padding-block: 0.7rem;
  flex-wrap: wrap;
}

.brand {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5ch;
  color: var(--fg);
  font-weight: 700;
  letter-spacing: 0.06em;
  white-space: nowrap;
}
.brand:hover { text-decoration: none; color: var(--accent); }
.brand__sigil { color: var(--accent); }
.brand__dim { color: var(--fg-dim); font-weight: 400; }

.nav {
  display: flex;
  gap: 0.25rem 1rem;
  flex-wrap: wrap;
  align-items: center;
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.nav a {
  color: var(--fg-soft);
  padding: 0.35em 0.1em;
  position: relative;
  white-space: nowrap;
}
.nav a::before {
  content: "";
  color: var(--accent);
  margin-right: 0;
  transition: margin-right 0.15s var(--ease);
}
.nav a:hover,
.nav a:focus-visible,
.nav a[aria-current="page"] {
  color: var(--accent);
  text-decoration: none;
}
.nav a:hover::before,
.nav a:focus-visible::before,
.nav a[aria-current="page"]::before {
  content: ">";
  margin-right: 0.5ch;
}
.nav__key {
  color: var(--fg-faint);
  font-size: 0.9em;
}

/* scroll readout: `[####------] 41%` */
.readout {
  font-size: var(--step--1);
  color: var(--fg-dim);
  letter-spacing: 0.05em;
  white-space: nowrap;
  display: none;
}
.readout__bar { color: var(--accent); }

@media (min-width: 60rem) {
  .readout { display: inline-block; }
}

/* ---- 09 breadcrumbs ----------------------------------------------------- */
.crumbs {
  font-size: var(--step--1);
  color: var(--fg-dim);
  padding-block: 1.4rem 0;
  letter-spacing: 0.04em;
}
.crumbs ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5ch;
  margin: 0;
  padding: 0;
}
.crumbs li::after {
  content: "/";
  color: var(--fg-faint);
  margin-inline: 0.5ch;
}
.crumbs li:last-child::after { content: ""; }
.crumbs a { color: var(--fg-soft); }
.crumbs [aria-current="page"] { color: var(--amber); }

/* ---- 10 hero ------------------------------------------------------------ */
.hero {
  position: relative;
  padding-block: clamp(2.5rem, 7vh, 5rem) var(--sect);
  overflow: hidden;
}

.hero__grid {
  display: grid;
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: center;
}
@media (min-width: 62rem) {
  .hero__grid {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  }
}

.hero__meta {
  font-size: var(--step--1);
  color: var(--fg-dim);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin-bottom: 1.2rem;
}

.hero__actions {
  margin-top: 1.9rem;
}

/* the rotating ASCII torus */
.torus {
  margin: 0;
  color: var(--accent);
  font-size: clamp(0.34rem, 1.55vw, 1rem);
  line-height: 1;
  white-space: pre;
  text-align: center;
  filter: drop-shadow(0 0 14px rgba(0, 255, 156, 0.35));
  /* NOT `contain: strict`. Size containment made the box resolve to
     min-height while the 28 rows of glyphs needed far more, and paint
     containment then sliced the bottom off the shape — 99px at 1600px,
     569px at 4K. Layout/style containment keeps the perf isolation without
     lying about the height. */
  contain: layout style;
  display: grid;
  place-items: center;
}

@media (min-width: 62rem) {
  /* Two-column hero. The rendered shape spans ~59 of the 64 grid columns, so
     ~1vw keeps it inside its half of the wrap at every width from 1000px to
     4K; the rem cap takes over once --wrap stops growing. */
  .torus { font-size: min(0.95vw, 2rem); }
}

.hero__viz {
  position: relative;
}
/* ---- 11 marquee + stats ------------------------------------------------- */
.marquee {
  border-block: 1px solid var(--line-soft);
  overflow: hidden;
  padding-block: 0.7rem;
  background: #020403;
  /* above .rail (80) so the rails pass behind it, not over it */
  position: relative;
  z-index: 85;
  transition: opacity 0.45s var(--ease);
}
.marquee__track {
  display: flex;
  gap: 3rem;
  width: max-content;
  animation: slide 38s linear infinite;
  font-size: var(--step--1);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-dim);
  white-space: nowrap;
}
.marquee:hover .marquee__track { animation-play-state: paused; }
.marquee__track span::before { content: "\2022  "; color: var(--accent); }
@keyframes slide {
  to { transform: translateX(-50%); }
}

.stats {
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
  background: var(--line-soft);
  border: 1px solid var(--line-soft);
}
.stat {
  background: var(--bg);
  padding: 1.4rem 1.2rem;
}
.stat__num {
  display: block;
  font-size: var(--step-2);
  color: var(--accent);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.stat__label {
  display: block;
  margin-top: 0.5rem;
  font-size: var(--step--1);
  color: var(--fg-dim);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* ---- 12 scrollytelling -------------------------------------------------- */
.scrolly {
  display: grid;
  gap: clamp(1.5rem, 4vw, 3.5rem);
}
@media (min-width: 62rem) {
  .scrolly {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
  }
}

.scrolly__viz {
  position: sticky;
  top: 5.5rem;
  z-index: 1;
}
@media (min-width: 62rem) {
  .scrolly__viz { top: 7rem; }
}

.scrolly__frame {
  margin: 0;
  white-space: pre;
  overflow-x: auto;
  /* 3vw fills the panel on a phone; the two-column override below takes over
     once there is a column to fit. 42 characters wide either way. */
  font-size: clamp(0.6rem, 3vw, 1.02rem);
  line-height: 1.25;
  color: var(--accent);
  min-height: 15rem;
  transition: opacity 0.28s var(--ease);
}
.scrolly__frame.is-swapping { opacity: 0.12; }

@media (min-width: 62rem) {
  /* 42 characters inside a padded box that is half the wrap: ~1.24vw tracks
     that from 1000px to 4K, with the rem cap for beyond. Must sit after the
     base rule above — media queries add no specificity, so source order is
     what decides this. */
  .scrolly__frame { font-size: min(1.24vw, 2.6rem); }
}

.scrolly__rail {
  display: flex;
  gap: 0.4ch;
  margin-top: 1rem;
  font-size: var(--step--1);
  color: var(--fg-faint);
  letter-spacing: 0.1em;
}
.scrolly__rail b {
  font-weight: 400;
  transition: color 0.2s var(--ease);
}
.scrolly__rail b.is-on { color: var(--accent); }

.step {
  /* A step wants roughly a screen of dwell, but on a 2160px display 72vh is
     1555px of mostly empty column. Cap it so tall screens read as spacious
     rather than abandoned. */
  min-height: min(72vh, 46rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: 2rem;
  opacity: 0.32;
  transition: opacity 0.35s var(--ease);
  border-left: 1px solid transparent;
  padding-left: 1.4rem;
}
.step.is-active {
  opacity: 1;
  border-left-color: var(--accent);
}
.step__idx {
  font-size: var(--step--1);
  color: var(--amber);
  letter-spacing: 0.24em;
  margin-bottom: 0.7rem;
}

@media (max-width: 61.99rem) {
  .step { min-height: auto; padding-block: 1.6rem; opacity: 1; }
  .scrolly__viz { position: static; }
  .scrolly__frame { min-height: 10rem; }
}

/* ---- 13 cards / case studies / reviews ---------------------------------- */
.card__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin-bottom: 0.6rem;
}
.card__idx {
  color: var(--fg-faint);
  font-size: var(--step--1);
}
.card h3 { margin-bottom: 0.4rem; }
.card p { font-size: var(--step-0); }
.card__more {
  display: inline-block;
  margin-top: 0.4rem;
  font-size: var(--step--1);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
/* whole-card click target without nesting interactive elements */
.card--link { position: relative; }
.card--link .stretch::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* No image-rendering:pixelated here — these are downscaled from 832px, and
   nearest-neighbour on a downscale aliases the character grid into moire. */
.case-thumb {
  border: 1px solid var(--line-soft);
  background: #000;
  margin-bottom: 1rem;
}

/* The portrait is 576px wide; letting it stretch past that only softens the
   characters, so cap it at its native size and centre it. */
.portrait {
  max-width: min(100%, 576px);
  margin-inline: auto;
  image-rendering: pixelated;
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  list-style: none;
  padding: 0;
  margin: 0.9rem 0 0;
  font-size: var(--step--1);
}
.tags li {
  border: 1px solid var(--line-soft);
  padding: 0.15em 0.6em;
  color: var(--fg-dim);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.metrics {
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
  background: var(--line-soft);
  border: 1px solid var(--line-soft);
  margin-block: 1.5rem;
}
.metric {
  background: #000;
  padding: 1rem;
}
.metric b {
  display: block;
  font-size: var(--step-1);
  color: var(--amber);
  font-variant-numeric: tabular-nums;
}
.metric span {
  font-size: var(--step--1);
  color: var(--fg-dim);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.review {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.review blockquote {
  margin: 0 0 1.2rem;
  color: var(--fg);
}
.review blockquote::before { content: "\201C"; color: var(--accent); }
.review blockquote::after  { content: "\201D"; color: var(--accent); }
.review__foot {
  display: flex;
  gap: 0.9rem;
  align-items: center;
  margin-top: auto;
}
.review__avatar {
  /* big enough that the individual characters still read as characters */
  width: 78px;
  height: 78px;
  flex: none;
  border: 1px solid var(--line-soft);
  object-fit: cover;
}
.review__who { font-size: var(--step--1); line-height: 1.45; }
.review__who b { color: var(--fg); display: block; letter-spacing: 0.04em; }
.review__who span { color: var(--fg-dim); }
.stars {
  color: var(--amber);
  letter-spacing: 0.2em;
  font-size: var(--step--1);
  margin-bottom: 0.7rem;
}

/* ---- 14 faq ------------------------------------------------------------- */
.faq { border-top: 1px solid var(--line-soft); }
.faq details {
  border-bottom: 1px solid var(--line-soft);
}
.faq summary {
  cursor: pointer;
  list-style: none;
  padding: 1.15rem 0.2rem;
  display: flex;
  gap: 1ch;
  align-items: flex-start;
  color: var(--fg);
  font-size: var(--step-1);
  transition: color 0.18s var(--ease);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::before {
  content: "[+]";
  color: var(--accent);
  flex: none;
  font-size: var(--step-0);
  line-height: 1.5;
}
.faq details[open] summary::before { content: "[-]"; }
.faq summary:hover { color: var(--accent); }
.faq__body {
  padding: 0 0 1.35rem 4ch;
  color: var(--fg-soft);
}
.faq__body p:last-child { margin-bottom: 0; }

/* ---- 15 forms ----------------------------------------------------------- */
.field { margin-bottom: 1.35rem; }
.field label {
  display: block;
  font-size: var(--step--1);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-bottom: 0.45rem;
}
.field label .req { color: var(--amber); }

.field input,
.field textarea,
.field select {
  width: 100%;
  background: #010302;
  border: 1px solid var(--line);
  color: var(--fg);
  font: inherit;
  padding: 0.7em 0.9em;
  transition: border-color 0.18s var(--ease), box-shadow 0.18s var(--ease);
}
.field textarea { resize: vertical; min-height: 9rem; }
.field input::placeholder,
.field textarea::placeholder { color: var(--fg-faint); }
.field input:focus,
.field textarea:focus,
.field select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), 0 0 18px -6px var(--accent);
}
.field__hint {
  font-size: var(--step--1);
  color: var(--fg-dim);
  margin-top: 0.35rem;
}
.field__error {
  font-size: var(--step--1);
  color: var(--danger);
  margin-top: 0.35rem;
  min-height: 1.2em;
}
.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] { border-color: var(--danger); }

/* honeypot — hidden from humans, tempting to bots */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-status {
  font-size: var(--step--1);
  color: var(--accent);
  min-height: 1.4em;
  letter-spacing: 0.06em;
}

/* ---- 16 callouts -------------------------------------------------------- */
.promise {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  border: 1px solid var(--amber);
  background: linear-gradient(180deg, rgba(255, 176, 0, 0.06), transparent);
  padding: 1rem 1.2rem;
  color: var(--fg);
  font-size: var(--step-0);
}
.promise__icon {
  color: var(--amber);
  flex: none;
  font-weight: 700;
  letter-spacing: 0.05em;
}
.promise b { color: var(--amber); }
.promise p { margin: 0; max-width: none; }
.promise small { color: var(--fg-dim); display: block; margin-top: 0.3rem; }

.note {
  border-left: 2px solid var(--line);
  padding-left: 1rem;
  color: var(--fg-dim);
  font-size: var(--step--1);
}

/* big terminal CTA band */
.cta-band {
  border: 1px solid var(--accent);
  padding: clamp(1.6rem, 5vw, 3.2rem);
  background:
    radial-gradient(90% 140% at 50% 0%, rgba(0, 255, 156, 0.08), transparent 70%),
    #010302;
  text-align: center;
}
.cta-band h2 { margin-bottom: 0.5rem; }
.cta-band p { margin-inline: auto; }

/* ---- 17 sticky mobile cta ---------------------------------------------- */
.mobile-cta {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 95;
  display: none;
  gap: 0.5rem;
  padding: 0.6rem 0.7rem calc(0.6rem + env(safe-area-inset-bottom));
  background: rgba(0, 0, 0, 0.96);
  border-top: 1px solid var(--accent);
  transform: translateY(105%);
  transition: transform 0.3s var(--ease);
}
.mobile-cta.is-visible { transform: translateY(0); }
.mobile-cta .btn { flex: 1; justify-content: center; padding-block: 0.85em; }
.mobile-cta .btn--ghost { flex: 0 0 auto; }

@media (max-width: 47.99rem) {
  .mobile-cta { display: flex; }
  body { padding-bottom: 4.5rem; }
}

/* ---- 18 footer ---------------------------------------------------------- */
.site-footer {
  border-top: 1px solid var(--line-soft);
  padding-block: 3rem 2rem;
  margin-top: var(--sect);
  font-size: var(--step--1);
}
.site-footer h2 {
  font-size: var(--step--1);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--fg-dim);
  margin-bottom: 0.9rem;
}
.site-footer ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 0.4rem;
}
.site-footer a { color: var(--fg-soft); }
.site-footer a:hover { color: var(--accent); }
.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1.5rem;
  justify-content: space-between;
  margin-top: 2.5rem;
  padding-top: 1.2rem;
  border-top: 1px solid var(--line-soft);
  color: var(--fg-dim);
}
.footer__sig {
  margin: 2rem 0 0;
  color: var(--fg-faint);
  font-size: clamp(0.3rem, 0.85vw, 0.6rem);
  line-height: 1.1;
  white-space: pre;
  overflow: hidden;
}

/* ---- 19 utilities + reveal --------------------------------------------- */
.u-accent { color: var(--accent); }
.u-amber  { color: var(--amber); }
.u-dim    { color: var(--fg-dim); }
.u-center { text-align: center; }
.u-mt-0   { margin-top: 0; }
.u-mb-0   { margin-bottom: 0; }
.u-mt-2   { margin-top: 2rem; }
.u-nowrap { white-space: nowrap; }
.u-measure { max-width: var(--measure); }
.u-mx-auto { margin-inline: auto; }

[data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}
[data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* decorative ascii blocks */
.ascii-art {
  margin: 0;
  white-space: pre;
  overflow-x: auto;
  line-height: 1.15;
  color: var(--fg-dim);
  font-size: clamp(0.42rem, 1.25vw, 0.75rem);
}
.ascii-art--accent { color: var(--accent); }

/* boot overlay */
.boot {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: #000;
  padding: 8vh 6vw;
  color: var(--accent);
  font-size: var(--step--1);
  line-height: 1.7;
  white-space: pre-wrap;
  transition: opacity 0.32s var(--ease);
}
.boot.is-done { opacity: 0; pointer-events: none; }

/* ---- 21 character rain (site-wide background) --------------------------
   One fixed layer behind every page. It sits at z-index:-1 over the <html>
   background, so no content needs a stacking context of its own.

   The mask is doing real work: it holds the rain back from the middle of the
   viewport, where the reading column is, and lets it run at full strength
   down the empty margins. That keeps body-copy contrast untouched while
   filling the sides.
   ------------------------------------------------------------------------ */
.matrix {
  position: fixed;
  inset: 0;
  z-index: -1;
  margin: 0;
  overflow: hidden;
  white-space: pre;
  font-family: var(--mono);
  font-size: 0.82rem;
  line-height: 1.18;
  /* Dimmed via the colour itself, not opacity — the ::after scrim below has
     to be able to paint at full strength to clear the centre. */
  color: #0b2418;
  user-select: none;
  pointer-events: none;
  contain: layout paint style;
  will-change: contents;
}

/* Clear behind the content column, full strength at the margins.
   The solid part of this gradient reaches ~50%±36%, which is just wider than
   the reading column, so no character ever falls behind body copy. */
.matrix::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
    ellipse 48% 80% at 50% 50%,
    var(--bg) 0%,
    var(--bg) 74%,
    transparent 100%
  );
}

@media (max-width: 83.99rem) {
  /* Below the rail breakpoint the margins are thin, so there is little room
     for the effect and a lot of risk of it crowding the text. */
  .matrix { color: #081b12; font-size: 0.74rem; }
  .matrix::after {
    background: radial-gradient(
      ellipse 60% 85% at 50% 50%,
      var(--bg) 0%,
      var(--bg) 78%,
      transparent 100%
    );
  }
}

/* ---- 22 side rails ------------------------------------------------------
   Above 84rem the viewport grows wider than the reading column, and those
   margins were dead space. Once you scroll past the hero the primary nav
   stows out of the header and re-materialises down the left margin, with a
   scroll gauge down the right.

   Both rails are built by main.js from the existing header nav, so there is
   no duplicated markup to keep in sync and no-JS visitors simply keep the
   normal top bar.

   Visibility is switched with `visibility`, not opacity alone, so exactly one
   nav is in the tab order and the accessibility tree at any moment.
   ------------------------------------------------------------------------ */
.rail {
  display: none;
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  z-index: 80;
  font-size: var(--step--1);
  line-height: 1.5;
  letter-spacing: 0.06em;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.3s var(--ease), visibility 0s linear 0.3s;
  /* main.js drives this: the rail is clipped to whatever sits below the
     gate bar, so it wipes into view from underneath the marquee rather than
     crossing over it. */
  clip-path: inset(var(--rail-clip, 0px) 0 0 0);
}

.rail.is-on {
  visibility: visible;
  opacity: 1;
  transition: opacity 0.3s var(--ease), visibility 0s;
}

.rail--left {
  left: max(0.75rem, calc(50% - var(--wrap) / 2 - 7rem));
}

.rail--right {
  right: max(0.75rem, calc(50% - var(--wrap) / 2 - 7rem));
  text-align: right;
}

.rail__cap {
  margin: 0 0 0.6rem;
  color: var(--fg-faint);
  text-transform: uppercase;
  letter-spacing: 0.24em;
  font-size: 0.68rem;
}

.rail ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.15rem;
}

.rail a {
  display: block;
  color: var(--fg-dim);
  white-space: pre;
  padding: 0.15em 0;
  text-decoration: none;
  transition: color 0.18s var(--ease);
}

.rail a:hover,
.rail a:focus-visible {
  color: var(--accent);
  text-decoration: none;
}

.rail a[aria-current="page"] { color: var(--accent); }

.rail__key { color: var(--fg-faint); }
.rail a:hover .rail__key,
.rail a[aria-current="page"] .rail__key { color: var(--amber); }

/* right rail: vertical scroll gauge */
.rail__gauge {
  margin: 0;
  white-space: pre;
  line-height: 1.05;
  color: var(--line);
  font-size: 0.72rem;
}
.rail__gauge b { color: var(--accent); font-weight: 400; }

.rail__pct {
  margin: 0.4rem 0 0;
  color: var(--fg-dim);
  font-variant-numeric: tabular-nums;
}

.rail__sec {
  margin: 0.9rem 0 0;
  color: var(--fg-faint);
  white-space: pre-line;
  max-width: 13ch;
  line-height: 1.4;
  margin-left: auto;
  text-transform: uppercase;
  font-size: 0.68rem;
  letter-spacing: 0.14em;
}

/* header nav stows away when the left rail takes over */
.site-header .nav {
  transition: opacity 0.26s var(--ease), transform 0.26s var(--ease),
    visibility 0s linear 0s;
}

.site-header.is-stowed .nav {
  opacity: 0;
  transform: translateY(-8px);
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.26s var(--ease), transform 0.26s var(--ease),
    visibility 0s linear 0.26s;
}

@media (min-width: 84rem) {
  /* Narrow the reading column so the rails have margin to live in, but keep
     it fluid — a fixed 70rem looks like a postage stamp on a 4K display.
     Media-query `rem` always resolves against the initial 16px root, so these
     breakpoints stay put even as the root size grows below. */
  :root { --wrap: clamp(70rem, 74vw, 150rem); }
  .rail { display: block; }
}

/* ---- 23 very large displays --------------------------------------------
   Past ~1920px the layout stops being too narrow and starts being too small:
   rem-based type keeps rendering at a 16px root while the viewport doubles.
   Scaling the root scales the whole system — type, spacing, gutters and the
   --wrap clamp above — in one move.
   ------------------------------------------------------------------------ */
@media (min-width: 120rem) {
  html { font-size: 18px; }
}
@media (min-width: 160rem) {
  html { font-size: 20px; }
}
@media (min-width: 210rem) {
  html { font-size: 22px; }
}

/* ---- 24 hero sequence ---------------------------------------------------
   Scroll-driven, wide screens only, and only when motion is allowed. Three
   overlapping phases, all fed by a single 0..1 progress value that main.js
   writes to --seq:

     A  the copy column corrupts red and drops out
     B  the torus detaches, grows and drifts to the middle of the viewport
     C  it breaks into individual characters, most scattering outward and
        some travelling to where the rails are about to appear

   Every rule here is scoped to .is-sequencing, so with JS off — or on a
   phone, or under prefers-reduced-motion — the hero is an ordinary hero.
   ------------------------------------------------------------------------ */
.hero__copy { position: relative; }

.is-sequencing .hero__copy {
  opacity: calc(1 - var(--seq-copy, 0));
  transform: translateX(calc(var(--seq-copy, 0) * -2.5rem));
  will-change: opacity, transform;
}

/* red corruption: two offset colour ghosts that grow with progress */
.is-sequencing .hero__copy::before,
.is-sequencing .hero__copy::after {
  content: "";
  position: absolute;
  inset: -0.5rem -1rem;
  pointer-events: none;
  opacity: var(--seq-copy, 0);
  mix-blend-mode: screen;
}
.is-sequencing .hero__copy::before {
  background: linear-gradient(
    90deg,
    transparent 0 8%,
    rgba(255, 46, 108, 0.16) 8% 12%,
    transparent 12% 46%,
    rgba(255, 46, 108, 0.22) 46% 49%,
    transparent 49% 78%,
    rgba(255, 46, 108, 0.14) 78% 83%,
    transparent 83%
  );
  animation: bandshift 0.42s steps(3) infinite;
}
.is-sequencing .hero__copy::after {
  background: repeating-linear-gradient(
    0deg,
    rgba(255, 46, 108, 0.09) 0 2px,
    transparent 2px 7px
  );
}

@keyframes bandshift {
  0%   { transform: translateX(0); }
  33%  { transform: translateX(-0.7rem); }
  66%  { transform: translateX(0.5rem); }
  100% { transform: translateX(0); }
}

/* the glitch slices — clipped copies jittering left and right in red */
.glitch-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  opacity: var(--seq-copy, 0);
}
.glitch-layer span {
  position: absolute;
  left: 0;
  right: 0;
  background: rgba(255, 46, 108, 0.5);
  height: var(--h, 3px);
  top: var(--t, 0);
  transform: translateX(calc(var(--x, 0) * var(--seq-copy, 0)));
  mix-blend-mode: overlay;
}

/* Scrim: the sequence used to draw the torus straight over live content,
   which read as a collision rather than an effect. This fades the page down
   to near-black behind it, holds while the shape is alone on screen, then
   lifts as the shards fly out and the rails arrive. */
.seq-scrim {
  position: fixed;
  inset: 0;
  z-index: 87; /* under the torus and shards (88), over everything else */
  background: var(--bg);
  opacity: 0;
  pointer-events: none;
  display: none;
}
.is-sequencing .seq-scrim {
  display: block;
  opacity: var(--seq-scrim, 0);
}

/* --- torus stage */
.hero__viz { position: relative; min-height: 22rem; }

.is-sequencing .torus {
  position: fixed;
  margin: 0;
  /* above the marquee (85) so the sequence floats over it instead of being
     sliced in half by it, but under the CRT overlay (90) and header (100) */
  z-index: 88;
  transform-origin: 0 0;
  will-change: transform, opacity;
  /* The in-flow torus uses `contain: strict`, which is right when it is a
     stretched grid item but fatal once it is fixed: size containment
     collapses it to zero width and paint containment then clips the text
     out of existence. Shrink-wrap the glyphs instead. */
  contain: none;
  display: block;
  width: max-content;
  min-height: 0;
}

.torus.is-shattered { opacity: 0; }

/* --- shards */
.shards {
  position: fixed;
  inset: 0;
  z-index: 88;
  pointer-events: none;
  display: none;
  contain: layout paint style;
}
.shards.is-on { display: block; }

.shard {
  position: absolute;
  color: var(--accent);
  font-family: var(--mono);
  line-height: 1;
  white-space: pre;
  /* One custom property on the parent drives every shard, so a frame costs
     one style write rather than N.

     The resting position lives in left/top as plain pixels so it is resolved
     once at build time; only the displacement goes through calc(). Keeping
     the calc chain short matters — this restyles a few hundred elements per
     frame, and every extra term is paid on all of them. */
  transform:
    translate3d(
      calc(var(--dx) * var(--burst, 0) * 1px),
      calc(var(--dy) * var(--burst, 0) * 1px),
      0
    )
    rotate(calc(var(--r) * var(--burst, 0) * 1deg))
    /* one shared shrink rather than a per-shard value: the characters start
       at exactly the torus's size, so the handoff is seamless, then dwindle
       as they scatter */
    scale(calc(1 - var(--burst, 0) * 0.42));
  opacity: calc(1 - var(--burst, 0) * var(--fade, 1));
}

@media (max-width: 83.99rem) {
  /* no rails here, so nothing for the shards to become */
  .shards { display: none !important; }
}

/* ---- 25 sequence skills bar --------------------------------------------
   Once the torus breaks, the in-flow skills marquee — which has scrolled
   off by then — hands over to a copy pinned across the top of the viewport,
   directly beneath the header (main.js measures it into --seq-bar-top).
   The in-flow bar is faded rather than removed so it keeps its layout box:
   the side rails clip themselves against it, and zero-height geometry would
   break that.
   ------------------------------------------------------------------------ */
.seq-marquee {
  position: fixed;
  top: var(--seq-bar-top, 0px);
  left: 0;
  right: 0;
  z-index: 84;
  display: none;
  overflow: hidden;
  background: #020403;
  border-bottom: 1px solid var(--line-soft);
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.45s var(--ease), visibility 0s linear 0.45s;
}
.seq-marquee.is-on {
  visibility: visible;
  opacity: 1;
  transition: opacity 0.45s var(--ease), visibility 0s;
}

/* Same geometry and cadence as the in-flow bar, so the hand-off reads as
   the same object staying put rather than a second one arriving. */
.seq-marquee__track {
  display: flex;
  gap: 3rem;
  width: max-content;
  padding-block: 0.7rem;
  animation: slide 38s linear infinite;
  font-size: var(--step--1);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-dim);
  white-space: nowrap;
}
.seq-marquee:hover .seq-marquee__track { animation-play-state: paused; }
.seq-marquee__track span::before { content: "\2022  "; color: var(--accent); }

@media (min-width: 84rem) {
  .seq-marquee { display: block; }
}

/* Hand-off: the in-flow bar goes quiet once the pinned one is up. */
.has-seqbar .marquee { opacity: 0; }

/* ---- 20 reduced motion / print ----------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  [data-reveal] { opacity: 1; transform: none; }
  .crt-glow::after { display: none; }
  .marquee__track { animation: none; }
  .step { opacity: 1; }
}

@media print {
  .crt-lines, .crt-glow, .mobile-cta, .site-header, .torus, .matrix, .rail { display: none !important; }
  body { background: #fff; color: #000; }
  a { color: #000; text-decoration: underline; }
  .box { background: none; border: 1px solid #999; }
}
