/* mrjohnfoster.com · "Signal from noise" · silkscreen build */

@font-face {
  font-family: Display;
  src: url("display.woff2") format("woff2");
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: Body;
  src: url("body.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}
/* Bricolage Grotesque, a static semibold (opsz 24) Latin subset: every heading and title except the
   hero name and "Let's chat.", which keep Bodoni as the page's two monumental moments */
@font-face {
  font-family: Heading;
  src: url("heading.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

:root {
  --ground: #0c0e0d;
  --panel: #141716;
  --hair: #2a2e2c;
  --text: #ece8df;
  --muted: #a9a89f;
  --signal: #7fe0c3;
  --field-line: #646a66;
  --copy-max: 58%;
  --brass: rgb(201 180 138 / .7);
  --brass-solid: #c9b48a;
  --stop-h: 85vh;
  --brass-under: #3a301e;

  /* roles */
  --bg: var(--ground);
  --fg: var(--text);
  --fg-muted: var(--muted);
  --line: var(--hair);
  --accent: var(--signal);

  --display: Display, "Bodoni 72", "Bodoni MT", Didot, Georgia, serif;
  --heading: Heading, 'Bricolage Grotesque', Manrope, system-ui, sans-serif;
  --sans: Body, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --ease: cubic-bezier(.16, 1, .3, 1);
  --ease-in: cubic-bezier(.5, 0, .75, 0);

  --header: 72px;
  --gutter: clamp(20px, 4vw, 64px);
  --max: 1320px;
  --section: clamp(72px, 10vw, 140px);

  color-scheme: dark;
  scrollbar-color: #4a4f4c var(--ground);
  scroll-padding-top: calc(var(--header) + 16px);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  background: var(--ground);
  view-transition-name: none;
}

@supports (height: 1lvh) {
  :root { --stop-h: 85lvh; }
}
/* smooth anchor scrolling only for the flat page; on the board, scripted journeys replace it */
@media (prefers-reduced-motion: no-preference) {
  html:not(.world-live) { scroll-behavior: smooth; }
}

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  font-weight: 400;
  font-kerning: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  caret-color: var(--signal);
  accent-color: var(--signal);
}
@media (min-width: 960px) {
  body { font-size: 18px; }
}

h1, h2, h3, h4, p, figure, ul { margin: 0; }
h1, h2, h3, h4 { font-weight: 400; }
img, svg { display: block; }
img { max-width: 100%; height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
button { -webkit-tap-highlight-color: transparent; }
strong { font-weight: 700; color: var(--fg); }

::selection { background: #7fe0c3; color: #0c0e0d; }

a {
  color: inherit;
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .22em;
  text-decoration-color: var(--accent);
  transition: color .2s var(--ease), text-decoration-color .2s var(--ease);
}
a:hover { color: var(--accent); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}
main:focus,
[data-station]:focus { outline: none; }

.i {
  width: 20px;
  height: 20px;
  flex: none;
  overflow: visible;
}
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

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

/* ---------- the board: one fixed canvas behind every section ---------- */

#hero-canvas {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 0;
  width: 100%;
  height: 100vh;
  height: 100lvh;
  pointer-events: none;
  touch-action: pan-y;
}
main {
  position: relative;
  z-index: 1;
}

/* ---------- type ---------- */

.h2 {
  font-family: var(--heading);
  font-weight: 600;
  font-size: clamp(34px, 4.4vw, 60px);
  line-height: 1.04;
  letter-spacing: -.025em;
  text-wrap: balance;
  color: var(--fg);
}
.h3 {
  font-family: var(--heading);
  font-weight: 600;
  font-size: clamp(24px, 2vw, 28px);
  line-height: 1.08;
  letter-spacing: -.02em;
  text-wrap: balance;
  color: var(--fg);
}
/* the two-part headings (One person / for the whole problem., Different settings. / The same responsibility.)
   always break at their join from 720px up */
@media (min-width: 720px) {
  .h2-line { display: block; }
}
.lead {
  font-size: 18px;
  line-height: 1.5;
  max-width: 50ch;
  color: var(--fg);
}
@media (min-width: 960px) {
  .lead { font-size: 20px; }
}
.meta {
  font-size: 14px;
  line-height: 1.5;
  letter-spacing: .01em;
  font-variant-numeric: tabular-nums;
  color: var(--fg-muted);
}

/* ---------- skip link + header ---------- */

.skip-link {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 100;
  padding: 12px 18px;
  background: var(--text);
  color: var(--ground);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform .2s var(--ease);
}
.skip-link:focus { transform: none; }
.skip-link:hover { color: var(--ground); }

.site-header {
  position: fixed;
  inset: 0 0 auto;
  z-index: 50;
  height: var(--header);
  background-color: var(--ground);
  border-bottom: 1px solid var(--hair);
  view-transition-name: site-header;
}
/* over the board the header is transparent at every scroll position: the board shades its own top band.
   Without the board (no script, no WebGL) the page scrolls under the header, so it stays solid there */
.js:not(.flat) .site-header {
  background-color: transparent;
  border-bottom-color: transparent;
}
/* below 960px the form stays in flow, the one block that scrolls under the header: while it passes,
   the header takes the ground so its links never print over the form's labels */
.js.form-under .site-header { background-color: var(--ground); }

/* loading: with script, the page waits on plain ground under the header until site.js mounts the board
   (html.world-live) or settles on the flat layout (html.flat, which the head script also sets after 8 s).
   Main is only hidden, never moved, and comes back without a transition. One soft pulse runs along the
   header's foot meanwhile; nothing else moves. Without script nothing waits. */
.js:not(.world-live):not(.flat) main { opacity: 0; }
.js:not(.world-live):not(.flat) .site-header::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 180px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--signal) 50%, transparent);
  transform: translateX(-100%);
  /* six passes cover the longest wait: the head script settles on the flat layout at 8 s */
  animation: load-pulse 1.4s cubic-bezier(.45, 0, .55, 1) 6;
  pointer-events: none;
}
@keyframes load-pulse {
  from { transform: translateX(-100%); }
  to { transform: translateX(100vw); }
}
@media (prefers-reduced-motion: reduce) {
  .js:not(.world-live):not(.flat) .site-header::after {
    width: 100%;
    background: var(--signal);
    transform: none;
    animation: none;
  }
}
.header-inner {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.brand {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-weight: 600;
  font-size: 17px;
  letter-spacing: -.005em;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
}
.brand:hover { color: var(--text); }
.nav ul {
  list-style: none;
  padding: 0;
  display: flex;
  align-items: center;
  gap: clamp(12px, 2.4vw, 32px);
}
.nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 16px;
  font-weight: 500;
  color: var(--text);
  text-decoration-color: transparent;
  text-underline-offset: .35em;
  white-space: nowrap;
}
.nav a:hover { color: var(--text); text-decoration-color: var(--signal); }
.nav .nav-cta {
  padding: 0 16px;
  border: 1px solid var(--text);
  transition: background-color .2s var(--ease), color .2s var(--ease);
}
.nav .nav-cta:hover {
  background: var(--text);
  color: var(--ground);
  text-decoration-color: transparent;
}
/* Work · Experience · Let's chat needs about 386px beside the brand, so phones below 520px drop Experience */
@media (max-width: 519px) { .nav-exp { display: none; } }
/* small phones: Work · Let's chat in a tighter set */
@media (max-width: 399px) {
  .nav a { font-size: 15px; }
  .nav ul { gap: 12px; }
  .nav .nav-cta { padding: 0 14px; }
}

/* ---------- touch: native paging by stop ----------
   On coarse pointers every reading stop (and the page end) carries a zero-height snap marker that
   site.js places at the stop's exact scroll offset; with mandatory snapping and scroll-snap-stop, one
   swipe moves exactly one stop whatever its size, and the page never rests between stops. iOS Safari
   reads scroll-snap-type only from the root scroller (html, as here) and honours scroll-snap-stop from
   Safari 15; older engines still page, a hard fling may pass a stop there. html.snap-off suspends it
   (journeys, a field in use, an open readout). Fine pointers never snap: the station hold serves them. */
.snap-rail {
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  height: 0;
  pointer-events: none;
}
.snap-mark {
  position: absolute;
  left: 0;
  width: 1px;
  height: 0;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}
@media (pointer: coarse) {
  html.world-live { scroll-padding-top: 0; }
  html.world-live:not(.snap-off) { scroll-snap-type: y mandatory; }
}

/* ---------- the scroll hint ----------
   A quiet cue for first-time visitors at the foot of the viewport; site.js shows it once the board has
   landed and after ten idle seconds at a station, and hides it on any scroll. It is never a control. */
.scroll-hint {
  position: fixed;
  left: 50%;
  bottom: calc(20px + var(--hint-lift, 0px) + env(safe-area-inset-bottom, 0px));
  z-index: 40;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  transform: translateX(-50%);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity .25s var(--ease), visibility 0s linear .25s;
}
.scroll-hint.is-shown {
  opacity: 1;
  visibility: visible;
  transition: opacity .4s var(--ease), visibility 0s;
}
/* where a plate reaches low in the frame, the words sit beside the arrow */
.scroll-hint.is-row { flex-direction: row; gap: 10px; }
.scroll-hint-arrow { width: 22px; height: 22px; color: var(--signal); }
.scroll-hint-label {
  font-size: 14px;
  line-height: 20px;
  letter-spacing: .01em;
  color: var(--muted);
  white-space: nowrap;
  /* a soft ground halo, no box: a bright trace passing under the words never takes them below legibility */
  text-shadow: 0 0 2px var(--ground), 0 0 8px var(--ground);
}
@media (prefers-reduced-motion: no-preference) {
  .scroll-hint.is-shown .scroll-hint-arrow { animation: hint-drift 1.8s ease-in-out infinite; }
}
@keyframes hint-drift {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(6px); }
}

/* ---------- hero ---------- */

.hero {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-rows: 1fr auto;
  min-height: clamp(640px, 100svh, 1100px);
  overflow: hidden;
  background: transparent;
}
.hero-fallback {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-image: repeating-linear-gradient(to bottom, transparent 0 13px, var(--hair) 13px 14px);
  -webkit-mask-image: linear-gradient(to bottom, rgb(0 0 0 / .2) 0%, rgb(0 0 0 / .65) 45%, #000 100%);
  mask-image: linear-gradient(to bottom, rgb(0 0 0 / .2) 0%, rgb(0 0 0 / .65) 45%, #000 100%);
  transition: opacity .9s var(--ease);
}
/* static: the upper lines break into noise, the lower lines run clean */
.hero-fallback::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    repeating-linear-gradient(90deg, transparent 0 7px, var(--ground) 7px 11px),
    repeating-linear-gradient(90deg, transparent 0 19px, var(--ground) 19px 26px),
    repeating-linear-gradient(90deg, transparent 0 43px, var(--ground) 43px 61px);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 22%, transparent 68%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 22%, transparent 68%);
}
.hero.is-live .hero-fallback { opacity: 0; }
/* When the visual draws the name itself (ink variant), keep the HTML name for assistive tech only. */
.hero.canvas-name .hero-name { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
/* Sideways drags reach the visual; vertical swipes still scroll the page. */
.hero { touch-action: pan-y; }
/* Let drags on empty hero space reach the visual; the text and button stay interactive. */
.hero-content { pointer-events: none; }
.hero-content h1, .hero-content p, .hero-content a, .hero-content button { pointer-events: auto; }
.hero-content {
  position: relative;
  z-index: 2;
  align-self: end;
  padding-top: calc(var(--header) + 32px);
  padding-bottom: clamp(32px, 8svh, 72px);
}
.hero-name {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(64px, 22vw, 96px);
  line-height: .86;
  letter-spacing: -.045em;
  color: var(--text);
  margin-left: -.04em;
}
.hero-name span { display: block; }
.hero-copy { margin-top: 20px; }
.hero-sub {
  font-weight: 500;
  font-size: clamp(18px, 1.6vw, 24px);
  line-height: 1.35;
  color: var(--text);
}
.hero-invite {
  margin-top: 28px;
  max-width: 44ch;
  font-size: 16px;
  line-height: 1.6;
  color: var(--muted);
}
.hero-action { margin-top: 28px; }
@media (min-width: 720px) {
  .hero-name { font-size: clamp(84px, 13.5vw, 212px); }
  .hero-invite { font-size: 18px; }
  .hero-content { padding-bottom: clamp(48px, 14svh, 150px); }
}

.status-strip {
  position: relative;
  z-index: 2;
  border-top: 1px solid var(--hair);
}
.status-inner {
  display: flex;
  width: 100%;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 2px 24px;
  padding-block: 13px 15px;
  font-size: 13px;
  line-height: 1.45;
  letter-spacing: .01em;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
@media (max-width: 719px) {
  .status-inner { flex-direction: column; }
}
@media (max-width: 359px) {
  .status-build { display: none; }
}
.hero-rule {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  height: 1px;
  background: var(--signal);
}

/* ---------- sections ---------- */

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

/* What I do */
.what-head {
  display: grid;
  gap: 24px;
}
@media (min-width: 960px) {
  .what-head {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 64px;
    align-items: end;
  }
  .what-head .lead { padding-bottom: .35em; }
}
.lanes {
  display: grid;
  gap: 40px;
  margin-top: clamp(56px, 7vw, 104px);
}
@media (min-width: 840px) {
  .lanes { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(28px, 3vw, 48px); }
}
.lane {
  padding-top: 24px;
  border-top: 1px solid var(--hair);
}
.lane p {
  margin-top: 16px;
  color: var(--muted);
  max-width: 44ch;
}

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

.work-head { margin-bottom: clamp(40px, 5vw, 72px); }

.feature {
  display: grid;
  gap: 28px;
  padding-top: clamp(32px, 4vw, 56px);
  border-top: 1px solid var(--line);
}
.feature + .feature { margin-top: clamp(64px, 8vw, 120px); }
@media (min-width: 960px) {
  .feature {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: 32px;
    row-gap: 40px;
  }
  .feature-copy { grid-column: 1 / span 5; grid-row: 1; }
  .feature-media { grid-column: 6 / -1; grid-row: 1; }
}
.project-title {
  display: flex;
  align-items: center;
  gap: 16px;
}
.project-mark { width: 52px; height: 52px; filter: brightness(0) invert(.91); }
.project-mark--wide { width: 84px; height: 44px; }
.project-name {
  font-family: var(--heading);
  font-weight: 600;
  font-size: clamp(28px, 2.8vw, 40px);
  line-height: 1.04;
  letter-spacing: -.02em;
}
.feature-copy { align-self: start; }
.feature-copy .meta { margin-top: 18px; }
.proof { max-width: 60ch; color: var(--fg); }
.feature-copy .proof { margin-top: 16px; }
.feature-copy .notes { margin-top: 24px; }

.shot {
  display: block;
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--ground);
  transition: border-color .2s var(--ease);
}
a.shot:hover { border-color: var(--accent); }
.shot img {
  width: 100%;
  aspect-ratio: 8 / 5;
  object-fit: cover;
}
.shot .shot-crop { object-position: 50% 32%; }
.feature-media figcaption { margin-top: 14px; }
.shot-caption { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: 4px 16px; }
.shot-more { color: var(--text); font-size: 16px; display: inline-flex; align-items: center; min-height: 44px; }
#what-title { text-wrap: wrap; }
@media (max-width: 719px) {
  #what-title .h2-line { display: inline-block; }
}

/* notes disclosures */
.notes { border-top: 1px solid var(--line); }
.notes > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 56px;
  padding-block: 14px;
  list-style: none;
  cursor: pointer;
  font-weight: 600;
  color: var(--fg);
}
.notes > summary::-webkit-details-marker { display: none; }
.notes > summary span { transition: color .2s var(--ease); }
.notes > summary:hover span { color: var(--accent); }
.notes > summary .i {
  width: 22px;
  height: 22px;
  color: var(--accent);
  transition: transform .3s var(--ease);
}
.notes[open] > summary .i { transform: rotate(45deg); }
.notes-body { padding-block: 8px 40px; }
.notes-body > p { max-width: 68ch; }
.notes-body > p + p { margin-top: 1em; }

.case-row {
  display: grid;
  gap: 24px;
  padding-block: 32px;
}
.case-row:first-child { padding-top: 16px; }
.case-row + .case-row { border-top: 1px solid var(--line); }
.case-title {
  font-family: var(--heading);
  font-weight: 600;
  font-size: clamp(22px, 1.8vw, 26px);
  line-height: 1.08;
  letter-spacing: -.02em;
  text-wrap: balance;
  margin-bottom: 16px;
}
.case-copy p { max-width: 60ch; }
.case-copy p + p { margin-top: 1em; }
.case-media img {
  width: 100%;
  border: 1px solid var(--line);
}
.case-media--phone { justify-self: center; }
.case-media--phone img { width: min(100%, 280px); }

/* compact rows */
.rows { margin-top: clamp(64px, 8vw, 120px); }
.row {
  display: grid;
  gap: 16px;
  padding-block: clamp(32px, 4vw, 56px);
  border-top: 1px solid var(--line);
}
@media (min-width: 960px) {
  .row { max-width: var(--copy-max); }
}
.row + .row { margin-top: clamp(24px, 3vw, 40px); }
.row-name {
  font-family: var(--heading);
  font-weight: 600;
  font-size: clamp(26px, 2.4vw, 34px);
  line-height: 1.06;
  letter-spacing: -.02em;
  text-wrap: balance;
}
.row-head .meta { margin-top: 10px; }
.figures {
  margin-bottom: 20px;
  font-family: var(--heading);
  font-weight: 600;
  font-size: clamp(24px, 2.4vw, 32px);
  line-height: 1.08;
  letter-spacing: -.02em;
  font-variant-numeric: lining-nums;
  text-wrap: balance;
}
.row .notes { margin-top: 24px; }
.row .notes-body { padding-bottom: 8px; }

/* ---------- Experience: the signal path ---------- */

@media (min-width: 960px) {
  .exp-head { max-width: var(--copy-max); }
}
.exp-intro {
  margin-top: 24px;
  max-width: 60ch;
  color: var(--text);
}
.rail {
  --route: .1;
  position: relative;
  display: none;
  margin-top: clamp(48px, 6vw, 88px);
}
.interactions-ready .rail {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
}
.rail-svg {
  position: absolute;
  overflow: visible;
  pointer-events: none;
}
.rail-svg--h { left: 0; top: 4px; width: 100%; height: 4px; }
.rail-svg--v { display: none; }
.rail-under { stroke: var(--brass-under); stroke-width: 1; }
.rail-base { stroke: var(--brass); stroke-width: 2; }
.rail-signal {
  stroke: var(--signal);
  stroke-width: 2;
  stroke-dasharray: 1 1;
  stroke-dashoffset: calc(1 - var(--route));
  transition: stroke-dashoffset .3s var(--ease);
}
.port { display: flex; justify-content: center; }
.port .node { width: 100%; }
.node {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  min-height: 44px;
  padding: 0 8px 8px;
  border: 0;
  background: none;
  color: var(--muted);
  font-weight: 600;
  font-size: 16px;
  line-height: 1.35;
  text-align: center;
  cursor: pointer;
  transition: color .2s var(--ease);
}
/* solder pads */
.node-dot {
  position: relative;
  z-index: 1;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--brass);
  background: var(--ground);
  transition: background-color .3s var(--ease), border-color .3s var(--ease);
}
.node:hover { color: var(--text); }
.node:hover .node-dot { border-color: var(--text); }
.node[aria-selected="true"] { color: var(--text); }
.node[aria-selected="true"] .node-dot {
  background: var(--signal);
  border-color: var(--signal);
}
@media (max-width: 719px) {
  .interactions-ready .rail { grid-template-columns: minmax(0, 1fr); }
  .rail-svg--h { display: none; }
  .rail-svg--v { display: block; left: 4px; top: 0; width: 4px; height: 100%; }
  .node {
    flex-direction: row;
    gap: 20px;
    min-height: 52px;
    padding: 0;
    text-align: left;
  }
}

.exp-panels {
  display: grid;
  margin-top: clamp(40px, 5vw, 72px);
}
.exp-panel {
  padding-top: 32px;
  border-top: 1px solid var(--hair);
}
.exp-panel + .exp-panel { margin-top: 48px; }
.panel-place { display: block; color: var(--muted); }
.exp-panel { max-width: 60ch; align-self: start; }
.exp-copy { margin-top: 20px; }
.exp-copy p { color: var(--muted); }
.exp-copy p + p { margin-top: 1em; }
html.interactions-ready:not(.world-live) .exp-panels { view-transition-name: exp-panels; }
.interactions-ready .exp-panel {
  grid-area: 1 / 1;
  margin: 0;
  padding-top: 0;
  border-top: 0;
}
.interactions-ready .exp-panel:not(.is-active) { visibility: hidden; }
.interactions-ready .panel-place { display: none; }
@media (max-width: 719px) {
  .interactions-ready .exp-panel:not(.is-active) { display: none; }
}

/* ---------- About ---------- */

.about-grid {
  display: grid;
  gap: 40px;
}
@media (min-width: 840px) {
  .about-grid {
    grid-template-columns: minmax(0, var(--copy-max)) minmax(0, 1fr);
    gap: clamp(48px, 7vw, 120px);
    align-items: center;
  }
  .about-copy { grid-column: 1; grid-row: 1; }
  .portrait { grid-column: 2; grid-row: 1; justify-self: end; width: 100%; }
}
.portrait {
  max-width: 420px;
  border: 1px solid var(--hair);
}
.portrait img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}
.about-copy .h2 { margin-bottom: 28px; }
.about-copy p { max-width: 52ch; }
.about-copy p + p { margin-top: 1em; color: var(--muted); }

/* ---------- Contact ---------- */

.contact-grid {
  display: grid;
  gap: 48px;
}
.contact-title {
  font-family: var(--display);
  font-weight: 450;
  font-size: clamp(48px, 13vw, 72px);
  line-height: .95;
  letter-spacing: -.035em;
}
.contact-intro {
  margin-top: 28px;
  max-width: 36ch;
}
/* the page's last stop also carries the social links and the location line */
.contact-links {
  list-style: none;
  padding: 0;
  margin-top: 20px;
  display: flex;
  flex-wrap: wrap;
  gap: 0 32px;
}
.contact-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--text);
}
.contact-line {
  margin-top: 8px;
  font-size: 14px;
  letter-spacing: .01em;
  color: var(--muted);
}
@media (min-width: 1024px) {
  .contact-grid {
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    gap: clamp(56px, 6vw, 104px);
    align-items: start;
  }
  .contact-lead {
    position: sticky;
    top: calc(var(--header) + 48px);
  }
  .contact-title { font-size: clamp(96px, 10vw, 150px); }
  .contact-title .h2-line { display: block; }
}

/* buttons */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-height: 50px;
  padding: 0 22px;
  border: 1px solid var(--text);
  background: var(--text);
  color: var(--ground);
  font-weight: 600;
  font-size: 16px;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .2s var(--ease), border-color .2s var(--ease), color .2s var(--ease);
}
.btn .i { transition: transform .25s var(--ease); }
.btn:hover { background: var(--signal); border-color: var(--signal); color: var(--ground); }
.btn:hover .i { transform: translateX(3px); }
.btn:disabled { cursor: progress; opacity: .7; }
.btn:disabled:hover { background: var(--text); border-color: var(--text); }
.btn--line { background: transparent; color: var(--text); }
.btn--line:hover { background: var(--text); border-color: var(--text); color: var(--ground); }

/* form panel */
.form-panel {
  container-type: inline-size;
  padding: clamp(24px, 4vw, 48px);
  border: 1px solid var(--hair);
  background: var(--panel);
  scroll-margin-top: calc(var(--header) + 16px);
}
.success-title {
  font-family: var(--heading);
  font-weight: 600;
  font-size: clamp(24px, 2vw, 28px);
  line-height: 1.08;
  letter-spacing: -.02em;
}
.field {
  display: grid;
  gap: 8px;
  margin-top: 22px;
}
.lead-form > .field:first-of-type { margin-top: 0; }
.field-pair { display: grid; column-gap: 20px; }
@container (min-width: 520px) {
  .field-pair { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}
.field label {
  font-weight: 600;
  font-size: 16px;
  line-height: 1.4;
  color: var(--text);
}
.field input,
.field textarea {
  width: 100%;
  min-height: 50px;
  padding: 12px 14px;
  border: 1px solid var(--field-line);
  border-radius: 0;
  background: var(--ground);
  color: var(--text);
  font-size: 16px;
  line-height: 1.5;
  transition: border-color .15s var(--ease);
}
.field textarea { min-height: 168px; resize: vertical; }
.field input:hover,
.field textarea:hover { border-color: #8a8e88; }
.field input:focus-visible,
.field textarea:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 2px;
  border-color: var(--signal);
}
.field ::placeholder { color: var(--muted); opacity: 1; }
.field [aria-invalid="true"] { border-color: var(--signal); }
.form-actions { margin-top: 28px; }
.form-status {
  margin-top: 16px;
  color: var(--muted);
}
.form-status:empty { margin-top: 0; }
.form-status.is-error { color: var(--signal); }
.form-status a { color: var(--text); }
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}
.lead-form { position: relative; }
.success p { margin-top: 10px; color: var(--muted); }
.success:focus { outline: none; }
.success:focus-visible { outline: 2px solid var(--signal); outline-offset: 6px; }
.form-note {
  margin-top: 28px;
  padding-top: 18px;
  border-top: 1px solid var(--hair);
  font-size: 16px;
  color: var(--muted);
}
.direct {
  font-weight: 600;
  font-size: clamp(20px, 2.4vw, 28px);
  line-height: 1.3;
  overflow-wrap: anywhere;
}
.interactions-ready .direct { display: none; }
html:not(.interactions-ready) .lead-form,
html:not(.interactions-ready) .form-note { display: none; }

/* ---------- view transitions ---------- */

::view-transition-old(site-header) { display: none; }
::view-transition-new(site-header) { animation: none; }
::view-transition-group(exp-panels) { animation-duration: .25s; animation-timing-function: var(--ease); }
::view-transition-old(exp-panels) { animation: vt-fade-out .15s var(--ease-in) both; }
::view-transition-new(exp-panels) { animation: vt-fade-in .25s var(--ease) both; }
::view-transition-new(notes-body) { animation: vt-rise .3s var(--ease) both; }
::view-transition-old(notes-body) { animation: vt-fade-out .12s var(--ease-in) both; }
@keyframes vt-fade-out { to { opacity: 0; } }
@keyframes vt-fade-in { from { opacity: 0; } }
@keyframes vt-rise { from { opacity: 0; transform: translateY(12px); } }

/* ---------- scroll-driven reveals (visible by default) ---------- */

@keyframes rv-head {
  from { clip-path: inset(100% -.15em -.3em -.15em); }
  to { clip-path: inset(-.3em -.15em -.3em -.15em); }
}
@keyframes rv-rise {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}
@keyframes rv-wipe {
  from { -webkit-mask-position: 100% 0; mask-position: 100% 0; }
  to { -webkit-mask-position: 0 0; mask-position: 0 0; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    html:not(.world-live) .rv-head {
      animation: rv-head cubic-bezier(.2, .7, .2, 1) both;
      animation-timeline: view();
      animation-range: entry 0% cover 30%;
    }
    html:not(.world-live) .rv {
      animation: rv-rise cubic-bezier(.2, .7, .2, 1) both;
      animation-timeline: view();
      animation-range: entry 0% cover 25%;
    }
    html:not(.world-live) .rv-wipe {
      -webkit-mask-image: linear-gradient(90deg, #000 45%, transparent 55%);
      mask-image: linear-gradient(90deg, #000 45%, transparent 55%);
      -webkit-mask-size: 260% 100%;
      mask-size: 260% 100%;
      -webkit-mask-repeat: no-repeat;
      mask-repeat: no-repeat;
      animation: rv-wipe linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 40%;
    }
  }
}

/* IntersectionObserver fallback, armed by site.js only where scroll timelines are missing */
@media (prefers-reduced-motion: no-preference) {
  .rv-armed .rv,
  .rv-armed .rv-head,
  .rv-armed .rv-wipe {
    transition: opacity .7s var(--ease), transform .7s var(--ease), clip-path .9s var(--ease);
  }
  .rv-armed .rv:not(.in) { opacity: 0; transform: translateY(10px); }
  .rv-armed .rv-head:not(.in) { clip-path: inset(100% -.15em -.3em -.15em); }
  .rv-armed .rv-head.in { clip-path: inset(-.3em -.15em -.3em -.15em); }
  .rv-armed .rv-wipe:not(.in) { clip-path: inset(0 100% 0 0); }
  .rv-armed .rv-wipe.in { clip-path: inset(0 0 0 0); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-property: opacity, color, background-color, border-color, stroke, fill, outline-color, text-decoration-color !important;
  }
  .rail-signal { transition: none; }
}

/* =====================================================================
   The silkscreen layer: copy on the hardware.
   Each content block ("plate", [data-surface]) is wrapped at mount in a
   fixed full-viewport .slot (the perspective, shared by every slot) and a
   .slot-scene (the camera); the plate takes its surface matrix from the
   board's onFrame. Plates stay in DOM and reading order; nothing is cloned.
   Without the world (no script, no WebGL, no onFrame) none of this applies.
   ===================================================================== */

.slot {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  height: 100lvh;
  z-index: 2;
  overflow: visible;
  pointer-events: none;
  perspective-origin: 50% 50%;
}
.slot-scene {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  pointer-events: none;
}
.world-live .plate {
  position: absolute;
  left: 0;
  top: 0;
  margin: 0;
  width: var(--plate-w, 640px);
  height: var(--plate-h, auto);
  max-width: none;
  padding: 28px 32px;
  transform-origin: 0 0;
  backface-visibility: hidden;
  opacity: 0;
  pointer-events: none;
  font-size: 18px;
  line-height: 1.55;
  color: var(--text);
  text-align: left;
  border: 0;
  background: none;
  transition: none;
  animation: none !important;
  clip-path: none !important;
  -webkit-mask: none !important;
  mask: none !important;
}

/* the in-flow containers collapse; each section keeps one stop height per reading pose */
.world-live main .section,
.world-live .hero {
  min-height: 0;
  height: calc(var(--stops, 1) * var(--stop-h));
  padding: 0;
  border: 0;
  overflow: visible;
}
.world-live :is(.hero-content, .status-strip, .what-head, .lanes, .feature, .feature-media, .rows, .exp-stage, .about-grid, .contact-grid) {
  margin-block: 0;
  padding: 0;
  border: 0;
  background: none;
  min-height: 0;
}
.world-live .rail { display: block; margin: 0; }
.world-live :is(.hero-rule, .rail-svg) { display: none; }
.world-live :is(.rv, .rv-head, .rv-wipe) { animation: none !important; opacity: 1; clip-path: none; }

/* plate typography: fixed sizes in plate pixels (never below 16px; headings never below 22px) */
/* 44px keeps "The same responsibility." whole on the 560px experience silkscreen */
.world-live .plate .h2 { font-size: 44px; }
.world-live .plate .h3 { font-size: 28px; }
.world-live .plate .case-title { font-size: 24px; }
.world-live .plate .lead { font-size: 20px; max-width: none; }
.world-live .plate .meta,
.world-live .plate.status-inner { font-size: 16px; }
.world-live .plate .contact-line { font-size: 18px; }
.world-live .plate p { max-width: none; }
.world-live .plate--lid .hero-name { font-size: 150px; }
.world-live .plate--lid .hero-sub { font-size: 24px; }
.world-live .plate--lid .hero-invite { font-size: 18px; max-width: none; }
/* the section heading leads module 1 as its own band, larger than the project title it introduces */
.world-live .plate--module .h2.work-head {
  font-size: 44px;
  line-height: 1.02;
  letter-spacing: -.025em;
  margin: 0 0 32px;
  padding: 0;
  border: 0;
}
.world-live .plate.lane { padding: 12px 20px; }
.world-live .plate.lane .h3 { font-size: 28px; line-height: 1.08; }
.world-live .plate.lane p { margin-top: 8px; line-height: 1.5; }
.world-live .plate--screen .shot-caption { min-height: 0; }
.world-live .plate--module .project-name,
.world-live .plate--module .row-name { font-size: 34px; }
.world-live .plate--module .figures { font-size: 28px; }
.world-live .plate.what-head { display: block; }
.world-live .plate.what-head .lead { margin-top: 20px; padding: 0; }
.world-live .plate.status-inner { flex-direction: row; gap: 4px 24px; padding-block: 20px; }
.world-live .plate--screen { padding: 0; }
.world-live .plate--screen figcaption { margin-top: 0; padding: 2px 4px 0; }
.world-live .plate--photo { padding: 0; border: 1px solid var(--hair); }
/* port tops: one label size for all five, up to three lines with the pad above (KISS Institute for Practical Robotics) */
.world-live .plate--port { padding: 8px 6px; display: flex; align-items: center; justify-content: center; }
.world-live .plate--port .node { color: var(--muted); font-size: var(--port-size, 16px); line-height: 1.3; min-height: 48px; gap: 10px; padding: 0; width: 100%; }
.world-live .plate--port .node[aria-selected="true"] { color: var(--text); }
.world-live .plate--panel .exp-panel { max-width: none; }
.world-live .plate.contact-lead { position: absolute; top: 0; }
.world-live .plate.contact-lead .contact-title { font-size: 96px; }
.world-live .plate.contact-lead .h2-line { display: block; }
.world-live .plate.contact-lead .contact-intro { max-width: none; }
@media (max-width: 719px) {
  .world-live .plate { padding: 20px; font-size: 16px; }
  .world-live .plate .contact-line { font-size: 16px; }
  .world-live .plate .h2 { font-size: 34px; }
  .world-live .plate .h3 { font-size: 24px; }
  .world-live .plate .case-title { font-size: 22px; }
  .world-live .plate .lead { font-size: 18px; }
  .world-live .plate--lid .hero-name { font-size: 82px; }
  .world-live .plate--lid .hero-sub { font-size: 18px; }
  .world-live .plate--lid .hero-invite { font-size: 16px; }
  .world-live .plate--module .project-name,
  .world-live .plate--module .row-name { font-size: 28px; }
  .world-live .plate--module .h2.work-head { font-size: 34px; margin-bottom: 24px; }
  .world-live .plate.lane { padding: 12px 16px; }
  .world-live .plate.lane .h3 { font-size: 24px; }
  .world-live .plate--module .figures { font-size: 24px; }
  .world-live .plate.status-inner { flex-direction: column; }
  .world-live .plate--port { padding: 8px 4px; }
  .world-live .plate--port .node { flex-direction: column; text-align: center; }
  .world-live .plate--screen,
  .world-live .plate--photo { padding: 0; }
  .world-live .plate.contact-lead .contact-title { font-size: 56px; }
}

/* the notes readout: a flat 2D panel tethered to its module by a brass leader */
.readout {
  position: fixed;
  z-index: 4;
  left: 0;
  top: 0;
  width: min(60ch, calc(100vw - 2 * var(--gutter)));
  max-height: calc(100vh - var(--header) - 48px);
  overflow: auto;
  overscroll-behavior: contain;
  padding: 24px 28px;
  background: var(--panel);
  border: 1px solid var(--hair);
  color: var(--text);
  font-size: 18px;
}
.readout .notes-body { padding: 0; }
.readout-close {
  position: sticky;
  top: -8px;
  float: right;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: -12px -14px 0 12px;
  padding: 0;
  border: 0;
  background: var(--panel);
  color: var(--signal);
  cursor: pointer;
}
.readout-close .i { width: 22px; height: 22px; transform: rotate(45deg); }
.readout .case-row:first-child { padding-top: 0; }
.readout .case-title { font-size: 24px; }
@media (max-width: 719px) {
  .readout { font-size: 16px; padding: 20px; max-height: 58vh; }
}
.leader {
  position: fixed;
  inset: 0;
  z-index: 3;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.leader line { stroke: var(--brass-solid); stroke-width: 1; }

/* the form on the board (960px and up): a silkscreen block beside the contact plate. The board draws
   its bracket, so the plate has no fill or border of its own; below 960px it stays in flow as a panel */
.world-live .plate--form {
  display: flex;
  flex-direction: column;
  padding-block: 24px;
}
/* compact on the board, so the pair holds 1:1 below the header on a 664px tall laptop viewport */
.world-live .plate--form .field { gap: 12px; margin-top: 20px; }
.world-live .plate--form .lead-form > .field:first-of-type { margin-top: 0; }
.world-live .plate--form .field label { color: var(--muted); }
/* name and email side by side, so the block stays within one screen at scale 1 */
.world-live .plate--form .field-pair { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
/* the fields print on the board like its silkscreen: muted keeps their outline at 5.9:1 against the face
   (the hair or field-line greys fall under 3:1 on its grain) */
.world-live .plate--form .field input,
.world-live .plate--form .field textarea {
  background: transparent;
  border-color: var(--muted);
}
.world-live .plate--form .field textarea { height: 120px; min-height: 120px; resize: none; }
.world-live .plate--form .field input:hover,
.world-live .plate--form .field textarea:hover { border-color: var(--text); }
.world-live .plate--form .field input:focus-visible,
.world-live .plate--form .field textarea:focus-visible,
.world-live .plate--form .field [aria-invalid="true"] { border-color: var(--signal); }
.world-live .plate--form .btn {
  background: transparent;
  border-color: var(--text);
  color: var(--text);
}
.world-live .plate--form .btn:hover,
.world-live .plate--form .btn:disabled:hover {
  background: var(--text);
  border-color: var(--text);
  color: var(--ground);
}
.world-live .plate--form .form-actions { margin: 24px 0; }
.world-live .plate--form .form-note { margin-top: auto; }
/* the status (a validation message or a failed send) takes the note's row while it has something to say */
.world-live .plate--form .lead-form { position: static; }
.world-live .plate--form .form-status {
  position: absolute;
  left: 32px;
  right: 32px;
  bottom: 24px;
  margin: 0;
  font-size: 16px;
  line-height: 1.5;
}
.world-live .plate--form.has-status .form-note { visibility: hidden; }
@media (max-width: 959px) {
  /* the contact stop parks over a full viewport of padding, so the in-flow form starts at the viewport
     bottom at the stop, rises as the visitor scrolls on, and its bottom meets the viewport bottom at the end */
  .world-live main .section.contact {
    height: auto;
    padding-top: max(calc(var(--stops, 1) * var(--stop-h)), 100vh);
    padding-top: max(calc(var(--stops, 1) * var(--stop-h)), 100lvh);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .world-live .form-panel { position: relative; z-index: 3; }
}

.world-live .hero-content :is(h1, p, a, button) { pointer-events: inherit; }
/* lanes: content centred on the plate so a shorter lane never leaves an empty lower half */
.world-live .plate.lane { display: flex; flex-direction: column; justify-content: center; }
/* module 1 carries the section heading band; its proof sits a little closer to fit the 404 / 450 plate */
.world-live [data-surface="work.mod1"] .proof { margin-top: 10px; }

/* ports read as controls: a pointer, and the label underlines on hover or focus (the active label stays bone with the lit dot) */
.world-live .plate--port,
.world-live .plate--port .node { cursor: pointer; }
.world-live .plate--port .node:hover .node-label,
.world-live .plate--port .node:focus-visible .node-label {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--signal);
  text-underline-offset: .22em;
}
/* phones: the panel plate is at most 540px tall; Moni's two paragraphs must fit at 16px */
@media (max-width: 719px) {
  .world-live .plate--panel { padding: 16px; }
  .world-live .plate--panel .h3 { font-size: 24px; }
  .world-live .plate--panel .exp-copy { margin-top: 12px; }
  .world-live .plate--panel .exp-copy p + p { margin-top: 12px; }
}

/* the 404 page: one column on the ground */
.not-found { min-height: 100vh; min-height: 100svh; display: grid; place-items: center; }
.not-found-main { padding: 96px 0; }
.not-found-main .lead { margin-top: 24px; max-width: 48ch; }
