/* ------------------------------------------------------------------ */
/* Fonts, self-hosted. Same four woff2 files v1 uses, redeclared here  */
/* on purpose: v2 is a self-contained iteration, so it does not import */
/* styles.css and the live page keeps exactly one stylesheet request.  */
/*                                                                     */
/* The style reference specifies PPNeueMontreal and names Inter as the  */
/* substitute. Geist is used instead: it is already in the repo, needs  */
/* no network request, and its geometric grotesque skeleton is a much   */
/* closer match to PPNeueMontreal than Inter's. The convention that     */
/* matters is preserved, not the file name: weight 200 for 18px body,   */
/* weight 400 for every headline, weight 600 for 14px uppercase labels. */
/* ------------------------------------------------------------------ */
@font-face {
  font-family: 'Geist Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('./fonts/geist-latin-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Geist Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('./fonts/geist-latin-ext-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
    U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Geist Mono carries every headline. A fixed grid is construction you can  */
/* see — one cell per glyph, the way the hero field is built from vertices  */
/* — which is the one thing a neutral grotesque could not say here. Both    */
/* files were already in the repo, so the display face costs no new         */
/* request. Weight 300, not 400: monospace gains apparent weight at display */
/* sizes and 400 reads as bold at 96px.                                     */
@font-face {
  font-family: 'Geist Mono Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('./fonts/geist-mono-latin-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Geist Mono Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('./fonts/geist-mono-latin-ext-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
    U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ------------------------------------------------------------------ */
/* Tokens                                                              */
/* Single dark theme, locked. Pure #000000 is deliberate and comes      */
/* straight from the style reference: the void is the surface, there    */
/* are no dark-grey panels and no card fills anywhere on this page.     */
/* Accent is locked to Electric Iris for every filled action; Saffron   */
/* Spark is labels only; Deep Verdant appears only inside the canvas.   */
/* The home page (body.home) goes further, on request: the logo's four   */
/* triangle colours tint its icons, rules, glows and timeline, so it     */
/* carries lines and colour where the blog and service pages do not.     */
/* Shape system: interactive elements are full pills, everything else   */
/* is 24px. There are no bordered containers to apply it to.            */
/* ------------------------------------------------------------------ */
:root {
  --void: #000000;
  --bone: #ffffff;
  --ash: #9a9a9a;
  --silver: #bdbdbd;
  --iris: #8052ff;
  --iris-lift: #916aff;
  --saffron: #ffb829;
  /* The logo's other triangle colours. Only the Work page's backdrops and */
  /* section labels use them; everywhere else the accent is still violet.  */
  --rose: #ff5fd2;
  --azure: #4d7cff;
  --lilac: #a78bff;

  --font-sans: 'Geist Variable', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-display: 'Geist Mono Variable', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Type scale. Reference px values are the desktop ceiling; the floors  */
  /* keep the display sizes from breaking a 360px viewport. Tracking is   */
  /* in em so it stays at -0.04em through the whole clamp range.          */
  /* Ceilings pulled in from the grotesque settings: a monospace sets every
     glyph on the same advance, so the same string runs noticeably wider and
     the old maxima overflowed their columns. */
  --text-display: clamp(2.75rem, 7.2vw, 96px);
  --text-heading-lg: clamp(1.625rem, 3.7vw, 52px);
  --text-heading: clamp(1.875rem, 3.8vw, 42px);
  --text-heading-sm: clamp(1.625rem, 3.1vw, 36px);
  --text-subheading: clamp(1.5rem, 2.5vw, 31px);
  --text-body: 18px;
  --text-label: 14px;
  /* Positive, where the grotesque wanted -0.04em. Monospace already carries
     its own sidebearings; closing it up fights the grid that is the whole
     reason this face is here. */
  --tracking-display: 0.02em;

  --page-max: 1280px;
  --gutter: clamp(24px, 5vw, 60px);
  --nav-h: 72px;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Everything readable sits above the field. `main` is not positioned by
   default, so without this the fixed canvas would paint over the sections. */
main {
  position: relative;
  z-index: 1;
}

html {
  scroll-behavior: smooth;
  /* The fixed nav must not cover an anchor target. */
  scroll-padding-top: calc(var(--nav-h) + 24px);
}

body {
  background: var(--void);
  color: var(--bone);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: 200;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

a {
  color: inherit;
}

::selection {
  background: var(--iris);
  color: var(--bone);
}

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

.shell {
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.shell--narrow {
  max-width: 860px;
}

/* Small uppercase label. Rationed on purpose: the hero has one and the   */
/* process section has one. Platforms and the closing section have none,  */
/* so the page does not announce every section the same way.              */
.label {
  font-size: var(--text-label);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.025em;
  text-transform: uppercase;
  color: var(--ash);
}

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

.display,
.heading {
  font-family: var(--font-display);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
  line-height: 1.1;
  text-wrap: balance;
}

/* Home page headlines are medium (500), not light (300): the colour layer  */
/* below them needs a heavier line to hold its own. Every other page keeps  */
/* the 300 above.                                                            */
.home .hero__title,
.home .heading,
.home .display,
.home .cascade__name,
.home .stage__title {
  font-weight: 500;
}

.display {
  font-size: var(--text-display);
}

.heading {
  font-size: var(--text-heading);
  max-width: 18ch;
}

.lede {
  font-size: var(--text-body);
  font-weight: 200;
  color: var(--silver);
  max-width: 46ch;
}

/* ------------------------------------------------------------------ */
/* Nav                                                                 */
/* Transparent, sitting directly on the void: no border, no backdrop   */
/* blur. The gradient is a black-to-transparent scrim, invisible on a   */
/* black page, there only so display type cannot collide with the nav   */
/* as it scrolls underneath.                                            */
/* ------------------------------------------------------------------ */
.nav {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: 10;
  background: linear-gradient(to bottom, var(--void) 55%, rgba(0, 0, 0, 0));
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  height: var(--nav-h);
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.nav__brand {
  display: block;
  flex: none;
}

/* The wordmark ships as near-black artwork for the light v1 page.       */
/* brightness(0) flattens every opaque pixel to black while keeping the  */
/* antialiased alpha, then invert(1) lifts it to pure white. The mark     */
/* itself is untouched.                                                  */
/* A real white PNG now, so the brightness(0) invert(1) hack that used to  */
/* lift the near-black v1 artwork is gone. 32px rather than the 20px that  */
/* artwork wanted: the new mark reserves its right-hand third for the      */
/* triangle cluster, so the wordmark itself is only 40% of the frame       */
/* height against the old 64%, and matching heights would shrink it.       */
.nav__logo {
  display: block;
  height: 60px;
  width: auto;
}

.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(20px, 2.4vw, 36px);
}

.nav__links a:not(.btn) {
  font-size: var(--text-label);
  font-weight: 600;
  letter-spacing: 0.025em;
  text-transform: uppercase;
  color: var(--ash);
  text-decoration: none;
  transition: color 0.25s var(--ease-out);
}

.nav__links a:not(.btn):hover {
  color: var(--bone);
}

/* Five links and the CTA need about 960px. Between there and the phone   */
/* layout the bar keeps the three that lead to a page and drops the two   */
/* that do not: Process only scrolls the home page, and Contact is the    */
/* same address the footer gives.                                         */
@media (max-width: 960px) {
  .nav__links a[href$='#process'],
  .nav__links a[href^='mailto:'] {
    display: none;
  }
}

/* The phone menu, a <details> so it needs no script (blog pages load    */
/* none). Hidden here; the mobile block turns it on. The panel is the    */
/* void itself, full screen under the bar, with the links set as display */
/* type.                                                                  */
.nav__menu {
  display: none;
}

.nav__toggle {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-right: -10px;
  list-style: none;
  cursor: pointer;
}

.nav__toggle::-webkit-details-marker {
  display: none;
}

.nav__toggle-bar {
  grid-area: 1 / 1;
  width: 22px;
  height: 1.5px;
  background: var(--bone);
  transition: transform 0.3s var(--ease-out);
}

.nav__toggle-bar:first-child {
  transform: translateY(-4px);
}

.nav__toggle-bar:last-child {
  transform: translateY(4px);
}

.nav__menu[open] .nav__toggle-bar:first-child {
  transform: rotate(45deg);
}

.nav__menu[open] .nav__toggle-bar:last-child {
  transform: rotate(-45deg);
}

.nav__panel {
  position: fixed;
  inset: var(--nav-h) 0 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: 32px var(--gutter) 48px;
  overflow-y: auto;
  background: var(--void);
}

.nav__panel a {
  padding-block: 10px;
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 8vw, 2.5rem);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
  color: var(--bone);
  text-decoration: none;
}

.nav__panel a:hover {
  color: var(--iris-lift);
}

/* ------------------------------------------------------------------ */
/* Buttons                                                             */
/* One filled violet pill per view. Everything else is a ghost text     */
/* link with no container, so hierarchy comes from weight and colour.   */
/* ------------------------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  padding: 15px 26px;
  border-radius: 999px;
  font-size: var(--text-label);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.025em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.25s var(--ease-out), transform 0.2s var(--ease-out),
    color 0.25s var(--ease-out);
}

/* #8052ff against #ffffff is 4.57:1, clearing WCAG AA for body text. */
.btn--primary {
  background: var(--iris);
  color: var(--bone);
}

.btn--primary:hover {
  background: var(--iris-lift);
  transform: translateY(-1px);
}

.btn--primary:active {
  transform: scale(0.98);
}

.btn--ghost {
  padding-inline: 0;
  color: var(--bone);
}

.btn--ghost:hover {
  color: var(--saffron);
}

.btn--sm {
  padding: 12px 20px;
}

/* ------------------------------------------------------------------ */
/* Hero                                                                */
/* Asymmetric split: copy left, constellation right. The canvas is      */
/* pulled left of its own column so the scattered fragments drift into  */
/* the empty gutter beside the copy instead of clipping at a hard edge.  */
/* ------------------------------------------------------------------ */
.hero {
  display: grid;
  /* The copy column has to fit the longest hero headline line at the full
     72px-class display size. At 1.18fr it cleared it by 2px, so an ordinary
     copy edit pushed the authored two-line break onto a third line. */
  grid-template-columns: minmax(0, 1.26fr) minmax(0, 1fr);
  align-items: center;
  gap: var(--gutter);
  min-height: 100dvh;
  max-width: var(--page-max);
  margin-inline: auto;
  padding: calc(var(--nav-h) + 6vh) var(--gutter) 10vh;
}

.hero__copy {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 24px;
  justify-items: start;
  /* Wide enough that each sentence of the headline holds one line in the
     monospace: "Everyone bought AI." is 19 fixed-width cells, and at 620px
     it broke to four lines with "AI." and "faster." left as orphans. */
  max-width: 700px;
}

.hero__label {
  margin-bottom: -6px;
}

.hero__title {
  font-family: var(--font-display);
  font-size: var(--text-heading-lg);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
  line-height: 1.1;
}

.hero__sub {
  font-size: var(--text-body);
  font-weight: 200;
  color: var(--bone);
  max-width: 480px;
}

/* The headline's second sentence takes the logo's colours. inline-block so */
/* the gradient spans the text, not the 700px column.                        */
.hero__tone {
  display: inline-block;
  background: linear-gradient(95deg, var(--iris-lift) 0%, var(--rose) 60%, var(--azure) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* What the studio promises, as three icons rather than a sentence. */
.hero__trust {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 14px 28px;
  max-width: 560px;
  font-size: 16px;
  font-weight: 400;
}

.hero__trust li {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.hero__trust svg {
  flex-shrink: 0;
  color: var(--tone);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px;
  margin-top: 12px;
}

.hero__visual {
  position: relative;
  height: min(76vh, 660px);
  margin-left: -18%;
}


/* The field is one fixed, viewport-sized layer behind the whole page, not  */
/* a hero decoration, so the cursor keeps recruiting it on every section.   */
/* It stays transparent to input: nothing on the page needs to click it,    */
/* and this keeps the copy fully selectable where they overlap.            */
/*                                                                        */
/* Sized explicitly rather than by `inset` alone: <canvas> is a replaced   */
/* element, so width/height: auto resolves to its intrinsic 300x150 and    */
/* the right and bottom insets are simply dropped.                        */
.field {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* The large viewport, where supported: a phone's toolbars slide away as it
     scrolls, and a 100% canvas would be stretched to the new height until
     the resize handler caught up. Sized to the large viewport it never
     changes, and the strip under the toolbars is already painted. */
  height: 100lvh;
  z-index: 0;
  pointer-events: none;
}

/* ------------------------------------------------------------------ */
/* Sections                                                            */
/* ------------------------------------------------------------------ */
.section {
  padding-block: clamp(96px, 14vh, 180px);
}

/* A section's eyebrow, heading and lede, stacked. */
.section__intro {
  display: grid;
  gap: 24px;
  justify-items: start;
}

.section--cta {
  text-align: center;
  padding-block: clamp(120px, 20vh, 220px);
}

/* Home page: a hairline in the logo's four colours opens each section, and */
/* a soft glow sits behind it. The glow is a plain radial gradient, not a   */
/* blurred shape, so scrolling past it costs nothing. Both sit at z-index   */
/* -1 inside `main`, above the canvas and under the copy.                    */
.home .section {
  position: relative;
}

.home .section::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  width: min(100% - 2 * var(--gutter), var(--page-max) - 2 * var(--gutter));
  height: 1px;
  transform: translateX(-50%);
  background: linear-gradient(
    90deg,
    transparent,
    var(--iris) 18%,
    var(--rose) 48%,
    var(--azure) 78%,
    transparent
  );
  opacity: 0.55;
}

.home .section::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.home #services::after {
  background: radial-gradient(55% 45% at 88% 30%, rgba(255, 95, 210, 0.13), transparent 70%),
    radial-gradient(45% 40% at 8% 80%, rgba(128, 82, 255, 0.14), transparent 70%);
}

.home #process::after {
  background: radial-gradient(50% 40% at 6% 35%, rgba(77, 124, 255, 0.14), transparent 70%),
    radial-gradient(45% 40% at 95% 85%, rgba(255, 184, 41, 0.08), transparent 70%);
}

.home #cta::after {
  background: radial-gradient(50% 55% at 50% 55%, rgba(128, 82, 255, 0.22), transparent 70%),
    radial-gradient(35% 40% at 28% 40%, rgba(255, 95, 210, 0.1), transparent 70%),
    radial-gradient(35% 40% at 74% 45%, rgba(77, 124, 255, 0.1), transparent 70%);
}

/* ------------------------------------------------------------------ */
/* Platforms cascade                                                   */
/* Each row steps further right than the one above, so three items read */
/* as a diagonal across the void rather than as three stacked cards.    */
/* ------------------------------------------------------------------ */
.cascade {
  display: grid;
  gap: clamp(60px, 9vw, 120px);
  margin-top: clamp(60px, 8vw, 96px);
}

.cascade__row {
  display: grid;
  /* Wide enough for the longest offer name ("The First Win") to hold one
     line at the 42px display size; a wrapped name breaks the cascade. */
  grid-template-columns: minmax(0, 290px) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 60px);
  align-items: baseline;
}

.cascade__row:nth-child(2) {
  margin-left: 10%;
}

.cascade__row:nth-child(3) {
  margin-left: 20%;
}

/* Home page: each service has a colour, an icon, and a line across its row  */
/* in that colour, which also traces the staircase.                          */
.home .cascade__row {
  --tone: var(--iris-lift);
  padding-top: 32px;
  border-top: 1px solid color-mix(in srgb, var(--tone) 45%, transparent);
}

.home .cascade__row:nth-child(2) {
  --tone: var(--rose);
}

.home .cascade__row:nth-child(3) {
  --tone: var(--azure);
}

.cascade__icon {
  display: block;
  box-sizing: content-box;
  width: 30px;
  height: 30px;
  padding: 14px;
  margin-bottom: 20px;
  border-radius: 16px;
  color: var(--tone);
  background: color-mix(in srgb, var(--tone) 12%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tone) 35%, transparent);
}

.cascade__name {
  font-family: var(--font-display);
  font-size: var(--text-heading-sm);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
  line-height: 1.2;
}

.cascade__body {
  font-size: var(--text-body);
  font-weight: 200;
  color: var(--silver);
  max-width: 34ch;
}

/* Links to the service pages: the staircase's fourth step, set small like
   the CTA's note so it reads as a signpost, not a fourth offer. */
.cascade__more {
  margin-top: clamp(48px, 6vw, 72px);
  margin-left: 30%;
  font-size: var(--text-label);
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--ash);
}

.cascade__more a {
  color: var(--saffron);
  text-decoration: none;
  transition: color 0.25s var(--ease-out);
}

.cascade__more a:hover {
  color: var(--bone);
}

/* ------------------------------------------------------------------ */
/* Process, split-screen scroll                                        */
/* The heading pins with position:sticky while the four stages pass it. */
/* No scroll listener and no pin library is involved.                   */
/* ------------------------------------------------------------------ */
.split {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: clamp(36px, 6vw, 96px);
  align-items: start;
}

.split__aside {
  position: sticky;
  top: calc(var(--nav-h) + 8vh);
  display: grid;
  gap: 24px;
  justify-items: start;
}

.stages {
  display: grid;
  gap: clamp(60px, 8vw, 120px);
  list-style: none;
}

.stage {
  display: grid;
  gap: 18px;
}

/* Home page: the stages are a sequence, so they hang off a line. Each has */
/* a node on it with its own icon and colour; the line runs through all    */
/* four colours between them. The node's black fill hides the line behind  */
/* it, so it needs no clipping.                                            */
.home .stages {
  position: relative;
}

.home .stages::before {
  content: '';
  position: absolute;
  left: 23px;
  top: 24px;
  bottom: 24px;
  width: 1.5px;
  background: linear-gradient(to bottom, var(--iris-lift), var(--rose), var(--azure), var(--saffron));
  opacity: 0.6;
}

.home .stage {
  --tone: var(--iris-lift);
  grid-template-columns: 48px minmax(0, 1fr);
  column-gap: 32px;
  row-gap: 18px;
}

.home .stage:nth-child(2) {
  --tone: var(--rose);
}

.home .stage:nth-child(3) {
  --tone: var(--azure);
}

.home .stage:nth-child(4) {
  --tone: var(--saffron);
}

.home .stage > * {
  grid-column: 2;
}

.home .stage > .stage__node {
  grid-column: 1;
  grid-row: 1 / span 3;
  align-self: start;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  color: var(--tone);
  background: var(--void);
  box-shadow: inset 0 0 0 1.5px var(--tone), 0 0 28px -4px color-mix(in srgb, var(--tone) 55%, transparent);
}

.home .stage .label--accent {
  color: var(--tone);
}

/* Each stage opens with a small amber label, "01 · Planning & discovery", */
/* then its name. The numerals are plain text in the markup, not counters.  */
.stage__title {
  font-family: var(--font-display);
  font-size: var(--text-subheading);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
  line-height: 1.2;
}

.stage__body {
  font-size: var(--text-body);
  font-weight: 200;
  color: var(--silver);
  max-width: 44ch;
}

/* ------------------------------------------------------------------ */
/* Closing CTA                                                         */
/* The one symmetrical moment on the page, which is what makes it land. */
/* ------------------------------------------------------------------ */
.section--cta .display {
  max-width: 17ch;
  margin-inline: auto;
}

.section--cta .lede {
  margin: 30px auto 0;
  max-width: 44ch;
}

.cta__actions {
  margin-top: 36px;
}

/* The logo's four triangles, outlined, over the closing headline. */
.cta__mark {
  display: flex;
  justify-content: center;
  margin-bottom: 36px;
}

.home .section--cta .btn--primary {
  box-shadow: 0 0 44px -8px rgba(128, 82, 255, 0.75);
}

.cta__note,
.work__index {
  margin-top: 30px;
  font-size: var(--text-label);
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--ash);
}

.cta__note a,
.work__index a {
  color: var(--saffron);
  text-decoration: none;
  transition: color 0.25s var(--ease-out);
}

.cta__note a:hover,
.work__index a:hover {
  color: var(--bone);
}

/* ------------------------------------------------------------------ */
/* Work                                                                */
/* Generated by tools/work.mjs into /work/. It is the home page's own   */
/* vocabulary pointed at projects: the hero, one section per project    */
/* with no two laid out alike, and the centred close. Each project also */
/* gets a mockup of its own: a laptop, a stack of windows, a fan of     */
/* phones, a run of steps.                                              */
/*                                                                      */
/* There is no particle field here. Each section has a backdrop of the  */
/* logo's triangles instead, in the section's own colour (--tone), and  */
/* they move at different speeds as the page scrolls.                   */
/*                                                                      */
/* The devices are hairline outlines with nothing filled in. They are   */
/* the page's one exception to "no borders": a line drawing of a device */
/* is the picture, not a panel or a divider. Their one fill is the void */
/* itself, so a triangle passes behind a device and not through it.     */
/* ------------------------------------------------------------------ */
.work {
  --frame: rgba(255, 255, 255, 0.26);
}

/* Backdrops. A blurred triangle near the edge runs past the viewport, and */
/* clip (not hidden) cuts it off there without making `main` a scroll     */
/* container, which would break the sticky heading in the steps layout.   */
.page--work main {
  overflow-x: clip;
}

.work,
.hero--work,
.work-close {
  position: relative;
}

/* Behind everything in `main`, including the sections either side: -1 in  */
/* main's stacking context, so a triangle that drifts out of its own       */
/* section slides under the next one's copy and never over it. --u is the  */
/* unit the triangles are sized in: a hundredth of the viewport's width,   */
/* held so they neither vanish on a phone nor balloon on a wide monitor.   */
.backdrop {
  --u: clamp(7px, 1vw, 15px);
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

/* One triangle, drawn as the logo draws them: a thick outline with round  */
/* corners. --shift comes from main-v2.js, one value per backdrop, and the */
/* triangle's own depth (--d) turns it into travel. With no script, or     */
/* under reduced motion, --shift is 0 and the triangle sits where it was   */
/* composed.                                                               */
.tri {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: calc(var(--s) * var(--u));
  overflow: visible;
  fill: none;
  stroke: var(--c);
  stroke-width: 11;
  stroke-linejoin: round;
  transform: translate3d(0, calc(var(--shift, 0) * var(--d) * -1px), 0) rotate(var(--r));
  will-change: transform;
}

/* The three depths. Blur is what places a triangle: the near ones are out */
/* of focus in front, the far ones slightly soft behind, and only the      */
/* middle distance is sharp.                                               */
.tri--near {
  opacity: 0.45;
  filter: blur(calc(1.4 * var(--u)));
}

.tri--mid {
  opacity: 0.9;
}

.tri--far {
  opacity: 0.5;
  filter: blur(1.5px);
}

/* A wash is the section's colour with no shape to it. It is a gradient    */
/* and not a blurred triangle, because a blur that size would be a screen  */
/* of pixels to composite on every scrolled frame, and looks the same.     */
.wash {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: calc(var(--s) * var(--u));
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(closest-side, var(--c), transparent);
  opacity: 0.3;
  transform: translate(0, calc(var(--shift, 0) * var(--d) * -1px));
}

/* A section's labels take its colour. Every tone clears 4.5:1 on black. */
.work .facts dt {
  color: var(--tone);
}

/* Short of the full screen, so the first project's name shows under it. */
.hero--work {
  min-height: 88dvh;
}

.work__index {
  margin-top: 6px;
  line-height: 1.9;
}

.work__copy {
  display: grid;
  gap: 24px;
  justify-items: start;
  align-content: start;
}

.work__body {
  font-size: var(--text-body);
  font-weight: 200;
  color: var(--silver);
  max-width: 44ch;
}

/* Copy on one side and the mockup on the other, as the hero is. The flip */
/* puts the mockup first without moving it ahead of its own heading in    */
/* the markup. The copy column is as narrow as it can be and still hold a */
/* sixteen-cell name ("Elite View Glass") on one line at full size.       */
.work__split {
  display: grid;
  grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
  gap: clamp(36px, 6vw, 96px);
  align-items: center;
}

.work__split--flip {
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
}

.work__split--flip > .work__copy {
  order: 2;
}

/* The name on the left and the body on the right, as a cascade row is, */
/* with the mockup running the full width under them.                   */
.work__lead {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: clamp(36px, 6vw, 96px);
  align-items: start;
}

.work__lead + .mock {
  margin-top: clamp(56px, 8vw, 104px);
}

/* A project's facts. The amber term is the page's .label. */
.facts {
  display: grid;
  gap: 10px;
  max-width: 44ch;
  font-size: 16px;
  font-weight: 300;
  color: var(--silver);
}

.facts > div {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr);
  gap: 16px;
  align-items: baseline;
}

.mock {
  position: relative;
}

/* A phone is sized by its width alone. Every other measure is a share of */
/* that width, written as a percentage of the box it applies to, so the   */
/* corners stay circular and the bezel even at any size: the screen is    */
/* 1290x2796, the bezel 3.4% of the body's width, the body's corner 16%.  */
.phone {
  position: relative;
  aspect-ratio: 1000 / 2088;
  border-radius: 16% / 7.66%;
  background: var(--void);
  box-shadow: inset 0 0 0 1px var(--frame);
}

.phone img {
  position: absolute;
  inset: 1.63% 3.4%;
  width: 93.2%;
  height: 96.74%;
  object-fit: cover;
  border-radius: 13.5% / 6.24%;
}

/* An app's screenshot leaves room for the Dynamic Island between the clock */
/* and the battery. A website's does not, so its phone goes without one.    */
.phone--app::after {
  content: '';
  position: absolute;
  top: 2.77%;
  left: 50%;
  width: 27.3%;
  height: 3.84%;
  border-radius: 999px;
  background: var(--void);
  transform: translateX(-50%);
}

/* A screen that is on its way: the outline goes dashed and says so. */
.phone--empty {
  display: grid;
  place-items: center;
  padding: 14%;
  border: 1px dashed rgba(255, 255, 255, 0.36);
  box-shadow: none;
}

.phone--empty p {
  font-family: var(--font-display);
  font-size: clamp(11px, 1.1vw, 13px);
  line-height: 1.4;
  color: var(--ash);
  text-align: center;
}

.browser {
  overflow: hidden;
  border: 1px solid var(--frame);
  border-radius: clamp(8px, 1.2vw, 14px);
  background: var(--void);
}

.browser__bar {
  position: relative;
  display: grid;
  place-items: center;
  height: clamp(20px, 2.6vw, 34px);
}

/* The window's three buttons: one dot, and two shadows of it. */
.browser__bar::before {
  content: '';
  position: absolute;
  left: clamp(8px, 1.1vw, 14px);
  top: 50%;
  width: clamp(4px, 0.5vw, 6px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--frame);
  box-shadow:
    clamp(7px, 1vw, 12px) 0 var(--frame),
    clamp(14px, 2vw, 24px) 0 var(--frame);
  transform: translateY(-50%);
}

.browser__url {
  font-family: var(--font-display);
  font-size: clamp(8px, 0.95vw, 12px);
  line-height: 1;
  color: var(--ash);
}

.browser img,
.laptop__screen img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
}

/* Laptop: a lid with the screen in it, on a base that runs wider than the */
/* lid and has the thumb notch cut into its top edge.                      */
.laptop__screen {
  margin-inline: 7%;
  padding: 1.7% 1.7% 2.2%;
  border: 1px solid var(--frame);
  border-bottom: 0;
  border-radius: clamp(8px, 1.4vw, 18px) clamp(8px, 1.4vw, 18px) 0 0;
  background: var(--void);
}

.laptop__screen img {
  border-radius: clamp(2px, 0.4vw, 5px);
}

.laptop__base {
  position: relative;
  height: clamp(7px, 1.1vw, 14px);
  border: 1px solid var(--frame);
  border-radius: 2px 2px clamp(8px, 1.4vw, 18px) clamp(8px, 1.4vw, 18px);
  background: var(--void);
}

.laptop__base::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 50%;
  width: 15%;
  height: 46%;
  border: 1px solid var(--frame);
  border-top: 0;
  border-radius: 0 0 6px 6px;
  transform: translateX(-50%);
}

/* The site on a phone stands in front of the laptop's right-hand corner. */
/* The padding is the part of the phone that hangs outside the laptop.    */
.mock--laptop {
  padding: 0 4% 4% 0;
}

.mock--laptop > .phone {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 19%;
}

/* Windows: every one in the same grid cell, each pushed further down and */
/* right than the one before and painted over it, which is the services   */
/* cascade again in three dimensions. The front window is the last one.   */
.mock--windows {
  display: grid;
  align-items: start;
}

.mock--windows > .browser {
  grid-area: 1 / 1;
  width: 54%;
}

.mock--windows > .browser:nth-child(2) {
  margin: 13% 0 0 23%;
}

.mock--windows > .browser:nth-child(3) {
  margin: 26% 0 0 46%;
}

/* Fan: the middle phone stands forward and the outer two lean away from */
/* it, tucked behind its edges.                                          */
.mock--fan {
  display: flex;
  justify-content: center;
  align-items: center;
  padding-block: 3%;
}

.mock--fan > .phone {
  flex: none;
  width: 34%;
}

.mock--fan > .phone:nth-child(1) {
  margin-right: -7%;
  transform: rotate(-8deg) translateY(5%);
}

.mock--fan > .phone:nth-child(2) {
  z-index: 1;
  width: 39%;
}

.mock--fan > .phone:nth-child(3) {
  margin-left: -7%;
  transform: rotate(8deg) translateY(5%);
}

/* Steps: the process section's split, with a phone for each stage. They  */
/* go two across with the second column dropped, so the order still reads */
/* left to right and down, and the heading beside them stays put.         */
.steps {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(48px, 6vw, 80px) clamp(24px, 4vw, 56px);
  list-style: none;
}

.step {
  display: grid;
  gap: 20px;
  align-content: start;
}

.step:nth-child(even) {
  margin-top: 34%;
}

/* No numerals, as with the stages: the step's own name is the label. */
.step__title {
  font-family: var(--font-display);
  font-size: clamp(1rem, 1.5vw, 20px);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
  line-height: 1.3;
}

/* Between the phone layout and a full desktop, a mockup and its copy do */
/* not both fit on one row.                                              */
@media (max-width: 960px) {
  .work__split,
  .work__split--flip,
  .work__lead {
    grid-template-columns: 1fr;
    gap: 48px;
  }

  .work__split--flip > .work__copy {
    order: 0;
  }
}

/* ------------------------------------------------------------------ */
/* Blog                                                                */
/* Generated by tools/blog.mjs into /blog/. Same void; a table or a    */
/* quote is set apart by space and type, and the colour layer further   */
/* down adds only hairlines, glows and tints. There is no field on these pages; a   */
/* long read needs the page still. Body steps up from 200 to 300 and    */
/* down to silver, because 200 bone reads well for a paragraph and      */
/* tires the eye over fifteen hundred words.                            */
/* ------------------------------------------------------------------ */
.shell--read {
  max-width: 820px;
}

.post,
.blog {
  padding-block: calc(var(--nav-h) + clamp(48px, 10vh, 120px)) clamp(96px, 14vh, 180px);
}

.post__header {
  display: grid;
  gap: 24px;
  justify-items: start;
}

.post__title {
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
  line-height: 1.15;
  text-wrap: balance;
}

.post__summary {
  font-size: var(--text-body);
  font-weight: 200;
  color: var(--silver);
  max-width: 52ch;
}

.post__cover {
  margin-top: clamp(40px, 6vw, 64px);
}

.post__cover img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 24px;
}

.prose {
  margin-top: clamp(48px, 7vw, 72px);
  font-weight: 300;
  line-height: 1.7;
  color: var(--silver);
  max-width: 68ch;
}

.prose > * + * {
  margin-top: 1.2em;
}

.prose h2,
.prose h3,
.prose h4 {
  font-family: var(--font-display);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
  line-height: 1.25;
  color: var(--bone);
  text-wrap: balance;
}

.prose h2 {
  font-size: var(--text-subheading);
  margin-top: 2.4em;
}

.prose h3 {
  font-size: clamp(1.25rem, 1.8vw, 24px);
  margin-top: 2em;
}

.prose h4 {
  font-size: var(--text-body);
  margin-top: 1.8em;
}

.prose strong {
  font-weight: 500;
  color: var(--bone);
}

.prose a {
  color: var(--bone);
  text-decoration: underline;
  text-decoration-color: var(--iris);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: text-decoration-color 0.25s var(--ease-out);
}

.prose a:hover {
  text-decoration-color: var(--bone);
}

.prose ul,
.prose ol {
  padding-left: 1.3em;
}

.prose li + li {
  margin-top: 0.5em;
}

.prose li::marker {
  color: var(--iris);
}

/* No rule down the side: the quote is indented and set in bone. */
.prose blockquote {
  padding-left: 1.5em;
  color: var(--bone);
  font-style: italic;
}

.prose blockquote > * + * {
  margin-top: 0.8em;
}


.prose code {
  font-family: var(--font-display);
  font-size: 0.88em;
  color: var(--bone);
}

.prose pre {
  overflow-x: auto;
  padding-left: 1.5em;
  line-height: 1.55;
}

.prose__table {
  overflow-x: auto;
}

/* A body image breaks the column with more air than a paragraph gets, so it
   reads as a pause between sections rather than as part of the text. */
.prose > .prose__figure {
  margin-block: 2.4em;
}

.prose__figure img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 24px;
}

.prose table {
  width: 100%;
  border-collapse: collapse;
  font-size: 16px;
}

.prose th {
  font-size: var(--text-label);
  font-weight: 600;
  letter-spacing: 0.025em;
  text-transform: uppercase;
  color: var(--ash);
  text-align: left;
}

.prose th,
.prose td {
  padding: 10px 24px 10px 0;
  vertical-align: top;
}

.prose td:first-child {
  color: var(--bone);
}

.post__sources,
.post__faq,
.post__related {
  margin-top: clamp(72px, 10vw, 112px);
  max-width: 68ch;
}

/* Related posts reuse the blog index's list, a subhead's distance below it. */
.post__related .posts {
  margin-top: 36px;
}

.post__subhead {
  font-family: var(--font-display);
  font-size: var(--text-subheading);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
}

/* References, set small: they are there to be checked, not read through. */
.post__sources ol {
  margin-top: 28px;
  padding-left: 1.6em;
  font-size: 16px;
  font-weight: 300;
  line-height: 1.6;
  color: var(--silver);
}

.post__sources li + li {
  margin-top: 14px;
}

.post__sources li::marker {
  color: var(--iris);
}

.post__sources a {
  color: var(--bone);
  text-decoration: underline;
  text-decoration-color: var(--iris);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  overflow-wrap: anywhere;
}

.post__sources a:hover {
  text-decoration-color: var(--bone);
}

.post__sources span {
  display: block;
  font-size: var(--text-label);
  color: var(--ash);
}

.post__faq dt {
  margin-top: 36px;
  font-weight: 500;
  color: var(--bone);
}

.post__faq dd {
  margin-top: 10px;
  font-weight: 300;
  line-height: 1.7;
  color: var(--silver);
}

.post__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px 28px;
  margin-top: clamp(72px, 10vw, 112px);
}

.posts {
  display: grid;
  gap: clamp(48px, 7vw, 72px);
  margin-top: clamp(64px, 9vw, 104px);
  list-style: none;
}

.posts__item,
.posts__text {
  display: grid;
  gap: 12px;
  align-content: start;
}

/* With a cover, the thumbnail takes a fixed column on the left. */
.posts__item--thumb {
  grid-template-columns: 240px minmax(0, 1fr);
  gap: 32px;
  align-items: start;
}

.posts__thumb img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 16px;
  transition: opacity 0.25s var(--ease-out);
}

.posts__thumb:hover img {
  opacity: 0.8;
}

.posts__title {
  font-family: var(--font-display);
  font-size: var(--text-subheading);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
  line-height: 1.25;
  text-wrap: balance;
}

.posts__title a {
  text-decoration: none;
  transition: color 0.25s var(--ease-out);
}

.posts__title a:hover {
  color: var(--iris-lift);
}

.posts__summary {
  font-weight: 200;
  color: var(--silver);
  max-width: 60ch;
}

/* Blog building blocks: captions, key points, contents, stats, charts and */
/* pull quotes. All set apart by type and space; still no boxes or rules. */
.post__cover figcaption,
.prose__figure figcaption {
  margin-top: 12px;
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--ash);
}

.post__takeaways,
.post__toc {
  margin-top: clamp(48px, 7vw, 72px);
  max-width: 68ch;
}

.post__takeaways ul {
  display: grid;
  gap: 14px;
  margin-top: 20px;
  list-style: none;
}

.post__takeaways li {
  position: relative;
  padding-left: 1.8em;
  font-size: clamp(1.0625rem, 1.4vw, 19px);
  font-weight: 300;
  line-height: 1.55;
  color: var(--bone);
}

.post__takeaways li::before {
  content: '→';
  position: absolute;
  left: 0;
  color: var(--iris);
}

.post__toc ol {
  display: grid;
  gap: 10px;
  margin-top: 18px;
  padding-left: 0;
  list-style: none;
  counter-reset: toc;
}

.post__toc li {
  counter-increment: toc;
  font-weight: 300;
  color: var(--silver);
}

.post__toc li::before {
  content: counter(toc, decimal-leading-zero);
  display: inline-block;
  width: 2.6em;
  font-family: var(--font-display);
  font-size: 0.85em;
  color: var(--iris);
}

.post__toc a {
  text-decoration: none;
  transition: color 0.25s var(--ease-out);
}

.post__toc a:hover {
  color: var(--bone);
}

.prose > .stats,
.prose > .chart,
.prose > .pull,
.prose > .people {
  margin-block: 2.4em;
}

/* The founders' portraits on /about/. Three across at every width: the */
/* names are short, and stacked they would be three screens on a phone. */
.people {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(12px, 3vw, 28px);
}

.people__item img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: clamp(12px, 2.4vw, 24px);
}

.people__item figcaption {
  margin-top: 14px;
  line-height: 1.4;
}

.people__name {
  display: block;
  font-family: var(--font-display);
  color: var(--bone);
}

.people__role {
  display: block;
  margin-top: 2px;
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--ash);
}

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 32px;
}

.stats__value {
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 5vw, 60px);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
  line-height: 1;
  color: var(--bone);
}

.stats__label {
  margin-top: 12px;
  font-size: 16px;
  line-height: 1.5;
  color: var(--silver);
}

.stats__source,
.chart__source {
  margin-top: 8px;
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--ash);
}

.chart__title {
  font-weight: 500;
  color: var(--bone);
}

.prose .chart__rows {
  display: grid;
  gap: 16px;
  margin-top: 20px;
  padding-left: 0;
  list-style: none;
}

.chart__rows li {
  display: grid;
  gap: 6px;
}

.chart__label {
  font-size: 15px;
  color: var(--silver);
}

.chart__track {
  display: flex;
  align-items: center;
  gap: 12px;
}

.chart__bar {
  flex: none;
  min-width: 3px;
  height: 14px;
  border-radius: 3px;
  background: var(--iris);
}

.chart__value {
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--bone);
}

.prose .chart__source {
  margin-top: 16px;
}

/* A pull quote repeats a line from the text at display size. No rule, no */
/* quote marks: size and the face carry it.                               */
.prose .pull {
  padding-left: 0;
  font-style: normal;
}

.pull p {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.8vw, 32px);
  font-weight: 300;
  letter-spacing: var(--tracking-display);
  line-height: 1.3;
  color: var(--bone);
  text-wrap: balance;
}

/* A section break is three spaced dots, the way Medium sets one: space */
/* and type, not a line.                                                 */
.prose hr {
  border: 0;
  height: auto;
  margin-block: 2.4em;
  text-align: center;
}

.prose hr::after {
  content: '· · ·';
  letter-spacing: 0.6em;
  color: var(--ash);
}

/* ---- Formats: one layout per writing structure (see docs/blog-formats.md) */

/* Guide and roundup: numbered sections. A guide numbers every section, to */
/* match its contents list. In a roundup the last h2 is "Where to start",  */
/* a close rather than an item, so it goes unnumbered.                     */
.post--guide .prose,
.post--list .prose {
  counter-reset: section;
}

.post--guide .prose h2,
.post--list .prose h2 {
  counter-increment: section;
}

.post--guide .prose h2::before,
.post--list .prose h2::before {
  content: counter(section, decimal-leading-zero);
  display: block;
  margin-bottom: 10px;
  color: var(--iris);
}

.post--guide .prose h2::before {
  font-size: var(--text-label);
  letter-spacing: 0.1em;
}

.post--list .prose h2::before {
  font-size: clamp(2.75rem, 5.5vw, 64px);
  line-height: 1;
}

.post--list .prose h2:last-of-type::before {
  content: none;
}

/* Case study and opinion: a drop cap opens the piece, like a magazine.    */
.post--story .prose > p:first-child::first-letter,
.post--essay .prose > p:first-child::first-letter {
  float: left;
  padding: 0.08em 0.14em 0 0;
  font-family: var(--font-display);
  font-size: 4.4em;
  font-weight: 300;
  line-height: 0.8;
  color: var(--bone);
}

/* Opinion reads as an essay: larger text on a shorter line. */
.post--essay .prose {
  font-size: 20px;
  max-width: 60ch;
}

.post--essay .pull p {
  text-align: center;
}

/* Briefing: the short version leads, so it is set larger. */
.post--briefing .post__takeaways li {
  font-size: clamp(1.125rem, 1.7vw, 21px);
}

/* Case study and data posts let the cover run wider than the text column. */
@media (min-width: 1200px) {
  .post--story .post__cover,
  .post--data .post__cover {
    margin-inline: -120px;
  }
}

/* ------------------------------------------------------------------ */
/* Scroll reveal                                                       */
/* main-v2.js only applies these classes when motion is allowed, so the */
/* reduce block below is a belt-and-braces guard for a cached class.    */
/* ------------------------------------------------------------------ */
.will-reveal {
  opacity: 0;
  transform: translateY(22px);
}

.will-reveal.is-visible {
  opacity: 1;
  transform: none;
  transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out);
}

/* Hero entry, staggered. Gated so a reduced-motion visitor gets the page */
/* already composed rather than mid-animation.                            */
@media (prefers-reduced-motion: no-preference) {
  .hero__copy > * {
    animation: rise 0.9s var(--ease-out) both;
  }

  .hero__copy > *:nth-child(1) {
    animation-delay: 0.05s;
  }
  .hero__copy > *:nth-child(2) {
    animation-delay: 0.14s;
  }
  .hero__copy > *:nth-child(3) {
    animation-delay: 0.23s;
  }
  .hero__copy > *:nth-child(4) {
    animation-delay: 0.32s;
  }
  .hero__copy > *:nth-child(5) {
    animation-delay: 0.41s;
  }

  .hero__visual {
    animation: fade 1.6s var(--ease-out) 0.2s both;
  }
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

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

  .will-reveal {
    opacity: 1;
    transform: none;
  }

  .btn {
    transition: none;
  }

  .btn--primary:hover,
  .btn--primary:active {
    transform: none;
  }
}

/* ------------------------------------------------------------------ */
/* Colour layer on the content pages                                   */
/* The home page's language, carried to everything on `page--blog`: the  */
/* blog, its posts, the services pages, About, Privacy and the 404. A    */
/* glow behind the top of the page, a hairline under the header, headings */
/* at weight 500, and a colour that steps violet, pink, blue, amber down  */
/* the page's sections. Pricing and Work keep their own designs. The      */
/* services, About and Privacy pages (`post--page`) go furthest: a        */
/* gradient title, a triangle on each heading, numbered lists as a        */
/* timeline. Posts keep their own markers, only tinted.                   */
/* ------------------------------------------------------------------ */
.page--blog main {
  position: relative;
}

.page--blog main::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: min(920px, 100%);
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(50% 55% at 84% 14%, rgba(128, 82, 255, 0.17), transparent 70%),
    radial-gradient(40% 40% at 10% 36%, rgba(255, 95, 210, 0.1), transparent 70%),
    radial-gradient(40% 40% at 70% 58%, rgba(77, 124, 255, 0.09), transparent 70%);
}

.page--blog .post__title,
.page--blog .prose h2,
.page--blog .prose h3,
.page--blog .prose h4,
.page--blog .posts__title {
  font-weight: 500;
}

.page--blog .post__header::after {
  content: '';
  width: 100%;
  height: 1px;
  margin-top: 16px;
  background: linear-gradient(90deg, var(--iris), var(--rose) 45%, var(--azure) 75%, transparent);
  opacity: 0.6;
}

/* Plain pages: the title takes the logo's colours, as the home hero does. */
.post--page .post__title,
.blog .post__title {
  background: linear-gradient(95deg, var(--iris-lift) 0%, var(--rose) 60%, var(--azure) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Each h2 steps through the four colours. */
.page--blog .prose h2 {
  --tone: var(--iris-lift);
}

.page--blog .prose h2:nth-of-type(4n + 2) {
  --tone: var(--rose);
}

.page--blog .prose h2:nth-of-type(4n + 3) {
  --tone: var(--azure);
}

.page--blog .prose h2:nth-of-type(4n + 4) {
  --tone: var(--saffron);
}

.page--blog .prose h2::after {
  content: '';
  display: block;
  width: 64px;
  height: 2px;
  margin-top: 14px;
  background: linear-gradient(90deg, var(--tone), transparent);
}

.post--guide .prose h2::before,
.post--list .prose h2::before {
  color: var(--tone);
}

/* The logo's triangle on each heading of a plain page, turning a quarter */
/* each time, as in the closing mark on the home page.                    */
.post--page .prose h2::before {
  content: '';
  display: block;
  width: 28px;
  height: 28px;
  margin-bottom: 16px;
  background: var(--tone);
  -webkit-mask: var(--triangle) center / contain no-repeat;
  mask: var(--triangle) center / contain no-repeat;
}

.post--page .prose h2:nth-of-type(4n + 2)::before {
  transform: rotate(90deg);
}

.post--page .prose h2:nth-of-type(4n + 3)::before {
  transform: rotate(180deg);
}

.post--page .prose h2:nth-of-type(4n + 4)::before {
  transform: rotate(-90deg);
}

.post--page {
  --triangle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'%3E%3Cpath d='M12 3.5 21.5 20h-19z'/%3E%3C/svg%3E");
}

.page--blog .prose li::marker {
  color: var(--rose);
}

/* A numbered list on a plain page is a sequence, so it hangs off a line. */
.post--page .prose ol {
  list-style: none;
  padding-left: 0;
  counter-reset: step;
}

.post--page .prose ol > li {
  --tone: var(--iris-lift);
  counter-increment: step;
  position: relative;
  padding-left: 60px;
  min-height: 40px;
}

.post--page .prose ol > li + li {
  margin-top: 2em;
}

.post--page .prose ol > li:nth-child(4n + 2) {
  --tone: var(--rose);
}

.post--page .prose ol > li:nth-child(4n + 3) {
  --tone: var(--azure);
}

.post--page .prose ol > li:nth-child(4n + 4) {
  --tone: var(--saffron);
}

.post--page .prose ol > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 16px;
  color: var(--tone);
  background: var(--void);
  box-shadow: inset 0 0 0 1.5px var(--tone), 0 0 24px -4px color-mix(in srgb, var(--tone) 55%, transparent);
}

.post--page .prose ol > li:not(:last-child)::after {
  content: '';
  position: absolute;
  left: 19.25px;
  top: 44px;
  bottom: -1.7em;
  width: 1.5px;
  background: linear-gradient(to bottom, var(--tone), transparent);
  opacity: 0.55;
}

/* A line that is only a link is a signpost: bolder, with a pink arrow. */
.post--page .prose p:has(> a:only-child) a {
  font-weight: 500;
  text-decoration-color: var(--rose);
  text-decoration-thickness: 2px;
}

.post--page .prose p:has(> a:only-child) a::after {
  content: '\2192';
  margin-left: 0.5em;
  color: var(--rose);
}

.page--blog .prose th {
  color: var(--saffron);
  border-bottom: 1px solid color-mix(in srgb, var(--iris) 55%, transparent);
}

/* Questions and answers: a line down the side, in the section's colour. */
.page--blog .post__faq dt,
.page--blog .post__faq dd {
  --tone: var(--iris-lift);
  padding-left: 20px;
  border-left: 2px solid color-mix(in srgb, var(--tone) 55%, transparent);
}

.page--blog .post__faq dt:nth-of-type(4n + 2),
.page--blog .post__faq dd:nth-of-type(4n + 2) {
  --tone: var(--rose);
}

.page--blog .post__faq dt:nth-of-type(4n + 3),
.page--blog .post__faq dd:nth-of-type(4n + 3) {
  --tone: var(--azure);
}

.page--blog .post__faq dt:nth-of-type(4n + 4),
.page--blog .post__faq dd:nth-of-type(4n + 4) {
  --tone: var(--saffron);
}

.page--blog .post__faq dt {
  padding-top: 4px;
  margin-top: 28px;
}

.page--blog .post__faq dd {
  padding-bottom: 4px;
  margin-top: 0;
  padding-top: 8px;
}

/* Post lists: each entry opens on a hairline in the next colour. */
.page--blog .posts__item {
  --tone: var(--iris-lift);
  padding-top: 28px;
  border-top: 1px solid color-mix(in srgb, var(--tone) 45%, transparent);
}

.page--blog .posts li:nth-child(4n + 2) {
  --tone: var(--rose);
}

.page--blog .posts li:nth-child(4n + 3) {
  --tone: var(--azure);
}

.page--blog .posts li:nth-child(4n + 4) {
  --tone: var(--saffron);
}

.page--blog .posts__item .label:not(.label--accent) {
  color: var(--tone);
}

.page--blog .post__foot .btn--primary {
  box-shadow: 0 0 44px -8px rgba(128, 82, 255, 0.75);
}

/* ------------------------------------------------------------------ */
/* Footer                                                              */
/* ------------------------------------------------------------------ */
.footer {
  padding-block: 60px 96px;
}

.home .footer,
.page--blog .footer {
  position: relative;
}

.home .footer::before,
.page--blog .footer::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  width: min(100% - 2 * var(--gutter), var(--page-max) - 2 * var(--gutter));
  height: 1px;
  transform: translateX(-50%);
  background: linear-gradient(90deg, transparent, var(--iris) 18%, var(--rose) 48%, var(--azure) 78%, transparent);
  opacity: 0.4;
}

.footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
  font-size: var(--text-label);
  font-weight: 400;
  color: var(--ash);
}

.footer__inner a {
  text-decoration: none;
  transition: color 0.25s var(--ease-out);
}

.footer__inner a:hover {
  color: var(--bone);
}

.footer__social {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.footer__social svg {
  flex-shrink: 0;
}

/* ------------------------------------------------------------------ */
/* Mobile, everything below 768px in one block                         */
/* Every asymmetric layout above collapses to a single column here.    */
/* ------------------------------------------------------------------ */
@media (max-width: 768px) {
  :root {
    --nav-h: 64px;
  }

  /* The scrim runs 20px past the bar and is solid for all of the bar      */
  /* itself: at 55% the copy scrolling underneath showed through the lower  */
  /* half of the wordmark. The overhang must not swallow taps on the copy   */
  /* beneath it, hence pointer-events back on only for the bar's contents.  */
  .nav {
    padding-bottom: 20px;
    background: linear-gradient(to bottom, var(--void) calc(100% - 20px), rgba(0, 0, 0, 0));
    pointer-events: none;
  }

  .nav__inner {
    gap: 16px;
    pointer-events: auto;
  }

  /* 60px of wordmark plus the CTA ran 63px off a 390px screen. */
  .nav__logo {
    height: clamp(34px, 10vw, 44px);
  }

  /* The bar keeps the wordmark, the one CTA and the menu toggle; the      */
  /* links move into the menu's panel. The CTA drops "30-minute" to fit;   */
  /* the hero's own button still says it in full.                          */
  .nav__links a:not(.btn),
  .nav__cta-long {
    display: none;
  }

  /* Holds the CTA against the toggle rather than mid-bar. */
  .nav__links {
    margin-left: auto;
  }

  .nav__menu {
    display: block;
    flex: none;
  }

  /* With the menu open the page underneath must not scroll. Inside this   */
  /* block so a menu left open while the window widens cannot lock the     */
  /* desktop page.                                                         */
  body:has(.nav__menu[open]) {
    overflow: hidden;
  }

  /* The field's mass sits mid-screen behind every section on a phone    */
  /* (see heroCentreY in constellation-v2.js), so the hero reserves no    */
  /* room for it.                                                          */
  .hero {
    grid-template-columns: 1fr;
    min-height: auto;
    gap: 48px;
    padding: calc(var(--nav-h) + 40px) var(--gutter) 72px;
  }

  .hero__copy {
    max-width: none;
  }

  /* Each sentence of the headline is 19 monospace cells and has to hold   */
  /* one line. The 1.625rem floor does that down to 360px; below it, size  */
  /* to the column instead (a Geist Mono cell plus tracking is ~0.62em,    */
  /* and /12 leaves a little slack) so "AI." and "faster." never orphan.   */
  .hero__title {
    font-size: min(var(--text-heading-lg), calc((100vw - 2 * var(--gutter)) / 12));
  }

  /* The field is a page-wide layer now, so on one column this reserves
     nothing but empty screen. */
  .hero__visual {
    display: none;
  }

  .heading {
    max-width: none;
  }

  .cascade__row,
  .cascade__row:nth-child(2),
  .cascade__row:nth-child(3) {
    grid-template-columns: 1fr;
    gap: 12px;
    margin-left: 0;
  }

  .cascade__more {
    margin-left: 0;
  }

  .split {
    grid-template-columns: 1fr;
    gap: 60px;
  }

  .split__aside {
    position: static;
  }

  .home .stages::before {
    left: 19px;
  }

  .home .stage {
    grid-template-columns: 40px minmax(0, 1fr);
    column-gap: 20px;
  }

  .home .stage > .stage__node {
    width: 40px;
    height: 40px;
  }

  .footer__inner {
    flex-direction: column;
    gap: 12px;
  }

  .post__cover img,
  .prose__figure img {
    border-radius: 16px;
  }

  .posts__item--thumb {
    grid-template-columns: 1fr;
    gap: 16px;
  }

  .prose table {
    font-size: 15px;
  }

  /* The windows are wider and step less, or the front one is a thumbnail; */
  /* the site's phone grows for the same reason.                           */
  .mock--windows > .browser {
    width: 74%;
  }

  .mock--windows > .browser:nth-child(2) {
    margin: 19% 0 0 13%;
  }

  .mock--windows > .browser:nth-child(3) {
    margin: 38% 0 0 26%;
  }

  .mock--laptop {
    padding: 0 5% 9% 0;
  }

  .mock--laptop > .phone {
    width: 25%;
  }

  .steps {
    gap: 40px 16px;
  }

  /* On one column there is no empty corner to put a sharp triangle in: it */
  /* lands on the copy. So the hero's cluster gets a band of its own under */
  /* the copy, stretched past the screen's left edge because it was        */
  /* composed for the right-hand half of a wide one.                       */
  .hero--work {
    padding-bottom: 68vw;
  }

  .hero--work .backdrop {
    inset: auto auto 0 -62%;
    width: 168%;
    height: 78vw;
  }

  /* In the sections, where triangles do pass behind the copy, the sharp   */
  /* ones go soft and everything is turned down so the text stays in front. */
  .work .tri--mid,
  .work-close .tri--mid {
    opacity: 0.3;
    filter: blur(2px);
  }

  .work .tri--near,
  .work-close .tri--near {
    opacity: 0.32;
  }

  .work .wash,
  .work-close .wash {
    opacity: 0.22;
  }
}
