/* ==========================================================================
   jasoncotton.com
   Dark technical-editorial system.
   Order: tokens -> base -> layout -> sections -> dialog -> responsive -> motion
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Colour */
  --ground: #0a0c0d;
  --surface: #14181a;
  --rule: #262d31;
  --ink: #eceeef;
  --ink-dim: #a7b0b4;
  --accent: #8aa4ff;

  /* Type */
  --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
    Roboto, Helvetica, Arial, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas,
    monospace;

  --step-display: clamp(2.75rem, 8.2vw, 6.25rem);
  --step-section: clamp(1.5rem, 3vw, 2.125rem);
  --step-lead: clamp(1.25rem, 2.2vw, 1.75rem);
  --step-body: clamp(1rem, 1.1vw, 1.0625rem);
  --step-label: 0.78125rem;

  /* Space */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;
  --s-8: 4rem;
  --s-9: 6rem;
  --s-10: 9rem;

  /* Layout */
  --measure: 78rem;
  --gutter: clamp(1.25rem, 5vw, 4rem);
  --header-h: 4.25rem;

  /* Motion */
  --dur-fast: 180ms;
  --dur-base: 420ms;
  --dur-resolve: 1150ms;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);

  color-scheme: dark;
  scroll-behavior: smooth;
}

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

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

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

@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url("fonts/jetbrains-mono-latin-var.woff2") format("woff2");
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--step-body);
  line-height: 1.55;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}

h1,
h2,
h3,
p,
ul,
figure {
  margin: 0;
}

ul {
  padding: 0;
  list-style: none;
}

a {
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.shell {
  width: min(100% - (var(--gutter) * 2), var(--measure));
  margin-inline: auto;
}

.skip {
  position: absolute;
  left: var(--s-4);
  top: var(--s-4);
  z-index: 10;
  padding: var(--s-2) var(--s-4);
  background: var(--surface);
  border: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--step-label);
  text-decoration: none;
  transform: translateY(-200%);
}

.skip:focus-visible {
  transform: none;
}

/* Shared small-caps technical label */
.label {
  font-family: var(--font-mono);
  font-size: var(--step-label);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* Buttons and button-shaped links */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-5);
  border: 1px solid var(--rule);
  border-radius: 0.5rem;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: var(--step-body);
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--dur-fast) ease,
    background-color var(--dur-fast) ease, color var(--dur-fast) ease;
}

.btn:hover {
  border-color: var(--ink-dim);
}

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

.btn--primary:hover {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.btn:disabled {
  cursor: not-allowed;
  color: var(--ink-dim);
  border-color: var(--rule);
  /* Reads as "waiting" rather than broken: it keeps a filled body instead of
     being washed out to near-invisible. */
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  opacity: 1;
}

.btn:disabled:hover {
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  border-color: var(--rule);
}

/* Scroll targets clear the sticky header */
#work,
#how,
#top,
#main {
  scroll-margin-top: calc(var(--header-h) + var(--s-5));
}

/* --------------------------------------------------------------------------
   3. Header
   -------------------------------------------------------------------------- */

/* The hero is the first screen and already carries the name and a Contact
   action, so a navigation bar on top of it would only compete with it. The
   header is therefore `fixed` rather than `sticky` — it takes no space in the
   flow, letting the hero own the full viewport — and stays entirely hidden
   until the hero's own Contact action has scrolled out of view. */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 5;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--ground) 82%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
  opacity: 0;
  visibility: hidden;
  translate: 0 -100%;
  /* `visibility` is what keeps the hidden bar out of the tab order and the
     accessibility tree, so the name and links are never reachable or
     announced while the hero's own copy and actions are on screen. */
  transition: opacity var(--dur-base) var(--ease-out),
    translate var(--dur-base) var(--ease-out),
    visibility 0s linear var(--dur-base);
}

.site-header.is-past-hero {
  opacity: 1;
  visibility: visible;
  translate: 0 0;
  transition-delay: 0s, 0s, 0s;
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
}

.site-header__name {
  font-weight: 600;
  letter-spacing: -0.012em;
  text-decoration: none;
  color: var(--ink);
}

.site-header__nav {
  display: flex;
  align-items: center;
  gap: var(--s-6);
}

.site-header__link {
  position: relative;
  font-family: var(--font-mono);
  font-size: var(--step-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-decoration: none;
  background: none;
  border: 0;
  padding: var(--s-2) 0;
  cursor: pointer;
  transition: color var(--dur-fast) ease;
}

.site-header__link::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 1px;
  background: var(--accent);
  scale: 0 1;
  transform-origin: 0 50%;
  transition: scale var(--dur-fast) var(--ease-out);
}

.site-header__link:hover::after,
.site-header__link:focus-visible::after {
  scale: 1 1;
}

.site-header__link:hover {
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   4. Hero
   -------------------------------------------------------------------------- */

/* Two columns, two rows. The identity sits in the top-left, where the header
   bar used to be; the statement sits beneath it, vertically centred in the
   space that remains; the art spans both rows on the right. Giving the
   identity its own row is what stops the headline from colliding with the
   name — they are now separate bands rather than overlapping layers.

     ┌─────────────────────────┬──────────────┐
     │ portrait │ name         │              │
     │          │ signal       │              │
     ├──────────┴──────────────┤     art      │
     │ Simplifying             │              │
     │ hard problems           │              │
     │ [See work] [Contact]    │              │
     └─────────────────────────┴──────────────┘ */
.hero {
  position: relative;
  /* Capped rather than a full `100svh`: on tall viewports an uncapped hero
     left several hundred pixels of dead ground above and below the content,
     and pushed the next section entirely out of view. */
  min-height: min(100svh, 50rem);
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  grid-template-rows: auto minmax(0, 1fr);
  column-gap: var(--s-8);
  /* No top padding: the portrait is meant to run off the top edge of the
     viewport, which is what makes it read as a plane behind the page rather
     than a card sitting on it. */
  padding-block: 0 var(--s-7);
}

/* Soft accent bloom behind the art, and faint vertical hairlines, so the
   empty space reads as composed rather than merely blank. */
.hero::before,
.hero::after {
  content: "";
  position: absolute;
  z-index: 0;
  pointer-events: none;
}

/* Deliberately sized to fade to full transparency *inside* its own box, which
   is exactly the hero box. Nothing overflows, so nothing has to be clipped —
   and with no clip there is no hard cut line across the bloom. */
.hero::before {
  inset: 0;
  background: radial-gradient(
    40% 60% at 70% 46%,
    color-mix(in srgb, var(--accent) 17%, transparent),
    transparent 72%
  );
}

.hero::after {
  inset: 0;
  background-image: linear-gradient(
    to right,
    var(--rule) 1px,
    transparent 1px
  );
  background-size: 8rem 100%;
  opacity: 0.4;
  /* Faded on all four sides so the grid dissolves into the ground instead of
     stopping against a visible edge. */
  -webkit-mask-image: linear-gradient(
      to bottom,
      transparent,
      #000 22%,
      #000 62%,
      transparent
    ),
    linear-gradient(to right, transparent, #000 20%, #000 86%, transparent);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(
      to bottom,
      transparent,
      #000 22%,
      #000 62%,
      transparent
    ),
    linear-gradient(to right, transparent, #000 20%, #000 86%, transparent);
  mask-composite: intersect;
}

.hero > * {
  position: relative;
  z-index: 1;
}

.hero__identity {
  grid-column: 1;
  grid-row: 1;
  align-self: start;
  display: flex;
  align-items: center;
  gap: var(--s-6);
}

.hero__text {
  grid-column: 1;
  grid-row: 2;
  align-self: center;
}

.hero__art {
  grid-column: 2;
  grid-row: 1 / -1;
  align-self: center;
}

.hero__ident {
  min-width: 0;
}

/* The portrait is a background-free cutout whose shoulders already fade out
   in its own alpha channel, so it needs no panel, no border and no CSS mask —
   the figure simply stands on the page. Its left edge sits on the headline's
   text column so the two share one alignment. No background is painted behind
   it: a bloom was tried and removed, because a gradient on the element box
   reintroduces exactly the rectangle the cutout exists to avoid. */
.hero__portrait {
  flex: none;
  width: clamp(10rem, 18vw, 16rem);
  /* The `height` attribute reserves layout space before the image arrives,
     but it also lands as a presentational hint that would otherwise beat
     `aspect-ratio`. */
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  display: block;
}

.hero__name {
  font-size: clamp(2rem, 4.4vw, 3.5rem);
  font-weight: 600;
  letter-spacing: -0.036em;
  line-height: 1;
}

.hero__headline {
  font-size: var(--step-display);
  font-weight: 560;
  line-height: 0.94;
  letter-spacing: -0.042em;
  text-wrap: balance;
}

.hero__signal {
  position: relative;
  font-family: var(--font-mono);
  font-size: clamp(0.6875rem, 1vw, 0.875rem);
  font-weight: 450;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-wrap: balance;
  color: var(--ink-dim);
  margin-top: var(--s-4);
  padding-top: var(--s-5);
}

.hero__signal::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: linear-gradient(
    to right,
    var(--accent) 0,
    var(--rule) 32%,
    transparent 100%
  );
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  margin-top: var(--s-7);
}

/* --------------------------------------------------------------------------
   5. Hero art — complexity resolving into structure
   -------------------------------------------------------------------------- */

.hero__art {
  width: 100%;
  height: auto;
  display: block;
  overflow: visible;
}

/* Motion model
   -------------
   Only the unresolved LEFT side of the field keeps moving. The right side —
   the rail and the three outputs — settles once and then holds perfectly
   still, because that stillness is what "resolved" means here.

   Each line is anchored at its right endpoint (`transform-origin: 100% 50%`)
   and drifts by a fraction of a degree of ROTATION. Rotation about the right
   anchor displaces the loose left end by a few pixels while the resolved
   right end moves by effectively zero, so the drift tapers across the line
   for free — no per-point path animation required.

   The one-shot settle drives `transform`; the perpetual drift drives the
   independent `rotate` property. They are distinct properties, so they
   compose instead of overwriting, and every ambient keyframe begins and ends
   at the element's resting value — which is why the handoff from settle to
   drift has no visible jump. */

.hero-art__line {
  fill: none;
  stroke: var(--ink-dim);
  stroke-width: 1.15;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: var(--o);
  transform-box: fill-box;
  transform-origin: 100% 50%;
  animation: line-settle var(--dur-resolve) var(--ease-out) var(--d) backwards,
    line-drift var(--drift-dur, 13s) ease-in-out var(--d) infinite;
}

/* The three lines that survive into the outputs. Heavier, and ramped from
   light on the chaotic left to full weight at the resolved right, so each one
   visibly gains definition as it travels toward the structure. The ramp is
   drawn with a gradient stroke rather than a varying stroke width, which SVG
   cannot express along a path. */
.hero-art__line--key {
  stroke: url(#key-weight);
  stroke-width: 2;
}

.hero-art__key-stop {
  stop-color: var(--ink);
}

/* Varied amplitude and period so the field never visibly marches in step.
   Amplitudes are in degrees: at a ~386-unit lever arm rendered at roughly
   1.37x, 1deg moves the loose end by about 9px on screen, so a cycle travels
   roughly four times the amplitude.

   What makes drift *readable* is speed, not travel. Earlier values were large
   enough to see but spread over 13-15s, which works out near 3px/s — slow
   enough that the eye reads it as stillness. Amplitudes are up and periods
   are down so the loose ends move at roughly 7-9px/s: unmistakably moving,
   still unhurried. The taper means the midpoints move at half that and the
   resolved ends not at all, so the right side stays dead still. */
.hero-art__line:nth-child(5n + 1) {
  --drift: 2deg;
  --drift-dur: 8s;
}
.hero-art__line:nth-child(5n + 2) {
  --drift: -1.55deg;
  --drift-dur: 6.5s;
}
.hero-art__line:nth-child(5n + 3) {
  --drift: 1.8deg;
  --drift-dur: 9.5s;
}
.hero-art__line:nth-child(5n + 4) {
  --drift: -2.25deg;
  --drift-dur: 7s;
}
.hero-art__line:nth-child(5n + 5) {
  --drift: 1.4deg;
  --drift-dur: 8.8s;
}

@keyframes line-settle {
  from {
    opacity: 0;
    transform: rotate(var(--r)) translateX(var(--tx));
  }
}

/* Starts and ends at 0deg — the resting value — so it can begin at any time
   without a discontinuity, and loops seamlessly. */
@keyframes line-drift {
  0% {
    rotate: 0deg;
  }
  25% {
    rotate: var(--drift, 0.35deg);
  }
  75% {
    rotate: calc(var(--drift, 0.35deg) * -1);
  }
  100% {
    rotate: 0deg;
  }
}

/* `--k` is set per dot in the markup, scaled by how far LEFT the dot sits:
   dots deep in the noise float freely, dots near the rail barely move. */
.hero-art__dot {
  fill: var(--ink-dim);
  opacity: 0.45;
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: dot-in var(--dur-base) var(--ease-out) var(--d) backwards,
    dot-drift var(--drift-dur, 15s) ease-in-out var(--d) infinite;
}

.hero-art__dot:nth-child(4n + 1) {
  --dx: 10px;
  --dy: -13px;
  --drift-dur: 9.5s;
}
.hero-art__dot:nth-child(4n + 2) {
  --dx: -12.5px;
  --dy: 11px;
  --drift-dur: 7.5s;
}
.hero-art__dot:nth-child(4n + 3) {
  --dx: 7.5px;
  --dy: 14px;
  --drift-dur: 11s;
}
.hero-art__dot:nth-child(4n + 4) {
  --dx: -9.5px;
  --dy: -10px;
  --drift-dur: 8.5s;
}

@keyframes dot-in {
  from {
    opacity: 0;
    transform: scale(0.2);
  }
}

/* Three-point loop returning to the origin, so it wanders rather than
   oscillating on one axis. Begins and ends at the resting value. */
@keyframes dot-drift {
  0% {
    translate: 0 0;
  }
  33% {
    translate: calc(var(--dx, 2px) * var(--k, 1))
      calc(var(--dy, 2px) * var(--k, 1));
  }
  66% {
    translate: calc(var(--dx, 2px) * var(--k, 1) * -0.7)
      calc(var(--dy, 2px) * var(--k, 1) * -0.55);
  }
  100% {
    translate: 0 0;
  }
}

/* The resolved side. Settles once, then holds absolutely still. */
.hero-art__rail {
  stroke: var(--accent);
  stroke-width: 1.25;
  opacity: 0.85;
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: rail-in var(--dur-base) var(--ease-out) 760ms backwards;
}

@keyframes rail-in {
  from {
    opacity: 0;
    transform: scaleY(0.12);
  }
}

.hero-art__out {
  stroke: var(--accent);
  stroke-width: 2.25;
  stroke-linecap: round;
  transform-box: fill-box;
  transform-origin: 0% 50%;
  animation: out-in var(--dur-base) var(--ease-out) var(--d) backwards;
}

@keyframes out-in {
  from {
    opacity: 0;
    transform: scaleX(0.05);
  }
}

/* --------------------------------------------------------------------------
   6. Sections
   -------------------------------------------------------------------------- */

.section {
  padding-block: var(--s-10);
  border-top: 1px solid var(--rule);
}

.section__head {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin-bottom: var(--s-8);
}

/* Short accent stroke marking each section opening. */
.section__head::before {
  content: "";
  flex: none;
  width: 4px;
  height: 1.05em;
  background: var(--accent);
}

.section__title {
  font-size: var(--step-section);
  font-weight: 560;
  letter-spacing: -0.028em;
}

/* --- How I work --- */

.how__signal {
  font-size: var(--step-lead);
  font-weight: 500;
  letter-spacing: -0.022em;
  color: var(--accent);
  margin-bottom: var(--s-7);
}

.how__set {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s-5);
}

.how__item {
  position: relative;
  padding-top: var(--s-4);
}

/* Accent-tipped hairline: the rule starts in accent and fades to the neutral
   rule colour, giving each item a point of entry. */
.how__item::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: linear-gradient(
    to right,
    var(--accent) 0,
    var(--rule) 38%,
    var(--rule) 100%
  );
}

.how__index {
  display: block;
  margin-bottom: var(--s-5);
}

.how__phrase {
  font-size: clamp(1.0625rem, 1.5vw, 1.3125rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.2;
  text-wrap: balance;
}

/* --- Selected work --- */

.work__list {
  display: grid;
  gap: var(--s-9);
}

.case__head {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin-bottom: var(--s-5);
}

.case__rule {
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, var(--rule), transparent);
}

.case__title {
  font-size: clamp(1.25rem, 2vw, 1.625rem);
  font-weight: 500;
  letter-spacing: -0.026em;
  color: var(--ink-dim);
  margin-bottom: var(--s-6);
}

.case__flow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: start;
  gap: var(--s-5);
}

.case__node {
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
}

.case__node--result {
  border-top-color: var(--accent);
}

.case__node--result .case__label {
  color: var(--accent);
}

.case__label {
  display: block;
  margin-bottom: var(--s-3);
}

.case__text {
  color: var(--ink-dim);
  max-width: 34ch;
}

.case__arrow {
  font-family: var(--font-mono);
  color: color-mix(in srgb, var(--accent) 60%, transparent);
  align-self: center;
  font-size: 1.125rem;
  line-height: 1;
}

/* --- Footer --- */

.site-footer {
  border-top: 1px solid var(--rule);
  padding-block: var(--s-7);
}

.site-footer__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-4);
}

/* Reserved slot for profile links, professional email and resume.
   Renders nothing until authoritative values exist. */
.site-footer__links {
  display: flex;
  gap: var(--s-5);
}

/* --------------------------------------------------------------------------
   7. Contact dialog
   -------------------------------------------------------------------------- */

/* Absolutely positioned rather than the UA's fixed centring so the dialog keeps
   its natural height on short viewports and the page scrolls to reveal it,
   instead of the dialog growing its own scrollbar. site.js sets `top` to the
   current scroll offset so it always opens in view. */
.contact {
  position: absolute;
  inset-inline: 0;
  top: 0;
  width: min(100% - var(--s-6), 36rem);
  max-width: none;
  max-height: none;
  overflow: visible;
  margin: var(--s-8) auto;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--rule));
  border-radius: 0.875rem;
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--accent) 6%, var(--surface)),
    var(--surface) 38%
  );
  color: var(--ink);
  box-shadow: 0 32px 80px -32px rgb(0 0 0 / 0.85),
    0 0 0 1px color-mix(in srgb, var(--accent) 7%, transparent);
}

/* A single accent edge along the top, matching the accent ticks that open
   each section, so the dialog reads as part of the same system. */
.contact::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  border-radius: 0.875rem 0.875rem 0 0;
  background: linear-gradient(
    to right,
    var(--accent),
    color-mix(in srgb, var(--accent) 35%, transparent) 42%,
    transparent 78%
  );
}

.contact::backdrop {
  background: color-mix(in srgb, #05070a 78%, transparent);
  backdrop-filter: blur(6px);
}

.contact__inner {
  padding: var(--s-7);
}

.contact__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-6);
}

.contact__title {
  font-size: 1.5rem;
  font-weight: 560;
  letter-spacing: -0.028em;
}

.contact__close {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: transparent;
  color: var(--ink-dim);
  font-size: 1.125rem;
  line-height: 1;
  cursor: pointer;
  transition: color var(--dur-fast) ease, border-color var(--dur-fast) ease,
    background-color var(--dur-fast) ease;
}

.contact__close:hover {
  color: var(--ink);
  border-color: var(--ink-dim);
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}

/* Name and Email share a row on anything but the narrowest viewports, which
   makes the form read as short and quick to complete. */
.contact__row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-5);
}

.contact__field {
  display: block;
  margin-bottom: var(--s-5);
}

.contact__row .contact__field {
  margin-bottom: 0;
}

.contact__row + .contact__field {
  margin-top: var(--s-5);
}

.contact__input,
.contact__textarea {
  display: block;
  width: 100%;
  margin-top: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--rule);
  border-radius: 0.5rem;
  background: color-mix(in srgb, var(--ground) 72%, #000);
  color: var(--ink);
  font: inherit;
  font-size: var(--step-body);
  transition: border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease,
    background-color var(--dur-fast) ease;
}

.contact__textarea {
  resize: vertical;
  min-height: 8rem;
}

.contact__input:hover,
.contact__textarea:hover {
  border-color: var(--ink-dim);
}

.contact__input:focus-visible,
.contact__textarea:focus-visible {
  outline: none;
  border-color: var(--accent);
  background: var(--ground);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

.contact__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-4);
  margin-top: var(--s-6);
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule);
}

.contact__note {
  font-family: var(--font-mono);
  font-size: var(--step-label);
  color: var(--ink-dim);
  max-width: 24ch;
}

/* --------------------------------------------------------------------------
   8. Responsive
   -------------------------------------------------------------------------- */

/* Keep the name on one line wherever there is room for it. Beside a portrait
   this wide it would otherwise break after "Jason", which reads as a two-line
   heading rather than as a name. It is allowed to overrun its grid column into
   the column gap, which stays wider than the overrun at every width above this
   breakpoint. Below it the portrait and the name share a narrow row, so the
   name has to be free to wrap or the page overflows. */
@media (min-width: 48rem) {
  .hero__name {
    white-space: nowrap;
  }
}

@media (max-width: 60rem) {
  .how__set {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-6);
  }
}

@media (max-width: 48rem) {
  :root {
    --s-9: 4rem;
    --s-10: 5rem;
  }

  .hero {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto;
    gap: var(--s-7);
    padding-block: var(--s-7) var(--s-8);
    min-height: 0;
  }

  .hero__identity,
  .hero__text,
  .hero__art {
    grid-column: 1;
    grid-row: auto;
  }

  .hero__art {
    max-width: 30rem;
  }

  /* The hairline grid reads as interference at narrow widths, where it
     crosses the text rather than sitting beside it. */
  .hero::after {
    display: none;
  }

  .hero__identity {
    gap: var(--s-5);
  }

  .hero__portrait {
    width: clamp(7.5rem, 30vw, 10.5rem);
  }

  .case__flow {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-4);
  }

  .case__arrow {
    justify-self: start;
    transform: rotate(90deg);
  }

  .case__text {
    max-width: none;
  }

  .section__head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-2);
    margin-bottom: var(--s-7);
  }
}

@media (max-width: 30rem) {
  .how__set {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-5);
  }

  .site-header__nav {
    gap: var(--s-4);
  }

  .contact__row {
    grid-template-columns: minmax(0, 1fr);
  }

  .contact__row .contact__field + .contact__field {
    margin-top: var(--s-5);
  }

  .contact__inner {
    padding: var(--s-5);
  }
}

/* --------------------------------------------------------------------------
   9. Motion preference
   -------------------------------------------------------------------------- */

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

  .hero-art__line,
  .hero-art__dot,
  .hero-art__rail,
  .hero-art__out {
    animation: none;
  }

  /* The global override below zeroes transition durations but not delays,
     which would otherwise leave the header name invisible for a beat. */
  .site-header__name {
    transition-delay: 0s;
  }

  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}
