/* Offplanly — stage-1 landing page.
   Colour and type come from the venture's visual lock. Geometry, spacing and sizing
   come from the Paper artboard "V11e · full page" (1440x3205). The Paper file's own
   token table belongs to an unrelated project and is deliberately not used here. */

/* ---------- Faces: self-hosted, Latin subset, only the weights the lock uses ---------- */

@font-face {
  font-family: "IBM Plex Serif";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/ibm-plex-serif-latin-500-normal.woff2") format("woff2");
}

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/inter-latin-400-normal.woff2") format("woff2");
}

/* Subset to one sentence. This face carries only the glyphs of the single signed question
   the drawing sets at weight 500 — "What are the three strongest reasons to say no?" —
   because the full Latin face is 24KB and the page's weight budget has 16KB left. Any
   other Inter 500 string on this page will fall back to Inter 400 and lose its emphasis
   without saying so; `font-synthesis: none` means nothing is faked. That emphasis belongs
   to this one question by ruling, so the subset is a guard — but if the string ever
   changes, regenerate it. Rebuild from the @fontsource dev dependency, never served:
   pyftsubset node_modules/@fontsource/inter/files/inter-latin-500-normal.woff2
     --text="What are the three strongest reasons to say no?" --layout-features='*'
     --flavor=woff2 --output-file=site/fonts/inter-latin-500-subset.woff2 */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/inter-latin-500-subset.woff2") format("woff2");
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
}

@font-face {
  font-family: "Stack Sans Notch";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/stack-sans-notch-latin-500-normal.woff2") format("woff2");
}

/* ---------- The lock ---------- */

:root {
  --ground: #ffffff;
  --ink: #111111;
  --muted: #615e5b;
  --hairline: #e9eaeb;
  --control-border: #d8d3cc;
  --surface-tint: #f3efeb;
  --inverted: #111111;

  --font-display: "IBM Plex Serif", Georgia, serif;
  --font-prose: "Inter", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, monospace;
  --font-wordmark: "Stack Sans Notch", system-ui, sans-serif;

  --radius-pill: 160px;
  --radius-panel: 32px;
  --radius-card: 20px;

  /* The drawing is 1440 wide with a 56px gutter; below that the gutter narrows. */
  --page-width: 1440px;
  --gutter: 56px;
  /* The method section is drawn on a wider gutter than the fold. */
  --gutter-wide: 120px;
  --masthead-height: 40px;
  --masthead-offset: 32px;
  /* What the masthead takes off the top before the fold begins. */
  --fold-top: calc(var(--masthead-offset) + var(--masthead-height));
  /* The drawn hero height. The fold never goes below it, and grows past it only to
     reach the bottom of a taller window. */
  --hero-min: 830px;
  /* The haze and the skyline both stop 2px above the fold's bottom edge. */
  --horizon-inset: 2px;
}

/* Content is capped at the artboard width and gutters in from there. */
.measure {
  width: 100%;
  max-width: var(--page-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The accent is not a global token on purpose: it may appear in exactly two places
   on the finished page — the horizon haze, and the dashed branch ending in the empty
   NO RECORD ring. Each site names the colour itself so a third use cannot slip in
   through a shared variable. */

@media (max-width: 900px) {
  :root {
    --gutter: 20px;
    --gutter-wide: 20px;
    /* The masthead wraps to two rows: wordmark + button, then the nav. */
    --masthead-height: 76px;
    --hero-min: 620px;
  }
}

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

body {
  margin: 0;
  background-color: var(--ground);
  color: var(--ink);
  font-family: var(--font-prose);
  font-size: 18px;
  line-height: 28px;
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* The drawing is a 1440 artboard: content is capped at that width, but the horizon —
   haze and skyline — bleeds to the viewport edge however wide the window is. */
.page {
  width: 100%;
  overflow-x: clip;
}

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

/* ---------- The fold: haze, skyline, masthead, hero ---------- */

/* The horizon and the hero share one positioning context: both decorative layers are
   pinned to the fold's bottom edge so they hold the horizon when the hero grows. */
.fold {
  position: relative;
}

/* The haze and the skyline both end 2px above the fold's bottom edge, so they stay
   pinned to the horizon when the hero grows taller than its drawn height. */

.horizon-haze {
  --haze: #ff9900;
  position: absolute;
  inset-inline: 0;
  bottom: var(--horizon-inset);
  height: 300px;
  background-image: linear-gradient(
    in oklab 0deg,
    color-mix(in oklab, var(--haze) 4%, transparent) 0%,
    color-mix(in oklab, var(--haze) 17%, transparent) 48%,
    color-mix(in oklab, var(--haze) 0%, transparent) 100%
  );
}

/* The artwork is 14px taller than the band that shows it, exactly as drawn. That
   overflow is sky, so it is spent off the top: the buildings stand on the band's bottom
   edge, which is the horizon. */
.skyline {
  position: absolute;
  inset-inline: 0;
  bottom: var(--horizon-inset);
  height: 346px;
  overflow: clip;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

/* The silhouette ships as black on white and multiplies onto the haze, which keeps it
   a fraction of the weight of the same artwork with an alpha channel.
   Anchored to its own bottom edge: wider than the drawn 1440 the cover crop has to take
   a slice somewhere, and it must come off the sky, never off the buildings' bases. */
.skyline img {
  width: 100%;
  min-width: 1000px;
  height: 360px;
  flex-shrink: 0;
  object-fit: cover;
  object-position: 50% 100%;
  mix-blend-mode: multiply;
}

.masthead {
  position: relative;
  z-index: 1;
  margin-top: var(--masthead-offset);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  row-gap: 20px;
  min-height: var(--masthead-height);
}

.wordmark {
  font-family: var(--font-wordmark);
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 20px;
  color: var(--ink);
  text-decoration: none;
}

.masthead-nav {
  display: flex;
  align-items: center;
  gap: 28px;
  padding-left: 40px;
}

.masthead-nav a {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.01em;
  line-height: 16px;
  color: var(--muted);
  text-decoration: none;
}

.masthead-nav a:hover,
.masthead-nav a:focus-visible {
  color: var(--ink);
}

.masthead-cta {
  margin-left: auto;
  flex-shrink: 0;
}

/* Drawn 22px below the masthead, over the top of the hero rather than between them. */
.masthead-rule {
  position: absolute;
  inset-inline: 0;
  top: 22px;
  height: 1px;
  background-color: var(--hairline);
}

/* The ghost button: white fill, warm control border, pill radius. */
.button-ghost {
  display: inline-flex;
  align-items: center;
  padding: 11px 24px;
  border: 1px solid var(--control-border);
  border-radius: var(--radius-pill);
  background-color: var(--ground);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 16px;
  color: var(--ink);
  text-decoration: none;
}

.button-ghost:hover,
.button-ghost:focus-visible {
  border-color: var(--ink);
}

/* Press feedback on the page's controls: the control gives under the finger and comes
   back. The reduced-motion rule below collapses the duration, so it becomes instant
   rather than absent — the press still registers. */
:where(.button-ghost, .button-solid, .composer-send) {
  transition: transform 160ms ease-out;
}

:where(.button-ghost, .button-solid, .composer-send):active {
  transform: scale(0.97);
}

/* The horizon is the bottom of the fold, so the fold has to reach the bottom of the
   window or the silhouette stands on a strip of dead white. It grows to fill whatever
   is left under the masthead and never shrinks below the drawn height — at the drawn
   1440x902 the two are the same number, so nothing moves there. The hero centres its
   own content, so the extra height reads as more sky rather than a gap. */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: max(var(--hero-min), calc(100vh - var(--fold-top)));
  padding-bottom: 56px;
}

/* Small-viewport units where they exist: on a phone the fold then fits the visible
   area rather than the area behind the address bar. */
@supports (height: 100svh) {
  .hero {
    min-height: max(var(--hero-min), calc(100svh - var(--fold-top)));
  }
}

.hero-stack {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 34px;
  width: 100%;
  max-width: 788px;
}

.headline-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}

.headline {
  margin: 0;
  max-width: 680px;
  font-family: var(--font-display);
  font-size: 56px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 62px;
  text-align: center;
  color: var(--ink);
  text-wrap: balance;
}

.supporting {
  margin: 0;
  max-width: 620px;
  font-family: var(--font-prose);
  font-size: 18px;
  font-weight: 400;
  line-height: 28px;
  text-align: center;
  color: var(--muted);
  text-wrap: pretty;
}

/* ---------- Composer ---------- */

.composer {
  display: flex;
  flex-direction: column;
  gap: 18px;
  width: 100%;
  min-height: 152px;
  padding: 28px 28px 20px;
  border: 1px solid var(--inverted);
  border-radius: var(--radius-panel);
  background-color: var(--inverted);
}

/* No name attribute, no form association: what a visitor types here is discarded on
   the client and is structurally incapable of riding a submission. */
.composer-input {
  width: 100%;
  max-width: 700px;
  min-height: 32px;
  padding: 0;
  border: 0;
  resize: none;
  background: transparent;
  font-family: var(--font-prose);
  font-size: 19px;
  font-weight: 400;
  line-height: 32px;
  color: var(--ground);
}

.composer-input::placeholder {
  color: var(--ground);
  opacity: 1;
}

/* Where supported, the field grows with what is typed instead of clipping it. */
@supports (field-sizing: content) {
  .composer-input {
    field-sizing: content;
  }
}

.composer-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: auto;
}

.composer-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 18px;
  border: 1px solid var(--muted);
  border-radius: var(--radius-pill);
  background: transparent;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.01em;
  line-height: 16px;
  color: var(--ground);
  text-decoration: none;
}

.composer-chip svg {
  flex-shrink: 0;
}

.composer-send {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  margin-left: auto;
  border: 0;
  border-radius: var(--radius-card);
  background-color: var(--ground);
}

:where(a, button, textarea):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* The small controls inside the composer keep a ring against the inverted panel. */
.composer :where(a, button):focus-visible {
  outline-color: var(--ground);
}

/* The textarea fills the panel, so a ring on the field itself reads as a stark white
   rectangle. Ring the panel instead: quieter, and it still says exactly where focus is.
   Guarded on :has so a browser without it keeps the plain ring rather than none. */
@supports selector(:has(*)) {
  .composer:has(.composer-input:focus-visible) {
    outline: 2px solid var(--muted);
    outline-offset: 3px;
  }

  .composer-input:focus-visible {
    outline: none;
  }
}

/* ---------- Method section and the constellation ---------- */

/* Full bleed, so the horizon rule on its top edge runs edge to edge like the haze
   above it; the content inside is capped at the artboard width. */
.method {
  border-top: 1px solid var(--hairline);
  background-color: var(--ground);
}

.method-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 80px;
  width: 100%;
  max-width: var(--page-width);
  margin-inline: auto;
  padding: 96px var(--gutter-wide) 104px;
}

.method-column {
  width: 420px;
  flex-shrink: 0;
}

.method-head {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-bottom: 34px;
}

.eyebrow {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.12em;
  line-height: 14px;
  color: var(--muted);
}

.section-heading {
  margin: 0;
  font-family: var(--font-display);
  font-size: 32px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 38px;
  color: var(--ink);
  text-wrap: pretty;
}

.method-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.method-item {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-top: 28px;
  padding-bottom: 32px;
  border-top: 1px solid var(--hairline);
}

.method-item:last-child {
  border-bottom: 1px solid var(--hairline);
}

.method-index {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.08em;
  line-height: 14px;
  color: var(--muted);
}

.method-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.005em;
  line-height: 28px;
  color: var(--ink);
}

.method-body {
  margin: 0;
  font-family: var(--font-prose);
  font-size: 16px;
  font-weight: 400;
  line-height: 26px;
  color: var(--muted);
  text-wrap: pretty;
}

.constellation {
  display: flex;
  flex-grow: 1;
  flex-basis: 0;
  align-items: center;
  justify-content: center;
  min-height: 560px;
}

/* One aspect ratio at every width: the diagram scales, it never distorts. */
.constellation-svg {
  width: 100%;
  max-width: 700px;
  height: auto;
}

/* The linework keeps its drawn weight as the diagram scales down, so the dashed branch
   and the rings do not thin to nothing on a phone. A no-op at the drawn width.
   The two lines that draw themselves on are excluded deliberately: a non-scaling stroke
   is dashed in device pixels, which breaks the path-unit dash the reveal depends on.
   They hold their weight through `stroke-width` below instead. */
.constellation-svg :where(line, circle):not(.constellation-ray, .constellation-reveal) {
  vector-effect: non-scaling-stroke;
}

/* One ramp drives the whole diagram: `em` here is the SVG's own font-size, so the rays
   thicken by exactly the factor the type does and land back on the drawn 1px. */
.constellation-ray {
  stroke-width: calc(1em / 15);
}

.constellation-line {
  fill: none;
  stroke: var(--control-border);
}

.constellation-hub-dot {
  fill: var(--ink);
  stroke: none;
}

/* The diagram's three type sizes are one ramp: the label size is the SVG's own, and the
   two callouts are the drawn 12/15 and 11/15 of it. Only the base moves when the diagram
   is scaled; the ratios below never do. */
.constellation-svg {
  font-size: 15px;
}

/* Diagram labels are prose, diagram callouts are controls — the page's type rule.
   Mono at 11-12px takes weight 500; it goes spindly at that size with open tracking.
   Each ceiling is the size at which that label would reach the diagram's own edge and be
   clipped; they bind only where the base ramp has grown past them. */
.constellation-label {
  font-family: var(--font-prose);
  font-size: min(1em, 27px);
  font-weight: 400;
  fill: var(--ink);
  text-anchor: middle;
}

.constellation-callout {
  font-family: var(--font-mono);
  font-size: min(calc(1em * 12 / 15), 26px);
  font-weight: 500;
  letter-spacing: 0.1em;
  fill: var(--ink);
  text-anchor: middle;
}

.constellation-callout--record {
  font-size: min(calc(1em * 11 / 15), 24px);
  letter-spacing: 0.12em;
}

/* ---------- Questions section ---------- */

/* The accent is not spent here. This section is ink, muted and the tinted surface only. */

.questions {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: var(--page-width);
  margin-inline: auto;
  padding: 24px var(--gutter-wide) 112px;
  gap: 44px;
  background-color: var(--ground);
}

.questions-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 760px;
  gap: 14px;
}

/* The section's own display size: larger than the method section's, as drawn. The
   artboard also draws this one heading at -0.025em, but tracking is the lock's to set and
   the lock fixes display type at -0.02em — so the inherited value stands until the brain
   rules otherwise. Size, leading and alignment are geometry and come from the drawing. */
.questions-heading {
  font-size: 44px;
  line-height: 52px;
  text-align: center;
}

/* The bar exists only once the widget does. Without JavaScript the six lists are all
   on the page under their own headings, so there is no control left to be broken. */
.questions-tabs {
  display: none;
}

/* Six pills stop fitting a little above the drawn width, so the bar scrolls rather than
   wraps — a wrapped pill reads as a mistake. The scroll lives on this wrapper, whose
   vertical padding is the room a focus ring needs; the negative margin gives the
   section's own 44px gap back. `safe center` keeps the first tab reachable when the bar
   does overflow. */
.is-tabbed .questions-tabs {
  display: flex;
  justify-content: safe center;
  width: 100%;
  overflow-x: auto;
  padding-block: 6px;
  margin-block: -6px;
  scrollbar-width: none;
}

.questions-tabs::-webkit-scrollbar {
  display: none;
}

.questions-tablist {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  padding: 5px;
  gap: 4px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background-color: var(--surface-tint);
}

/* The unselected pill carries a transparent border so selecting one moves nothing. */
.questions-tab {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  padding: 9px 20px;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background-color: transparent;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 400;
  line-height: 16px;
  color: var(--muted);
  cursor: pointer;
}

.questions-tab:hover {
  color: var(--ink);
}

/* Mono keeps one advance at either weight, so the bar does not resize as tabs change. */
.questions-tab[aria-selected="true"] {
  border-color: var(--control-border);
  background-color: var(--ground);
  font-weight: 500;
  color: var(--ink);
}

/* The ring is drawn just inside the pill: the bar is a scroll container, and anything
   outside a tab's own box would be clipped by it. */
.questions-tab:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
}

.questions-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: 1200px;
  padding: 72px 100px;
  gap: 32px;
  border-radius: var(--radius-panel);
  background-color: var(--surface-tint);
}

.questions-panel {
  width: 100%;
  max-width: 820px;
  padding: 12px 40px 20px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background-color: var(--ground);
}

.questions-panel[hidden] {
  display: none;
}

.questions-panel:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* The group's name. It labels the list on its own until the tab bar exists to do it. */
.questions-panel-title {
  margin: 0 0 8px;
  padding-top: 8px;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  line-height: 16px;
  color: var(--ink);
}

.is-tabbed .questions-panel-title {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.questions-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.question {
  display: flex;
  align-items: center;
  gap: 20px;
  padding-block: 22px;
  border-bottom: 1px solid var(--hairline);
}

.question:last-child {
  border-bottom: 0;
}

.question-index {
  flex-shrink: 0;
  width: 24px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.08em;
  line-height: 18px;
  color: var(--muted);
}

.question-text {
  margin: 0;
  font-family: var(--font-prose);
  font-size: 19px;
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 28px;
  color: var(--ink);
  text-wrap: pretty;
}

/* The section's single emphasis, drawn and signed, on one question in "The money". */
.question-text--emphasis {
  font-weight: 500;
}

/* Between the stack point and the drawn width the stage holds its full measure, but the
   drawn 100px inset starts eating the card rather than framing it. */
@media (max-width: 1200px) {
  .questions-stage {
    padding: 56px 48px;
  }
}

/* ---------- Closing band ---------- */

.closing {
  display: flex;
  justify-content: center;
  width: 100%;
  max-width: var(--page-width);
  margin-inline: auto;
  padding: 0 var(--gutter-wide) 104px;
  background-color: var(--ground);
}

.closing-card {
  display: flex;
  align-items: center;
  gap: 64px;
  width: 100%;
  max-width: 1200px;
  padding: 72px;
  border-radius: var(--radius-panel);
  background-color: var(--inverted);
}

.closing-copy {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: 16px;
}

.closing-heading {
  margin: 0;
  font-family: var(--font-display);
  font-size: 36px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 44px;
  color: var(--ground);
  text-wrap: pretty;
}

/* The drawn value is a translucent white, not a grey: it has the inverted surface behind
   it, and a flat tint would go muddy where the card's radius meets the page. */
.closing-body {
  margin: 0;
  font-family: var(--font-prose);
  font-size: 17px;
  font-weight: 400;
  line-height: 28px;
  color: #ffffff9e;
  text-wrap: pretty;
}

/* The page's second button shape: solid where the ghost button is outlined. The base is
   ink on the white page; on the inverted card it inverts with it, as drawn. */
.button-solid {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 14px 28px;
  border: 0;
  border-radius: var(--radius-pill);
  background-color: var(--ink);
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 500;
  line-height: 18px;
  color: var(--ground);
  text-decoration: none;
  cursor: pointer;
}

.closing-cta {
  flex-shrink: 0;
  background-color: var(--ground);
  color: var(--ink);
}

/* The ring has to be visible against whichever surface the button sits on. */
.closing-card :where(a, button):focus-visible {
  outline-color: var(--ground);
}

/* ---------- Request access: the form, and the dialog it moves into ---------- */

/* Where the form lives with no script running. `request-access.js` hides this section
   once the dialog exists, so the drawn page ends closing band → footer as it should —
   but only after every part of the dialog is in place. */
.request.is-dialog {
  display: none;
}

.request {
  display: flex;
  justify-content: center;
  width: 100%;
  max-width: var(--page-width);
  margin-inline: auto;
  padding: 0 var(--gutter-wide) 96px;
  background-color: var(--ground);
}

.request-card {
  display: flex;
  flex-direction: column;
  gap: 32px;
  width: 100%;
  max-width: 560px;
  padding: 48px;
  border-radius: var(--radius-panel);
  background-color: var(--surface-tint);
}

.request-copy {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.request-heading {
  margin: 0;
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 36px;
  color: var(--ink);
  text-wrap: pretty;
}

.request-body {
  margin: 0;
  font-family: var(--font-prose);
  font-size: 17px;
  font-weight: 400;
  line-height: 28px;
  color: var(--muted);
  text-wrap: pretty;
}

.request-form {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.request-form[hidden] {
  display: none;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.field-label {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  line-height: 18px;
  color: var(--muted);
}

.field-control {
  width: 100%;
  padding: 13px 16px;
  border: 1px solid var(--control-border);
  border-radius: var(--radius-card);
  background-color: var(--ground);
  font-family: var(--font-prose);
  font-size: 16px;
  font-weight: 400;
  line-height: 24px;
  color: var(--ink);
}

/* The select is a control, so it is drawn as one rather than left to the platform. */
select.field-control {
  appearance: none;
  padding-right: 40px;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 22px) 22px, calc(100% - 16px) 22px;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}

.field-control:hover {
  border-color: var(--ink);
}

/* There is no error colour in the lock, and the accent is spent twice over and cannot be
   borrowed for a third thing. So a wrong field is said the way the rest of the page says
   everything: weight and the ink, not a new hue. */
.field-control[aria-invalid="true"] {
  border-color: var(--ink);
  border-width: 2px;
  padding: 12px 15px; /* the extra border, given back, so nothing shifts */
}

.field-error {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  line-height: 18px;
  color: var(--ink);
}

.field-error[hidden] {
  display: none;
}

/* The decoy is off the side of the screen, not display:none and not hidden — a form
   filler skips what is not rendered, which would defeat the whole point of it. */
.field-decoy {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* The heading and the confirmation take focus without being controls: they are given the
   page's own ring so a keyboard visitor can see where the dialog put them. */
:where(.field-control, .request-heading, .request-success):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* iOS Safari counts programmatic focus as `:focus-visible`, so the heading the dialog
   focuses on open was drawing a black box around the signed line for every touch
   visitor — a keyboard affordance shown to someone who never pressed a key. The class
   is set by the script only when the dialog was opened by pointer or touch, so a
   keyboard open still rings and nothing here weakens that. */
.request-heading.is-pointer-focus:focus-visible {
  outline: none;
}

.request-submit {
  align-self: flex-start;
}

.request-success {
  margin: 0;
  font-family: var(--font-prose);
  font-size: 17px;
  font-weight: 400;
  line-height: 28px;
  color: var(--ink);
  text-wrap: pretty;
}

.request-success[hidden] {
  display: none;
}

/* The dialog is built by the script, so nothing below exists until it runs. The card
   supplies the surface; the dialog is only the frame that centres and lifts it. */
.request-dialog {
  position: relative;
  width: 100%;
  max-width: 560px;
  max-height: calc(100vh - 48px);
  margin: auto;
  padding: 0;
  overflow: auto;
  border: 0;
  background: transparent;
}

@supports (max-height: 100svh) {
  .request-dialog {
    max-height: calc(100svh - 48px);
  }
}

.request-dialog::backdrop {
  background-color: rgb(17 17 17 / 0.55);
}

/* Opening is a fade rather than a movement. The page-wide reduced-motion rule collapses
   the duration, so a visitor who asks for no motion gets the dialog instantly. */
.request-dialog,
.request-dialog::backdrop {
  opacity: 0;
  transition:
    opacity 160ms ease-out,
    display 160ms allow-discrete,
    overlay 160ms allow-discrete;
}

.request-dialog[open],
.request-dialog[open]::backdrop {
  opacity: 1;
}

@starting-style {
  .request-dialog[open],
  .request-dialog[open]::backdrop {
    opacity: 0;
  }
}

/* The dialog is the one thing on the page allowed to hold the window still. */
.has-dialog {
  overflow: hidden;
}

.request-close {
  position: absolute;
  top: 16px;
  right: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.request-close:hover {
  color: var(--ink);
}

/* ---------- Footer ---------- */

/* Full bleed, so the rule on its top edge runs edge to edge the way the method
   section's does; the line inside is capped at the artboard width. */
.site-footer {
  border-top: 1px solid var(--hairline);
  background-color: var(--ground);
}

.site-footer-inner {
  display: flex;
  align-items: center;
  width: 100%;
  max-width: var(--page-width);
  margin-inline: auto;
  padding: 32px var(--gutter-wide) 40px;
}

.site-footer-line {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.04em;
  line-height: 18px;
  color: var(--muted);
}

/* ---------- The 404 page ----------

   `404.html` is the landing page's two fixed edges and nothing between them — the
   wordmark linking home, and the footer line. Both are already-signed strings; no new
   prose ships there by ruling, so the empty middle is the page. Its job is to answer an
   unmatched path with a real 404 instead of a second copy of the landing page, which is
   what Pages does otherwise. The footer is pushed to the bottom of the window because
   with no content between them the two edges would otherwise huddle at the top. */
.page-bare {
  display: flex;
  flex-direction: column;
  min-height: 100svh;
}

.page-bare .site-footer {
  margin-top: auto;
}

/* ---------- The constellation draws itself, once ----------

   Everything above is the finished diagram, so the page is complete before any script
   runs and stays complete if none ever does. The class below is added by
   `constellation.js` when the diagram scrolls into view; every rule under it uses
   `backwards` fill, so the hidden pre-state exists only inside the animation's own
   delay — there is no blank state for a visitor to get stuck in.

   The sequence: the four rays draw outward from the hub, each label arriving as its ray
   lands; a beat; then the branch reveals toward the ring, and the ring and its label
   last. Only stroke-dashoffset and opacity move, so nothing reflows. */

@media (prefers-reduced-motion: no-preference) {
  .is-drawing .constellation-hub {
    animation: constellation-arrive 300ms ease-out backwards;
  }

  .is-drawing .constellation-ray {
    stroke-dasharray: 1;
    animation: constellation-draw 600ms ease-out backwards;
  }

  .is-drawing .constellation-label text {
    animation: constellation-arrive 300ms ease-out backwards;
  }

  /* Each ray leaves at 90ms after the one before it, and its label lands with it. */
  .is-drawing .constellation-ray:nth-child(1) { animation-delay: 0ms; }
  .is-drawing .constellation-ray:nth-child(2) { animation-delay: 90ms; }
  .is-drawing .constellation-ray:nth-child(3) { animation-delay: 180ms; }
  .is-drawing .constellation-ray:nth-child(4) { animation-delay: 270ms; }

  .is-drawing .constellation-label text:nth-child(1) { animation-delay: 500ms; }
  .is-drawing .constellation-label text:nth-child(2) { animation-delay: 590ms; }
  .is-drawing .constellation-label text:nth-child(3) { animation-delay: 680ms; }
  .is-drawing .constellation-label text:nth-child(4) { animation-delay: 770ms; }

  /* The climax, after a beat: the branch draws, then the ring it ends in. */
  .is-drawing .constellation-reveal {
    stroke-dasharray: 1;
    animation: constellation-draw 700ms 1420ms ease-out backwards;
  }

  .is-drawing :where(.constellation-ring, .constellation-callout--record) {
    animation: constellation-arrive 300ms 2120ms ease-out backwards;
  }
}

@keyframes constellation-draw {
  from {
    stroke-dashoffset: 1;
  }
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes constellation-arrive {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* The mask's own ink: white is "show through". Wide enough to clear the branch's stroke
   at every scale, and it never paints anything itself. */
.constellation-reveal {
  stroke: var(--ground);
  stroke-width: 12;
}

/* The drawn two-column holds while the diagram still renders near full size. Below
   that the constellation would shrink until its labels stopped being readable, so
   the section stacks and gives the diagram the full measure back. */
@media (max-width: 1319px) {
  /* Centred, not flex-start: the column caps at 560px, so starting it left stranded a
     dead zone to its right the full width of the section while the diagram below it
     centred — worst at the wide end of this range. Centring the box is all that moves;
     the text inside it stays left-aligned, because the ragged right edge is the drawing's
     and not something the breakpoint gets to change. */
  .method-inner {
    flex-direction: column;
    align-items: center;
    gap: 64px;
  }

  .method-column {
    width: 100%;
    max-width: 560px;
  }

  /* Stacked, the flex axis is vertical: the diagram takes its own height back
     rather than being sized against a container that has none. */
  .constellation {
    flex: none;
    width: 100%;
    min-height: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after,
  /* The dialog's backdrop is not covered by the universal selector, and it fades. */
  *::backdrop {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

@media (max-width: 900px) {
  /* The drawn floor drops on small screens; the fill-the-window rule above still
     applies, so the horizon stays on the bottom edge of a tall phone. */
  .hero {
    padding-bottom: 40px;
  }

  /* Below the drawn width the display size scales down; the drawn 56px/62px stands
     everywhere the artboard describes. */
  .headline {
    font-size: clamp(34px, 8vw, 48px);
    line-height: 1.107;
  }

  /* Both nav items stay on the page; they move to their own row under the wordmark. */
  .masthead-nav {
    order: 1;
    width: 100%;
    padding-left: 0;
    gap: 24px;
  }

  .composer {
    padding: 20px 20px 16px;
  }

  /* The specimen question runs to three lines at this width; show it whole. */
  .composer-input {
    font-size: 17px;
    line-height: 28px;
    min-height: 84px;
  }

  .method-inner {
    padding-top: 64px;
    padding-bottom: 72px;
  }

  .section-heading {
    font-size: 28px;
    line-height: 34px;
  }

  .questions {
    padding-top: 8px;
    padding-bottom: 72px;
    gap: 32px;
  }

  .questions-heading {
    font-size: 30px;
    line-height: 36px;
  }

  /* The frame around the card has to give most of its inset back, or the questions
     wrap to four lines apiece inside it. */
  .questions-stage {
    padding: 20px 12px;
    gap: 20px;
  }

  .questions-panel {
    padding-inline: 20px;
  }

  /* Every question wraps at this width. Centring the number against a three-line
     question strands it in the middle of the row, so it sits on the first line
     instead — the same relationship the drawing has when each row is one line. */
  .question {
    align-items: flex-start;
    gap: 14px;
    padding-block: 18px;
  }

  .question-index {
    padding-top: 4px;
    width: 20px;
  }

  .question-text {
    font-size: 17px;
    line-height: 26px;
  }

  /* The drawn row puts the CTA beside the copy; at this width it goes under it, and
     takes the full measure so it is a comfortable target rather than a stub. */
  .closing {
    padding-bottom: 72px;
  }

  .closing-card {
    flex-direction: column;
    align-items: stretch;
    gap: 28px;
    padding: 32px;
  }

  .closing-heading {
    font-size: 28px;
    line-height: 36px;
  }

  .request {
    padding-bottom: 72px;
  }

  .request-card {
    gap: 28px;
    padding: 28px;
  }

  .request-heading {
    font-size: 24px;
    line-height: 32px;
  }

  .request-submit {
    align-self: stretch;
  }
}

/* Below 740 the diagram no longer fits at its drawn width, so it scales — and its type
   would scale with it, down to 7px at 375. The whole diagram must stay on screen (the
   empty ring is the point of it), so instead the type grows in user units as the
   diagram shrinks, holding the labels at roughly their drawn size on the glass.
   `4.2vw` resolves against the viewport before it is read as user units; the ratios
   below track the width the diagram actually gets, and the ratios above carry the other
   two sizes. Stacked, that width is the viewport less the gutters. */
@media (max-width: 739px) {
  .constellation-svg {
    font-size: calc(45.05px - 4.2vw);
  }
}

/* The same compensation in the other direction: between the stack point and the drawn
   1440 the two columns hold, and the diagram gets whatever the method column and the gap
   leave behind. At 1440 this resolves to the drawn 15. */
@media (min-width: 1320px) and (max-width: 1439px) {
  .constellation-svg {
    font-size: calc(52.2px - 2.58vw);
  }
}
