/* ─────────────────────────────────────────────────────────────────────────────
   Reset, page shell, and the handful of primitives every section shares.

   The ground is flat. There is no grid, no noise, no aurora and no gradient
   wash on the page behind the content — the one perspective grid on this site
   is the floor the hero's android stands on, in css/hero.css, because that
   grid is the room. A grid behind body copy would be a texture with no job.

   There is exactly ONE glass surface on this page: the sticky header. It is
   glass because content scrolls underneath it and the label has to stay
   readable while it does, which is a job and not a finish.
   ───────────────────────────────────────────────────────────────────────────── */

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

html {
  -webkit-text-size-adjust: 100%;
  scrollbar-color: var(--scrollbar-thumb) var(--surface-bg);
}

body,
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd,
ul,
ol {
  margin: 0;
}

ul[class],
ol[class] {
  padding: 0;
  list-style: none;
}

img,
svg,
canvas {
  display: block;
  max-width: 100%;
}

body {
  background: var(--surface-bg);
  color: var(--text-ink);
  font-family: var(--face-sans);
  font-size: var(--t-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* The ground is not pure #0a0a0b at the edges. A single inset ring, cyan at
   four percent, sets the page inside a very slightly warmer field so the
   hairline rules have something to sit against at the top and bottom of a
   scroll. It is one declaration and it is the entire background treatment. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: var(--z-ground);
  box-shadow: inset 0 0 240px -60px color-mix(in srgb, var(--accent) 4%, transparent);
}

::selection {
  background: color-mix(in srgb, var(--accent) 28%, transparent);
  color: var(--text-ink);
}

a {
  color: var(--accent-fg);
  text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent);
  text-underline-offset: 3px;
}

a:hover {
  text-decoration-color: var(--accent);
}

:focus-visible {
  outline: var(--focus-ring-width) solid var(--accent);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius);
}

/* ── skip link ───────────────────────────────────────────────────────────────
   The first focusable thing on the page, off-screen until focused. A page this
   long with a sticky header needs one: tabbing through the header to reach the
   pillars is the cost of not having it.
   ──────────────────────────────────────────────────────────────────────────── */
.skip {
  position: absolute;
  left: var(--space-4);
  top: var(--space-4);
  z-index: calc(var(--z-header) + 1);
  padding: var(--control-pad-y) var(--control-pad-x);
  background: var(--accent);
  color: var(--accent-ink);
  font: 600 var(--t-small) / 1 var(--face-mono);
  border-radius: var(--radius);
  transform: translateY(-200%);
}

.skip:focus {
  transform: none;
}

/* ── layout ───────────────────────────────────────────────────────────────────
   One column, one measure, one gutter. --page-max is 1180 rather than the
   source system's 880 because the hero is a two-column composition and 880
   would force the android below the copy on a laptop. The body prose inside
   is still held to --measure, so the extra width is spent on the hero and on
   the pillar grid, not on longer lines.
   ──────────────────────────────────────────────────────────────────────────── */
.shell {
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--page-x);
}

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

/* Below-the-fold sections skip layout and paint until they are near the
   viewport. On a page this long that is most of the page: the hero's 23-plate
   rig, the canvas loop and four sections' worth of text no longer all have to
   be laid out and painted during the first frame, which is the frame a visitor
   judges the site on.

   The hero is excluded on purpose — it is above the fold, so there is nothing
   to defer, and content-visibility:auto on the element holding the canvas would
   fight the packets script's own IntersectionObserver for the same information.

   contain-intrinsic-size is what stops the scrollbar jumping: without it the
   browser has no idea how tall a skipped section is and the scroll height grows
   as sections resolve. `auto 800px` lets the browser remember the real height
   after the first visit. */
.section:not(.hero) {
  content-visibility: auto;
  contain-intrinsic-size: auto 800px;
}

.section--rule {
  border-top: var(--rule-width) solid var(--rule);
}

/* ── the label ───────────────────────────────────────────────────────────────
   Uppercase mono micro-label at .16em. The source system's signature and the
   entire register of this page. It is set as one class and never inline, so a
   label cannot end up at the wrong tracking because one of them was.
   ──────────────────────────────────────────────────────────────────────────── */
.label {
  font: 500 var(--t-micro) / 1 var(--face-mono);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-faint);
}

.label--accent {
  color: var(--accent-fg);
}

/* The section eyebrow: a label that sits above its section's heading with a
   hairline running to the right edge of the column. It is the one place a
   rule is used as a divider rather than as a border, and it is doing the job
   of separating the label from everything else on the line. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

.eyebrow::after {
  content: "";
  flex: 1;
  height: var(--rule-width);
  background: linear-gradient(
    90deg,
    var(--rule-hard) 0%,
    color-mix(in srgb, var(--rule-hard) 20%, transparent) 100%
  );
}

/* ── headings ────────────────────────────────────────────────────────────────
   Display type is set tight. A tracked-out headline at 92px is the single
   loudest tell of a generated landing page, and --tracking-label is
   deliberately NOT applied here: the label is the label, the headline is not.
   ──────────────────────────────────────────────────────────────────────────── */
h1,
h2,
h3 {
  font-family: var(--face-sans);
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.h2 {
  font-size: var(--t-h2);
  max-width: 20ch;
}

.h3 {
  font-size: var(--t-h3);
  letter-spacing: -0.015em;
  line-height: 1.2;
}

.lead {
  font-size: var(--t-lead);
  color: var(--text-muted);
  max-width: var(--measure-narrow);
  text-wrap: pretty;
}

.prose {
  color: var(--text-muted);
  max-width: var(--measure);
  text-wrap: pretty;
}

.prose strong {
  color: var(--text-ink);
  font-weight: 600;
}

/* ── buttons ──────────────────────────────────────────────────────────────────
   Two controls, and the difference between them is fill versus no fill. The
   filled one is cyan with ink on it, which measures 8.85:1 in either mode
   because the fill is theme-invariant — so the primary action on this page is
   the highest-contrast object on it, which is the point of having one.

   --control-min-h is 44px and is enforced with min-height, not by padding,
   because a two-line label must not silently produce a 38px target.
   ──────────────────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-min-h);
  padding: var(--control-pad-y) var(--control-pad-x);
  font: 500 var(--t-small) / 1.2 var(--face-mono);
  letter-spacing: 0.02em;
  text-decoration: none;
  white-space: nowrap;
  border-radius: var(--radius);
  border: var(--rule-width) solid transparent;
  cursor: pointer;
  transition: background var(--motion-fast) var(--motion-ease),
    border-color var(--motion-fast) var(--motion-ease),
    color var(--motion-fast) var(--motion-ease),
    box-shadow var(--motion-fast) var(--motion-ease);
}

.btn--primary {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
}

.btn--primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.btn--primary:active {
  background: var(--accent-active);
  border-color: var(--accent-active);
}

.btn--ghost {
  background: transparent;
  color: var(--text-ink);
  border-color: var(--rule-hard);
}

.btn--ghost:hover {
  border-color: var(--accent);
  color: var(--accent-fg);
}

/* ── the chip ─────────────────────────────────────────────────────────────────
   --shape-chip, exempt from the 2px because it is exempt in the source system
   for the reason given there: a chip is a value lifted out of a sentence, and
   round ends are what say it is pick-up-able.
   ──────────────────────────────────────────────────────────────────────────── */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 28px;
  padding-inline: var(--space-3);
  border-radius: var(--shape-chip);
  border: var(--rule-width) solid var(--rule);
  background: var(--surface-paper);
  font: 500 var(--t-micro) / 1 var(--face-mono);
  letter-spacing: 0.04em;
  color: var(--text-muted);
  white-space: nowrap;
}

.chip svg,
.chip img {
  width: 14px;
  height: 14px;
  opacity: 0.85;
}

/* ── entrance ─────────────────────────────────────────────────────────────────
   One reveal, driven by a class the script adds, and the no-script state is
   the FINAL state — content is visible when JS never runs. Revealing by
   default and hiding on load would mean a page that is blank for anyone whose
   script is blocked, which is the wrong failure for a page whose job is to
   get someone to run a pip command.
   ──────────────────────────────────────────────────────────────────────────── */
.js [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition: opacity var(--motion-base) var(--motion-enter),
    transform var(--motion-base) var(--motion-enter);
}

.js [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* ── reduced motion ───────────────────────────────────────────────────────────
   Everything that would move, stops moving, and lands on the state it was
   animating TOWARD rather than on its first frame. Shortening a duration is
   not the same thing as honouring this: a 0.01ms reveal still flickers, and an
   idle loop still runs. So the loop is cancelled and the end state is set.
   ──────────────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .js [data-reveal] {
    opacity: 1;
    transform: none;
  }
}
