/* FF Dev Studio — desktop stylesheet.
 *
 * Structure and motion numbers reproduce docs/reference-spec.md §4 — that layer is
 * engineering, measured from the reference. Everything you can see is FF's:
 * palette, faces, type scale, the mark, the cut. See docs/brief.md.
 *
 * Two rules govern this file:
 *   1. Motion never owns resting state. Every element's correct final appearance is
 *      declared here; the engine animates *to* it. A throttled tab, a paused document or
 *      prefers-reduced-motion must still leave a fully readable page.
 *      => elements that animate in start from a class the engine removes, not from a bare
 *         transform that only JS can undo.
 *   2. Focus is always visible. The reference preventDefaults Tab; we do not.
 */

/* ---------------------------------------------------------------- faces */

/* Instrument Sans, instanced at two points on its weight axis, and Instrument
 * Serif for one phrase. Both OFL-1.1; sources and licences in assets/fonts/,
 * built by tools/gen_fonts.py. BRAND_GUIDELINES.md §5 sets the weights:
 * display 520-650 at 92-100% width, label 600-700. */
@font-face {
  font-family: "Display";
  src: url(/static/font/display.woff2) format("woff2");
  font-weight: 620;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "UI";
  src: url(/static/font/ui.woff2) format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Serif";
  src: url(/static/font/serif.woff2) format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

/* Metric-matched fallbacks. Without these the swap moves every line, and on the
 * giant title one line is a third of the viewport.
 * Measured from the built faces: Instrument Sans upm 1000, cap 720, x-height 510,
 * ascent 970, descent 250. Helvetica/Arial cap 716, x-height 519 — so size-adjust
 * splits the difference between matching cap height (100.6%) and matching
 * x-height (98.3%), and the overrides are Instrument Sans's own hhea values. */
@font-face {
  font-family: "Display Fallback";
  src: local("Helvetica Neue"), local("Arial"), local("Liberation Sans");
  size-adjust: 99%;
  ascent-override: 97%;
  descent-override: 25%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "UI Fallback";
  src: local("Helvetica Neue"), local("Arial"), local("Liberation Sans");
  size-adjust: 99%;
  ascent-override: 97%;
  descent-override: 25%;
  line-gap-override: 0%;
}

:root {
  /* BRAND_GUIDELINES.md §4. Browsing sits on Ink; focusing a project inverts the
   * ground to Bone (the colour comes from data/projects.json, and the engine
   * damps between them). Roughly 75% Ink / 20% Bone or project media / 5% signal,
   * which is the ratio the brand asks of an expression-led layout. */
  --bg: #0b0b0a;          /* FF Ink */
  --txt: #f3efe4;         /* FF Bone — 17.14:1 on Ink */
  --quiet: #9b9990;       /* Field Grey lifted for Ink — 6.89:1 */
  --signal: #d9ff43;      /* Signal Lime. Scarce, by rule. */

  --ease: cubic-bezier(0.25, 0.46, 0.45, 0.94); /* easeOutQuad */
  --ease-img: cubic-bezier(0.39, 0.575, 0.565, 1); /* easeOutSine */
  --edge: 50px; /* outer margin */
  --edge-display: 38px; /* the display blocks sit tighter */
  --bottom: 48px;

  /* The cut, DERIVED not chosen. The mark's slash runs 16 across 72 down on the
   * 194x72 master grid, so tan(theta) = 16/72 -> 12.5288deg from vertical.
   *
   * Nothing draws with it at the moment. The angle is already on every page,
   * carried by the two slashes in the mark, and the one decorative use of it —
   * a 34px hairline under EXPLORE — was removed because at that length it read
   * as a leaning stick rather than a cut. The tokens stay so that anything that
   * does need the angle derives it from the mark instead of guessing. */
  --cut-run: 0.222222;
  --cut-angle: 12.5288deg;
  /* Where the bar parks and where it ends up.
   *
   * skewX widens the bar's footprint by up to (height/2)*tan(angle) on each
   * side, and that reach scales with the VIEWPORT HEIGHT, not with the bar's
   * own width. On a landscape desktop it is small beside a 62%-wide bar. On a
   * tall phone it is larger than the bar, so a park of -115% left the top
   * corner of the cut leaning back onto the screen — a lime wedge sitting over
   * the index permanently. Both ends now carry the height term. */
  --cut-park: calc(-115% - 30vh);
  --cut-end: calc(190% + 30vh);

  /* The reference squeezed and shortened its display face to match a commercial
   * typeface it could not ship. FF has no such constraint: the face on the page
   * is FF's own, drawn at its natural width. Both compensations are therefore 1,
   * and the type scale below was re-derived for it — see docs/brief.md. */
  --display-cap: 1;
  --display-squeeze: 1;
}

/* ---------------------------------------------------------------- reset */

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

/* The focused card is a link — see Home.hover in src/scenes.js. The cursor is
 * the whole affordance: it says "clickable" over 1054x602 of project without
 * putting a single pixel on the screen. */
html.over-focus, html.over-focus * { cursor: pointer; }

html {
  background-color: var(--bg);
  -webkit-text-size-adjust: none;
  -webkit-font-smoothing: antialiased;
  cursor: default;
  -webkit-tap-highlight-color: transparent;
}
a, body, div, footer, h1, h2, header, html, img, li, nav, p, span, ul {
  margin: 0;
  padding: 0;
  border: 0;
  font: inherit;
  font-size: 100%;
}
ul, li { list-style: none; }
a { color: inherit; text-decoration: none; }
a, img, picture, svg, video { display: block; }
img, svg, video { width: 100%; height: auto; }
svg { height: 100%; }
body {
  font-family: "UI", "UI Fallback", Helvetica, Arial, sans-serif;
  font-feature-settings: "liga" off;
  font-variant-numeric: tabular-nums;
  color: var(--txt);
}

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

/* The reference has no visible focus at all. Ours does, everywhere — and it is
 * one of the two places Signal Lime is allowed to appear (§4: "a focus ring,
 * cursor response, progress marker or selected control"). Lime alone is 1.16:1
 * on Bone, so the ring keeps a currentColor outline underneath it and the lime
 * sits outside that; the pair reads on Ink and on Bone. */
:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
  box-shadow: 0 0 0 5px var(--signal);
}
.skip {
  position: absolute;
  z-index: 10000;
  top: 8px;
  left: 8px;
  padding: 10px 14px;
  font-size: 11px;
  letter-spacing: 0.04em;
  background: var(--txt);
  color: var(--bg);
  transform: translateY(-200%);
}
.skip:focus { transform: none; }

/* ---------------------------------------------------------------- shell */

#app {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  user-select: none;
}

#gl, #c2d {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ---------------------------------------------------------------- loader */

#load {
  position: absolute;
  z-index: 9998;
  top: 35px;
  left: var(--edge-display);
  font-family: "Display", "Display Fallback", Helvetica, Arial, sans-serif;
  font-size: calc(46px * var(--display-cap));
  line-height: 44px;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
#load > span { float: left; overflow: hidden; }
#load > span:not(:first-child) { margin-left: -2px; }
#load span span {
  display: block;
  padding-right: 4px;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0) scaleX(var(--display-squeeze));
  transform-origin: 0 50%;
  will-change: transform;
}
/* Resting state lives here, in CSS. The engine only writes --rx / --ry. */
#load span span.off { --rx: -110%; }

/* ---------------------------------------------------------------- nav */

/* The nav sits outside #page, so it cannot inherit the routed page's colour.
 * It reads the same value directly instead — a backstop, so an element the
 * engine forgets to recolour is merely un-transitioned rather than invisible. */
#nav {
  pointer-events: none;
  color: var(--title, var(--txt));
  transition: color 160ms var(--ease);
}

/* The wordmark is the recognition mark, drawn from the master paths — never the
 * name typed in a face (BRAND_GUIDELINES.md §3.7). Its clear space is 1X, where
 * X is one F stem: 16/194 of the width, so 8.25% each side. Minimum size for the
 * mark is 32px; at 44px tall it is 118px wide, comfortably clear of that. */
#n0 {
  position: absolute;
  top: 40px;
  left: var(--edge-display);
  height: 34px;
  pointer-events: all;
}
#n0 > span { float: left; overflow: hidden; }
#n0 span span {
  display: block;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
  transform-origin: 0 50%;
  transition: color 160ms var(--ease);
  will-change: color, transform;
}
#n0 span span.off { --rx: -110%; }
.mk { display: block; width: auto; height: 34px; }

.n1 {
  position: absolute;
  top: 40px;
  right: var(--edge);
  overflow: hidden;
}
.n1 a { display: block; pointer-events: all; }
#n1-0, #n1-1 {
  transition: color 160ms var(--ease);
  will-change: transform, color;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
.n1 a.off { --ry: 101%; }
/* the underline under ABOUT / CLOSE */
.n1 a > span { position: relative; display: block; font-size: 12px; line-height: 14px; }
.n1 a > span::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(1);
  transform-origin: 0 50%;
  transition: transform 400ms var(--ease);
}
.n1 a:hover > span::after { transform: scaleX(0); transform-origin: 100% 50%; }

#n2 {
  position: absolute;
  bottom: var(--bottom);
  left: var(--edge);
  font-size: 10px;
  line-height: 13px;
  letter-spacing: 0.06em;
  overflow: hidden;
  pointer-events: none;
}
#n2 > div { overflow: hidden; }
.n2 {
  display: table;
  position: relative;
  transition: color 160ms var(--ease);
  will-change: transform, color;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
.n2.off { --ry: 101%; }
#n3 {
  position: absolute;
  bottom: 47px;
  right: var(--edge);
  text-align: right;
  font-size: 12px;
  line-height: 14px;
}
#n3 > div { overflow: hidden; padding-left: 20px; }
.n3 {
  display: inline-block;
  position: relative;
  transition: color 160ms var(--ease);
  will-change: transform, color;
  pointer-events: all;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
.n3.off { --ry: 101%; }
.n3::after {
  content: "\2197";
  position: absolute;
  top: 0;
  left: 0;
  padding-right: 5px;
  transform: translateX(-100%);
  opacity: 0;
  transition: opacity 200ms var(--ease);
}
.n3:hover::after { opacity: 1; transition: opacity 400ms var(--ease); }

/* ---------------------------------------------------------------- routed page

 * Everything inside #page rides on whatever ground the current mode is showing,
 * and this site inverts: Ink while browsing, Bone once a project is focused. So
 * the routed content takes its colour from --title, the one value the engine
 * already maintains for exactly that question (src/ui.js, Nav.color).
 *
 * Without this every label, rule and glyph in the focused and project modes
 * stays the browsing colour and lands Bone-on-Bone. The reference has the same
 * bug in a milder form — its cards are pale rather than Bone, so its chrome
 * drops to about 1.6:1 there instead of disappearing outright.
 *
 * The 500ms matches the nav's own colour transition and the ground's damping,
 * so the whole page changes polarity as one move. */
#page {
  color: var(--title, var(--txt));
  /* Short on purpose. The switch now happens at the luminance where Ink and Bone
   * are equally readable, so there is nothing to ease across — a long fade would
   * only spend that time passing through a mid-grey that reads on neither. */
  transition: color 160ms var(--ease);
}

/* ---------------------------------------------------------------- giant titles */

/* The title block spans the full viewport height so the two lines can be placed against
 * it with percentages; the lines themselves are only as tall as one line of type. */
/* The title letters are server-rendered stacked at left:0 and given their real
 * x by the engine on the first layout pass, once it knows the viewport. That
 * reposition is a LAYOUT shift, not a transform, so the browser counts it: CLS
 * measured 0.325 on desktop — a poor score caused entirely by type moving into
 * place before anyone could read it.
 *
 * Hidden until placed. A visibility:hidden element's move is not counted, and
 * there is nothing to see at left:0 anyway. `.no-gl` and the noscript block
 * both bring it straight back for the paths where the engine never runs. */
.t { visibility: hidden; }
html.placed .t,
html.no-gl .t { visibility: visible; }

.t {
  position: absolute;
  top: 0;
  left: var(--edge-display);
  width: calc(100vw - var(--edge-display) * 2);
  height: 100%;
  font-family: "Display", "Display Fallback", Helvetica, Arial, sans-serif;
  letter-spacing: -0.024em;
  pointer-events: none;
  /* On a focused project the ground inverts to Bone, where Bone-on-Bone would be
   * invisible. The engine sets --title to the project's own text colour (Ink). */
  color: var(--title, var(--txt));
}
/* One line per title row; each letter is absolutely placed at the x the composition data
 * gives it, and clips its own horizontal wipe. */
.t > div {
  position: absolute;
  left: 0;
  width: 100%;
  overflow: hidden;
}
.t-l {
  position: absolute;
  top: 0;
  display: block;
  overflow: hidden;
  will-change: left;
}
.t-l > span {
  display: block;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0) scaleX(var(--display-squeeze));
  transform-origin: 0 50%;
  will-change: transform;
}
.t-l > span.off { --rx: -101%; }

/* ---------------------------------------------------------------- info blocks */

.i-l {
  position: absolute;
  bottom: 47px;
  left: 50%;
  font-size: 10px;
  line-height: 12px;
  letter-spacing: 0.02em;
  transform: translateX(-100%);
  pointer-events: none;
}
.i-l > div { display: flex; flex-wrap: wrap; overflow: hidden; }
.i-l div div:nth-child(1) { width: 30px; font-size: 6px; }
.i-l div div:nth-child(2) { width: 130px; }
.i-l div div:nth-child(3) { width: 250px; }

.i-r {
  position: absolute;
  bottom: var(--bottom);
  left: calc(50% + 140px);
  font-size: 10px;
  line-height: 10px;
  letter-spacing: 0.02em;
  pointer-events: none;
}
.i-r > div { overflow: hidden; }

.i-l div div, .i-r div div { will-change: transform; transform: translate3d(var(--rx, 0), var(--ry, 0), 0); }
.i-l div div.off, .i-r div div.off { --ry: -101%; }

/* ---------------------------------------------------------------- explore */

.e {
  position: absolute;
  bottom: 49px;
  left: 50%;
  transform: translateX(-50%);
  pointer-events: none;
  text-align: center;
}

/* The hit area, not the ink. The visible mark stays small; the target that
 * carries it is 112x106 rather than 48x82, which is the difference between a
 * control you can hit without aiming and one you cannot. Negative margins keep
 * the layout exactly where it was. */
/* `inherit`, not `all`. Five of these stack on the same point — see
 * Home.exploreTarget in src/scenes.js — and the engine enables exactly one by
 * setting pointer-events on its `.e`. `all` here would defeat that, because a
 * descendant re-enabling pointer-events overrides an ancestor that switched
 * them off. */
.e a {
  display: block;
  padding: 12px 32px;
  margin: -12px -32px;
  pointer-events: inherit;
}

.e-t {
  overflow: hidden;
  font-size: 12px;
  line-height: 15px;
  /* BRAND_GUIDELINES §5, label: 10-13px, 600-700, uppercase, 0.08-0.14em. */
  letter-spacing: 0.12em;
  text-indent: 0.12em; /* cancel the trailing letter-space so it stays centred */
}
.e-t > div {
  position: relative;
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
.e-t > div.off { --ry: -101%; }

/* A rule under the label, so it reads as something you press rather than a
 * caption. Quiet at rest — currentColor at a third — and it goes to full on
 * hover and focus. It is NOT lime: lime on Bone is 1.16:1, so taking it there
 * would lower the contrast rather than raise it.
 *
 * It hangs off the INNER div, the one the reveal translates. Put on `.e-t`
 * itself it does not move with the label, so it stayed painted across the
 * bottom of the home page while the word it belongs to was clipped away. */
.e-t > div::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0.12em;
  bottom: 0;
  height: 1px;
  background: currentColor;
  opacity: 0.32;
  transition: opacity var(--t-nav, 220ms) var(--ease);
}
.e a:hover .e-t > div::after,
.e a:focus-visible .e-t > div::after {
  opacity: 1;
}

/* The arrow. Signal Lime is NOT used here at rest, and that is the rule doing
 * its job rather than an omission: lime on Bone is 1.16:1, so a lime mark on
 * the light grounds is invisible — which is exactly what the skewed rule it
 * replaced turned out to be. §4 says so itself: the accent "may be reduced to
 * zero" where it does not serve, and must never be added just to look branded.
 *
 * So the arrow is currentColor: Ink on Bone at 17.14:1, Bone on Graphite at
 * 13.54:1. Lime returns on hover as a chip UNDER the glyph, which is the one
 * form that reads on any ground — §3.6 lists "Ink on Signal Lime for small
 * active, interactive, or campaign signals" as an approved colourway, and Ink
 * on lime is 17.19:1. */
.e-s {
  margin: 14px auto 0;
  width: 34px;
  height: 34px;
  overflow: hidden;
}
.e-s div { will-change: transform; transform: translate3d(var(--rx, 0), var(--ry, 0), 0); }
.e-s div.off { --ry: -110%; }

.e-chip {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 2px;                 /* §7: square, or a 2-4px radius */
  background: transparent;
  transition: background-color var(--t-nav, 220ms) var(--ease);
}
.e-chip svg { width: 18px; height: 18px; }
.e a:hover .e-chip,
.e a:focus-visible .e-chip {
  background: var(--signal);
  color: var(--ink);
}
.e-t, .e-s { pointer-events: none; }

/* ---------------------------------------------------------------- pagination
 * The rectangles are drawn on #c2d; these are the DOM number labels that wipe in either
 * side of the expanded tick. Row top/height/font-size are set per-frame by the engine. */

.pgn-w {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.pgn {
  position: absolute;
  left: 0;
  width: 100%;
  margin-top: 1px;
  overflow: hidden;
}
.pgn > div { position: absolute; top: 0; overflow: hidden; }
.pgn div div {
  padding: 0 2px;
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
.pgn div div.off { --rx: -100%; }

/* ---------------------------------------------------------------- project page chrome */

#p {
  position: absolute;
  top: 49px;
  left: 50%;
  transform: translateX(-50%);
  pointer-events: none;
  text-align: center;
}
#p-s {
  margin: auto;
  width: 14px;
  height: 14px;
  overflow: hidden;
  text-align: center;
}
#p-s div { will-change: transform; transform: translate3d(var(--rx, 0), var(--ry, 0), 0); }
#p-s div.off { --ry: -110%; }
#p-l { position: relative; width: 3px; overflow: hidden; }
#p-l div {
  position: absolute;
  left: 0;
  width: 1px;
  height: 100%;
  border-right: 1px solid currentColor;
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
#p-l div.off { --ry: -110%; }
#p-t { margin-top: 4px; overflow: hidden; font-size: 10px; line-height: 12px; letter-spacing: 0.02em; }
#p-t > div { will-change: transform; transform: translate3d(var(--rx, 0), var(--ry, 0), 0); }
#p-t > div.off { --ry: -101%; }

#v {
  position: absolute;
  bottom: 44px;
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
  pointer-events: none;
  font-size: 10px;
  line-height: 12px;
  letter-spacing: 0.02em;
}
#v > div { overflow: hidden; }
#v > div > div { will-change: transform; transform: translate3d(var(--rx, 0), var(--ry, 0), 0); }
#v > div > div.off { --ry: -101%; }

/* ---------------------------------------------------------------- media reel */

#w {
  position: absolute;
  z-index: 2;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
#w-l {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
#w-l div {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.w-l-bg, .w-l-img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.w-l-img { opacity: 0; object-fit: cover; will-change: transform; }
.w-l-bg.fx { opacity: 0; transition: opacity 100ms linear 1000ms; }
.w-l-img.fx { opacity: 1; transition: opacity 1000ms var(--ease-img); }

#w-s-w {
  position: absolute;
  top: 50%;
  right: var(--edge);
  width: 8.88889vh;
  height: 65vh;
  transform: translate(0, -50%);
}
/* A <button> with no background declared keeps the UA's `buttonface`, about
 * #efefef. Reel.colour() paints the real one, but it returns early off a
 * project route — so on every other route these ten sat at the browser's light
 * grey and flashed as pale rectangles whenever the rail animated across the
 * screen. Transparent is the resting state; the project route paints over it. */
.w-s {
  position: relative;
  width: 8.88889vh;
  height: 5vh;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  padding: 0;
  pointer-events: all;
  cursor: pointer;
}
.w-s:not(:first-child) { margin-top: 1.66667vh; }
.w-s-img {
  position: absolute;
  top: -0.1px;
  left: -0.1px;
  width: calc(100% + 0.2px);
  height: calc(100% + 0.2px);
  object-fit: cover;
  opacity: 0;
  pointer-events: none;
  will-change: transform;
}
.w-s-img.fx { opacity: 1; transition: opacity 1000ms var(--ease-img); }

#w-a {
  position: absolute;
  top: calc(17.5vh - 6px);
  right: 44px;
  width: calc(8.88889vh + 12px);
  height: calc(5vh + 12px);
  border: 1.34px solid currentColor;
  box-sizing: border-box;
  opacity: 0;
  pointer-events: none;
}

/* ---------------------------------------------------------------- about */

#a {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
#a-left { position: absolute; left: var(--edge); top: 49px; width: 100%; }
#a-nif-w { margin-left: 1px; }
.a-nif::after { content: ""; display: block; clear: both; }
/* Direct children only. The lockup nests three spans of its own inside the wipe
 * wrapper, and a descendant selector here would animate each of its parts
 * separately — which is exactly what section 3.7 forbids doing to the mark. */
.a-nif > span { float: left; overflow: hidden; }
.a-nif > span > span {
  float: left;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
  transform-origin: 0 50%;
  will-change: transform;
}
.a-nif > span > span.off { --rx: -101%; }

/* The horizontal lockup: mark, divider, stacked DEV / STUDIO. Proportions come
 * from the master file (mark 194x72, divider at x=222 of 330), so the whole
 * assembly is driven by one height and cannot drift out of lockup. */
.lk { display: flex; align-items: stretch; height: var(--lk-h, 11vh); }
.lk .mk { height: 100%; }
.lk-r {
  width: calc(var(--lk-h, 11vh) * 0.0278);
  min-width: 2px;
  margin: 0 calc(var(--lk-h, 11vh) * 0.19);
  background: currentColor;
}
.lk-w {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  font-family: "UI", "UI Fallback", Helvetica, Arial, sans-serif;
  font-size: calc(var(--lk-h, 11vh) * 0.42);
  line-height: 0.86;
  letter-spacing: -0.01em;
}

/* The one Instrument Serif phrase on the site (§5). One statement, one word. */
/* Not the Display face. That instance is Instrument Sans at the narrow end of
 * its width axis, drawn narrow for one job — a full-viewport title scattered
 * across 1800px. A statement is ordinary display type and belongs at the
 * brand's published 92-100% width (§5), which is what the UI instance is. */
#a-line {
  padding-top: 4.2vh;
  max-width: 21ch;
  font-family: "UI", "UI Fallback", Helvetica, Arial, sans-serif;
  font-size: calc(2.4vh + 19px);
  line-height: 1.08;
  letter-spacing: -0.028em;
}
#a-line em {
  font-family: "Serif", Georgia, serif;
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.012em;
}
#a-line > span { display: block; overflow: hidden; }
#a-line span span {
  display: block;
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
#a-line span span.off { --ry: 110%; }

#a-p { padding-top: 4.6vh; font-size: 11px; line-height: 14px; color: var(--quiet); }
#a-p > span { display: block; overflow: hidden; }
#a-p span span { display: block; will-change: transform; transform: translate3d(var(--rx, 0), var(--ry, 0), 0); }
#a-p span span.off { --ry: 110%; }

#a-social {
  position: absolute;
  left: var(--edge);
  bottom: calc(6.66667vh + 50px);
  font-size: 12px;
  line-height: 14px;
}
#a-social div { overflow: hidden; }
#a-social a { display: table; pointer-events: all; will-change: transform; transform: translate3d(var(--rx, 0), var(--ry, 0), 0); }
#a-social a.off { --ry: 110%; }

#a-list {
  position: absolute;
  bottom: calc(10.83333vh + 50px);
  right: 0;
  font-size: 11px;
  line-height: 11px;
}
#a-list > li { float: left; width: 230px; }
.a-li { position: relative; height: 385px; }
/* Rows stay in flow. Taking them out of it collapses every row onto the first one's
 * static position, because each subsequent hypothetical box ignores the ones before it. */
.a-li li { position: relative; padding-left: 12px; overflow: hidden; }
.a-li span { display: inline-block; }
.a-li span:first-child { position: absolute; top: 0; left: -17px; text-align: right; width: 24px; }
.a-li span:last-child { padding-left: 24px; }
.a-li div { will-change: transform; transform: translate3d(var(--rx, 0), var(--ry, 0), 0); }
.a-li div.off { --ry: 110%; }

/* Held clear of #a-rights by its own width plus a gap. The reference parks this
 * at right: 0 and relies on its label being short enough never to reach the
 * rights line; ours is a sentence of provenance, and at 1440 it wrapped to two
 * lines and ran straight through "ALL RIGHTS RESERVED". */
#a-design {
  position: absolute;
  bottom: var(--bottom);
  right: calc(var(--edge) + 140px);
  width: 678px;
  font-size: 10px;
  line-height: 10px;
  overflow: hidden;
}
#a-design a { display: table; pointer-events: all; will-change: transform; transform: translate3d(var(--rx, 0), var(--ry, 0), 0); }
#a-design a.off { --ry: 101%; }

#a-rights {
  position: absolute;
  bottom: var(--bottom);
  right: var(--edge);
  font-size: 10px;
  line-height: 10px;
  text-align: right;
}
#a-rights > div { overflow: hidden; }
#a-rights div div { display: inline-block; will-change: transform; transform: translate3d(var(--rx, 0), var(--ry, 0), 0); }
#a-rights div div.off { --ry: 101%; }

/* ---------------------------------------------------------------- pricing */

/* ROUTE GATING, and it is not belt-and-braces — it is the fix.
 *
 * Every routed panel ships in every fragment; what keeps a panel invisible off
 * its own route is that all of its ink lives inside an inner div the reveal
 * translates out of a clipping box. That works for text. It does NOT work for a
 * border, or a `::before` bullet, or anything painted by the box itself — those
 * are on the element the reveal leaves exactly where it is. So the pricing
 * cards drew their outlines and their little dashes across the home page: three
 * empty rectangles, one of them lime.
 *
 * Moving each one onto a revealed child would fix these three. Gating the panel
 * on the route fixes every one of them, including the next one someone adds. */
#pr {
  visibility: hidden;
  opacity: 0;
}
#page[data-page="pricing"] #pr {
  visibility: visible;
  opacity: 1;
}
/* Same guard on About, which has no such leak today only because everything in
 * it happens to be text. */
#a { visibility: hidden; opacity: 0; }
#page[data-page="about"] #a { visibility: visible; opacity: 1; }

/* The control on the index: a sibling of EXPLORE, one slot to its left, and the
 * plus rather than the arrow so they are not read as the same action.
 *
 * It shares the bottom-centre slot with EXPLORE, so it is gated twice: by route
 * here, and by MODE in the engine — EXPLORE belongs to a focused card and this
 * belongs to the strip, and they must never be in that slot together. */
.prc { visibility: hidden; }
#page[data-page="home"] .prc { visibility: visible; }
.prc {
  position: absolute;
  bottom: 49px;
  left: 50%;
  transform: translateX(-50%);
  pointer-events: none;
  text-align: center;
}
.prc a {
  display: block;
  padding: 12px 32px;
  margin: -12px -32px;
  pointer-events: inherit;
}

/* The panel. One non-scrolling viewport, like every other route here: the giant
 * figure and its terms hold the left, the three care plans the right, and the
 * two fact columns the floor. Nothing scrolls; short viewports shed instead. */
#pr {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* Clear of the nav. At top: 49px the label and the figure were printed straight
 * over the //FF mark, which sits at the same origin. */
#pr-left { position: absolute; left: var(--edge); top: 15vh; }
#pr-cap, #pr-plans-t { overflow: hidden; font-size: 10px; line-height: 13px; letter-spacing: 0.12em; }
#pr-cap > div, #pr-plans-t > div {
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
#pr-cap > div.off, #pr-plans-t > div.off { --ry: -101%; }

/* The figure is set like a project title — per letter, same wipe, same face —
 * because it is the one number the whole page is about. */
/* The one number the page is about, so it is sized like one. It had no
 * font-size at all and inherited the body's, which is how RM1,000 — the whole
 * point of the route — ended up as 12px of type behind the wordmark. */
#pr-fig-w {
  margin-top: 8px;
  margin-left: -2px;
  font-family: "Display", "Display Fallback", Helvetica, Arial, sans-serif;
  font-size: calc(13vh + 40px);
  line-height: 0.82;
  letter-spacing: -0.035em;
}
.pr-fig::after { content: ""; display: block; clear: both; }
.pr-fig > span { float: left; overflow: hidden; }
.pr-fig > span > span {
  float: left;
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
.pr-fig > span > span.off { --rx: -101%; }

#pr-band {
  padding-top: 3.2vh;
  max-width: 26ch;
  font-family: "UI", "UI Fallback", Helvetica, Arial, sans-serif;
  font-size: calc(1.5vh + 12px);
  line-height: 1.25;
  letter-spacing: -0.02em;
}
#pr-band > span { display: block; overflow: hidden; }
#pr-band span span {
  display: block;
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
#pr-band span span.off { --ry: 110%; }

#pr-terms { margin-top: 3vh; font-size: 10px; line-height: 16px; letter-spacing: 0.1em; }
#pr-terms li { overflow: hidden; }
#pr-terms li > div {
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
#pr-terms li > div.off { --ry: 110%; }

/* ---- the three plans ---- */

#pr-plans-w { position: absolute; right: var(--edge); top: 15vh; width: 46vw; max-width: 760px; }
#pr-plans { display: flex; align-items: stretch; gap: 1.2vw; margin-top: 12px; }
/* stretch, and no fixed height: a card sized by its own rows cannot clip them,
 * and the three stay level because the flex row stretches the short ones. */
.pr-plan { flex: 1 1 0; min-width: 0; display: flex; }

.pr-card {
  height: 100%;
  padding: 14px 14px 16px;
  border: 1px solid currentColor;
  /* §7: square, or 2-4px. Not a soft SaaS card. */
  border-radius: 2px;
}
/* One marked plan, and it is marked with the accent as an active state rather
   than with a heavier box. §4 allows exactly one primary signal. */
.pr-pick { border-color: var(--signal); }

.pr-card > div { overflow: hidden; }
.pr-card > div > div {
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
.pr-card > div > div.off { --ry: 110%; }

.pr-h { font-size: 10px; line-height: 15px; letter-spacing: 0.12em; }
.pr-h > div { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.pr-tag { color: var(--signal); }
.pr-pick .pr-tag { padding: 1px 5px; border-radius: 2px; background: var(--signal); color: var(--ink); }

.pr-p {
  margin-top: 8px;
  font-family: "Display", "Display Fallback", Helvetica, Arial, sans-serif;
  font-size: calc(2.6vh + 14px);
  line-height: 1.02;
  letter-spacing: -0.03em;
}
.pr-per { padding-left: 4px; font-family: "UI", "UI Fallback", sans-serif; font-size: 9px; letter-spacing: 0.1em; }
.pr-y { margin-top: 4px; font-size: 9px; line-height: 13px; letter-spacing: 0.08em; opacity: 0.62; }
.pr-lead { margin-top: 10px; font-size: 11px; line-height: 15px; }

.pr-rows { margin-top: 12px; font-size: 9px; line-height: 15px; letter-spacing: 0.04em; }
.pr-rows li { position: relative; padding-left: 10px; overflow: hidden; }
.pr-rows li::before { content: ""; position: absolute; left: 0; top: 7px; width: 4px; height: 1px; background: currentColor; }
.pr-rows li > div {
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
.pr-rows li > div.off { --ry: 110%; }

/* ---- the two fact columns, on the floor ---- */

#pr-list {
  position: absolute;
  left: var(--edge);
  bottom: calc(10vh + 46px);
  display: flex;
  gap: 3vw;
  font-size: 9px;
  line-height: 13px;
  letter-spacing: 0.04em;
}
#pr-list > li { width: 250px; }
.pr-li li { position: relative; overflow: hidden; }
.pr-li li > div {
  display: flex;
  gap: 8px;
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
.pr-li li > div.off { --ry: 110%; }
.pr-li li:first-child { margin-bottom: 6px; }
.pr-li li > div > span:first-child { width: 10px; flex: none; opacity: 0.55; }

/* Bottom centre, not bottom right: at right: var(--edge) it printed straight
 * through the nav's own social column. Centre is the slot EXPLORE and PRICING
 * use, and on this route both are gated off, so it is free and it is the
 * strongest place for the one action on the page. */
#pr-cta {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: var(--bottom);
  overflow: hidden;
  font-size: 11px;
  line-height: 18px;
  letter-spacing: 0.12em;
  text-align: center;
}
#pr-cta a {
  display: block;
  padding: 10px 26px;
  margin: -10px -26px;
  pointer-events: all;
  will-change: transform;
  transform: translate3d(var(--rx, 0), var(--ry, 0), 0);
}
#pr-cta a.off { --ry: 110%; }

@media (max-height: 860px) { #pr-list > li:last-child { display: none; } }
@media (max-height: 740px) { #pr-list { display: none; } }
@media (max-width: 1250px) { #pr-list { display: none; } #pr-plans-w { width: 52vw; } }
@media (max-width: 1000px) { #pr-band { display: none; } }

/* ---------------------------------------------------------------- the cut
 * One bar at the mark's own angle, swept across the viewport on the way into
 * and out of pricing. This is the site's only full-scale use of --cut-angle and
 * the only place Signal Lime covers real area — which §4 permits precisely
 * here: "a brief motion frame or narrow slash-led reveal". It is a transition,
 * never a resting state, so it carries `pointer-events: none` and sits above
 * everything for the ~700ms it exists. */
#cut {
  position: absolute;
  z-index: 9999;
  top: -10%;
  left: 0;
  width: 62%;
  height: 120%;
  background: var(--signal);
  transform: translate3d(var(--cut-park), 0, 0) skewX(calc(-1 * var(--cut-angle)));
  will-change: transform;
  pointer-events: none;
}

/* ---------------------------------------------------------------- 404 */

#p404 {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 18px;
  text-align: center;
}
.p404-big {
  font-family: "Display", "Display Fallback", Helvetica, Arial, sans-serif;
  font-size: calc(120px * var(--display-cap));
  line-height: 1;
}
.p404-big span { display: block; transform: scaleX(var(--display-squeeze)); }
#p404 a { padding: 20px; font-size: 12px; letter-spacing: 0.04em; text-decoration: underline; }

/* ---------------------------------------------------------------- responsive
 * Height- and aspect-driven, because the layout is one non-scrolling viewport.
 * Progressive shedding, not reflow — spec §4. */

@media (max-width: 700px) { #c2d, .pgn-w { display: none; } }

/* The lockup is artwork plus set type, so it scales by height, not font-size.
 * §3.5 puts the horizontal lockup's minimum at 160px wide; the mark alone is
 * 2.69x its own height, so even the shortest step here clears it four times over. */
@media (min-height: 850px) { #a-nif-w { --lk-h: 13vh; } }
@media (min-height: 750px) and (max-height: 850px) { #a-nif-w { --lk-h: 11vh; } }
@media (max-height: 750px) { #a-nif-w { --lk-h: 9.5vh; } }
@media (max-height: 700px) { #a-p { display: none; } }
@media (max-height: 640px) { #a-line { display: none; } }
@media (max-height: 850px) { #a-social div:nth-child(4), #a-social div:nth-child(5) { display: none; } }
@media (max-height: 600px) { #a-list { display: none; } }

@media (min-width: 1800px) { #a-list > li { width: 260px; } #a-design { width: 1028px; } }
@media (min-width: 1550px) and (max-width: 1800px) { #a-design { width: 908px; } }
@media (min-width: 1250px) and (max-width: 1550px) { #a-design { width: 620px; } }
@media (max-width: 1550px) { #a-list > li:last-child { display: none; } }
@media (max-width: 1250px) { #a-list > li:nth-child(3) { display: none; } #a-design { display: none; } }
@media (max-width: 1050px) { #a-list > li { display: none; } }
@media (max-width: 1350px) { .i-l, .i-r { display: none; } }
@media (max-width: 1150px) { #w-a, #w-s-w { display: none; } }

/* The single aspect breakpoint — switches the giant-title scale between vh and vw. */
/* The titles are uppercase, so the box only has to hold cap height. Measured
 * out of display.woff2, the ink of every uppercase letter these titles use runs
 * -0.010 to 0.730em — 0.740 tall, against 0.950 for the lowercase set, and
 * almost exactly centred on the em box rather than hanging below it.
 *
 * That symmetry collapses the two constants into one: with half-leading, ink
 * sits L/2 - 0.37F from the top and L/2 + 0.37F from it, so a box of
 * H = L = 0.74F + m centres the ink with m/2 of air either side and clips
 * nothing. F = 31vh is the largest size at which the widest line (SMOOTH) still
 * has slack to scatter into: it packs 82% of the block. */
@media (min-aspect-ratio: 1660/1200) {
  .t {
    line-height: 24.44vh;                         /* 0.74F + m */
    font-size: calc(31vh * var(--display-cap));   /* F */
  }
  .t > div { height: 24.44vh; }                   /* same as the line box */
  .tt { top: calc(50% - 29.44vh); }               /* .tb minus one box */
  .tb { top: calc(50% - 5vh); }
  .t > div { margin-left: -2.75559vh; }
  .t-l > span { padding-left: 2.75559vh; padding-right: 2.75559vh; }
  #w-l { width: 96.2963vh; height: 54.16667vh; }
}
@media (max-aspect-ratio: 1660/1200) {
  /* Same construction as the vh branch, one step smaller. A tall viewport gives
   * the title block proportionally less width, and at the vh branch's size the
   * runtime collision guard had to nudge letters on the two longest titles —
   * they still fitted, but the authored scatter was being overwritten. */
  .t {
    line-height: 15.374vw;
    font-size: calc(19.5vw * var(--display-cap));
  }
  .t > div { height: 15.374vw; }
  .tt { top: calc(50% - 18.98846vw); }
  .tb { top: calc(50% - 3.61446vw); }
  .t > div { margin-left: -1.73336vw; }
  .t-l > span { padding-left: 1.73336vw; padding-right: 1.73336vw; }
  #w-l { width: 69.61178vw; height: 39.15663vw; }
}

@media (min-height: 760px) { .e-l { height: 34px; } }
@media (min-height: 700px) and (max-height: 760px) { .e-l { height: 3.4vh; } }
@media (min-height: 660px) and (max-height: 700px) { .e-l { height: 2.5vh; } }
@media (max-height: 660px) { .e-l { height: 2vh; } }

@media (min-height: 870px) { #p-l { margin: 11px auto 0; height: 16px; } }
@media (min-height: 810px) and (max-height: 870px) { #p-l { margin: 11px auto 0; height: 1.2vh; } }
@media (max-height: 810px) { #p-l { height: 0; } }

/* ---------------------------------------------------------------- reduced motion
 * The reference has none. Everything here is motion, so this matters more than usual:
 * kill the transitions, and the engine snaps its damped values straight to target. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
  /* Resting state is a custom property, so reduced motion is one rule: put everything at
   * its final position and let the engine's snap-to-target do the rest. */
  .off, [style*="--rx"], [style*="--ry"] { --rx: 0 !important; --ry: 0 !important; }
}

/* ------------------------------------------------------------------ portrait
 *
 * The engine runs on a phone, so the furniture around it has to fit one. This
 * is not a "mobile skin": the strip, the modes, the cut and the damping are all
 * exactly the same code. What changes is the frame — the margins, the type
 * sizes, and where the three bottom blocks sit, because on a desktop they share
 * one row and at 390px they cannot.
 *
 * Dragging is the interaction, so the document must not claim the gesture:
 * without touch-action the browser starts a scroll and cancels the pointer
 * stream mid-drag, and the strip stutters or stops. The two document panels get
 * pan-y back, since they are the only things here that can overflow. */
@media (max-width: 900px) and (orientation: portrait) {
  :root {
    --edge: 22px;
    --edge-display: 18px;
    --bottom: 24px;
  }

  /* touch-action is an INTERSECTION down the ancestor chain: a child cannot
   * re-enable what a parent forbade. `none` on #app with `pan-y` on the panel
   * inside it therefore gave pan-y to nothing, and neither document panel could
   * be scrolled at all — pricing showed its first screen and stopped.
   *
   * So it is set per route on #app itself. The strip and the reel both want the
   * browser to keep its hands off horizontal gestures; the two document panels
   * want ordinary vertical scrolling and have no drag of their own, because
   * Home only binds its pointer handlers on the index. */
  #app, #gl { touch-action: none; overscroll-behavior: none; }
  html:has(#page[data-page="pricing"]) #app,
  html:has(#page[data-page="about"]) #app { touch-action: pan-y; }
  html:has(#page[data-page="pricing"]) #gl,
  html:has(#page[data-page="about"]) #gl { touch-action: pan-y; }

  .mk { height: 26px; }
  #n0 { top: 26px; }
  .n1 { top: 28px; }
  .n1 a > span { font-size: 11px; line-height: 13px; }
  #n2 { font-size: 9px; line-height: 12px; letter-spacing: 0.05em; }
  .n3 { font-size: 9px; line-height: 13px; letter-spacing: 0.05em; }

  /* The centre control lifts clear of the two bottom corners rather than
   * sitting between them, where at this width it collided with both. */
  .e, .prc { bottom: calc(var(--bottom) + 46px); }

  /* The project's two info blocks flank the centre on a desktop. There is no
   * room to flank anything at 390px, so they take an edge each on the row above
   * the control — same reading order, left before right. */
  .i-l {
    left: var(--edge);
    transform: none;
    bottom: calc(var(--bottom) + 104px);
    font-size: 9px;
    line-height: 12px;
  }
  .i-r {
    left: auto;
    right: var(--edge);
    bottom: calc(var(--bottom) + 104px);
    text-align: right;
    font-size: 9px;
    line-height: 12px;
  }
}

/* ---------------------------------------------- portrait: title and reel size
 *
 * On a desktop the focused card is 1054 wide and the title scatters ACROSS it —
 * the type sits over the work, which is the whole look. At 390 the card is 343
 * wide and 196 tall and the same construction put 76px letters straight through
 * the middle of it: the title unreadable, the screenshot hidden, and the one
 * thing the page exists to show obscured by its own label.
 *
 * So on a phone the title keeps its size and moves ABOVE the card instead of
 * over it. The two lines stack clear of the card's top edge with a gap, which
 * is the same reading order and the same type, just not on top of the subject.
 *
 * The numbers: a title line box is 15.374vw, the focused card is 0.88vw wide at
 * 1054:602 so half its height is 25.1vw, and 5vw of air sits between them. */
@media (max-width: 900px) and (orientation: portrait) {
  .tb { top: calc(50% - 45.5vw); }
  .tt { top: calc(50% - 60.9vw); }

  /* The project page's image was 69.6vw — 271px on a 390 screen, floating in
   * the middle of a mostly empty page. Width is the scarce axis on a phone, so
   * it takes nearly all of it. */
  #w-l { width: 92vw; height: 51.75vw; }
}

/* ------------------------------------------------- portrait: the two panels
 *
 * Pricing and About are desktop documents: a figure beside three cards, a
 * lockup sized to a wide viewport. Neither survives 390px unchanged — pricing
 * put its giant figure straight through the plan cards and ran all three off
 * the right edge, and About's horizontal lockup was cut off mid-word.
 *
 * A phone gets the same content in one column that scrolls. This is the one
 * place the phone genuinely differs from the desktop, and it differs because a
 * price list is a document and a column is what a document wants — not because
 * the phone is being given a lesser version of it. */
@media (max-width: 900px) and (orientation: portrait) {
  /* Both panels are pointer-events: none on the desktop — they are overlays,
   * and only the handful of links inside them opt back in. That is fine for a
   * layout that never scrolls and fatal for one that does: the touch fell
   * straight through the panel to #app, which is overflow: hidden, so the
   * gesture had nothing to scroll and pricing showed its first screen forever.
   *
   * Safe to turn on, because these two only exist on their own routes here —
   * off-route the panel is visibility: hidden, which takes its hit-testing with
   * it — and Home binds no drag on either route. */
  #pr, #a { pointer-events: auto; }

  /* ...which then swallowed the nav. #nav sits before #page in the DOM and is
   * unpositioned, so its links and the panel are both in the positioned layer
   * and the panel — later in DOM order, z-index auto — paints over them. With
   * the panel finally catching touches, that meant CLOSE could not be tapped:
   * you could get into About and pricing and not back out.
   *
   * The nav's blocks are already absolutely positioned, so a z-index lifts them
   * without touching the layout. Putting it on #nav itself would need
   * position: relative, which would re-anchor every one of those children to a
   * zero-height static box — the same trap that clipped the wordmark. */
  #n0, .n1, #n2, #n3 { z-index: 20; }

  #pr {
    inset: 0;
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
    padding: 92px var(--edge) 132px;
  }
  #pr-left, #pr-plans-w, #pr-list, #pr-cta {
    position: static;
    left: auto; right: auto; top: auto; bottom: auto;
    width: auto; max-width: none;
    transform: none;
    text-align: left;
  }
  #pr-fig-w { font-size: 19vw; line-height: 0.86; }
  #pr-plans-w { margin-top: 11vw; }
  #pr-plans { flex-direction: column; gap: 4vw; }
  #pr-band { display: block; }
  /* The two fact columns become two blocks, one after the other. */
  #pr-list { display: block; margin-top: 11vw; }
  #pr-list > li { width: auto; margin-bottom: 7vw; }
  #pr-cta { margin-top: 9vw; }
  #pr-cta a { display: inline-flex; align-items: center; min-height: 44px; padding: 0; margin: 0; }

  /* About's lockup is sized for a wide viewport and was running off the edge.
   * Sized from the width here, since width is what it overflowed. */
  #a-nif-w { --lk-h: 7.6vw; }
}

/* ------------------------------------------- portrait: panels vs the nav frame
 *
 * The nav caption and the social column are fixed to the bottom corners, which
 * is right over a strip that does not scroll and wrong over a panel that does:
 * on pricing they sat on top of the plan cards as they passed underneath.
 *
 * They come off for the two scrolling routes. Nothing is lost — pricing carries
 * START A PROJECT and About carries the same three links in its own column, so
 * the contact route is still one tap either way. */
@media (max-width: 900px) and (orientation: portrait) {
  html:has(#page[data-page="pricing"]) #n2,
  html:has(#page[data-page="pricing"]) #n3,
  html:has(#page[data-page="about"]) #n2,
  html:has(#page[data-page="about"]) #n3 { display: none; }

  /* About is a document too, and at 390 its blocks were laid out for a wide
   * frame — the lists ended up off the bottom and the page read as a headline
   * over half a screen of nothing. Same treatment as pricing. */
  #a {
    inset: 0;
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
    padding: 92px var(--edge) 96px;
  }
  #a-left, #a-list, #a-social, #a-design, #a-rights, #a-line, #a-p {
    position: static;
    left: auto; right: auto; top: auto; bottom: auto;
    width: auto; max-width: none;
    transform: none;
  }
  #a-list { display: block; margin-top: 10vw; }
  #a-list > li { width: auto; margin-bottom: 7vw; }
  #a-social { margin-top: 10vw; }
  /* Each bio line is its own block, set to break where a wide measure wants it.
   * At 390 every one of them wraps on its own and leaves a short remainder —
   * "FOUNDERS AND" alone on a line, then a new block under it. Inline here, so
   * the four lines flow as one paragraph and break where this measure wants. */
  #a-p > * { display: inline; }
  #a-p > *::after { content: " "; }
  #a-design, #a-rights { margin-top: 8vw; }
}

/* --------------------------------------------- portrait: undo the desktop shed
 *
 * A stack of max-width breakpoints sheds content as a DESKTOP window narrows —
 * the About lists at 1050, the provenance line at 1250, the project's two info
 * blocks at 1350. That was the right call when narrow meant a cramped desktop
 * and the phone got a different document entirely. Now the phone runs this
 * layout, and those rules were quietly deleting most of the page: About lost
 * every list item, a focused project lost its year, type, role and client.
 *
 * A phone is not a small desktop. It gets the content back, in one column. */
@media (max-width: 900px) and (orientation: portrait) {
  #a-list > li,
  #a-list > li:nth-child(3),
  #a-list > li:last-child { display: block; float: none; width: auto; }
  #a-design { display: block; width: auto; }
  /* .i-l and .i-r stay hidden. They are two columns of one table — labels on
   * the left, values on the right — and at 390 there is no room to set them
   * beside each other: the values wrap straight back through the labels. Worse,
   * they belong to a focused project but exist in every fragment, so turning
   * them on unconditionally printed a project's YEAR/TYPE/ROLE across the
   * bottom of About.
   *
   * The narrow-window breakpoint that hides them was right, and it is right for
   * the same reason here. A focused card on a phone is title, card, EXPLORE —
   * and the full table is one tap away on the project page, which has the room
   * for it. */
  /* #a-line is the statement — "Websites made impossible to ignore" — not a
   * rule between blocks, which is what the name suggested and what an earlier
   * pass here wrongly hid. */
  #a-line { display: block; margin-top: 6vw; }
}

/* ------------------------------------------------------ portrait: hit areas
 *
 * The nav is set at 10-12px, which is the right size to READ at and nowhere
 * near the right size to HIT: ABOUT measured 39x13, EMAIL 30x13.
 *
 * The type does not change. An invisible pseudo-element centred on each control
 * does the catching, and `max(100%, N)` never shrinks anything.
 *
 * TWO THINGS LEARNED THE HARD WAY HERE.
 *
 * `position: relative` is not free. Adding it to #n0 made the mark's absolutely
 * positioned inner spans resolve against the anchor instead of their intended
 * container, and the wordmark's first slash was clipped off. Only elements
 * whose descendants do not depend on the containing block get it.
 *
 * And 44px is not always the right number. Forcing 44px ROWS on the stacked
 * link columns grew them until the socials collided with the PRICING control.
 * Standalone controls get 44; a stacked list gets 26, which clears WCAG 2.5.8's
 * 24px minimum without pushing the layout around. */
@media (max-width: 900px) and (orientation: portrait) {
  #nav, #nav a, #app a, .skip { touch-action: manipulation; }

  /* Standalone controls — one each, nothing stacked under them. */
  #n1-0, #n1-1, .prc a, .e a, .skip, #a-design a, #v a, #li .e a { position: relative; }
  #n1-0::before,
  #n1-1::before,
  .prc a::before,
  .e a::before,
  .skip::before,
  #a-design a::before,
  #v a::before,
  #li .e a::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: max(100%, 44px);
    height: max(100%, 44px);
    transform: translate(-50%, -50%);
  }

  /* Stacked lists: 26px rows and 26px hit areas. Above the 24px floor, and the
   * columns stay the size the design drew them. */
  .n3, #a-social > div { line-height: 26px; }
  #n3 a, #a-social a { position: relative; display: block; }
  #n3 a::before,
  #a-social a::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: max(100%, 44px);
    height: 26px;
    transform: translate(-50%, -50%);
  }
}