/* ══════════════════════════════════════════════════════════════════════════
   Alunori — shared stylesheet (index, studinari, ruminari)

   Structure and rhythm follow the Studinari welcome page: 10px eyebrows,
   13px body, 12px supporting copy, 11px controls, headings at weight 500
   with an italic phrase, sections on a 62rem column.

   NOTHING IS FENCED IN. The reference's hairline cell grids were tried here
   and read as tables dropped into a page that is otherwise all air, so they
   are gone: the hero states its facts on open ground, note rows sit under a
   rule that fades away rather than between drawn columns, and the roster is
   a set of soft tinted blocks with no border at all. What is left of
   structure is a rule that fades out at both ends and a panel with a lit top
   edge — never a box closed on four sides.

   COLOUR IS ALMOST ABSENT, which is the point. The reference is a dark room
   photographed, not a screen emitting light: no glow, no neon, no gradient
   running across the page. This site holds to that and takes the room cool —
   a blue-grey near-black, off-white type, and one soft sky blue that appears
   only where something must be pointed at: a status dot, a rule, a live
   marker. The accent is muted on purpose. A bright chip of colour dropped
   into the middle of a wordmark is the thing that made this look cheap, so
   names are set in one ink and the seam between prefix and ending is shown
   with weight and a hairline instead of with paint.

   TEXTURE DOES THE WORK the gradients used to. A single sheet of film grain
   sits over the whole document at an opacity you can actually see, and a
   slow vignette pulls the corners down. Between them the flat ground stops
   being flat without ever becoming a light show.

   SINGLE THEME. This page commits to the dark palette rather than following
   the viewer's setting — it is a brand surface, not a document. Every colour
   is still painted explicitly, so the page holds on a light host ground.
   ══════════════════════════════════════════════════════════════════════════ */
:root {
  --paper:       #0E1116;
  --paper-2:     #0A0D11;
  --surface:     #161A21;
  --surface-2:   #1C222B;
  --ink:         #E8EDF3;
  --slate:       #8A939F;
  --border:      #262D37;
  --border-soft: #1E242D;

  --sky:         #93C6E8;
  --sky-rgb:     147 198 232;
  --on-accent:   #0E1116;

  /* Channel lists for every colour a gradient fades out of. Fading to the
     `transparent` keyword fades through transparent *black*, which greys a
     colour out before it disappears and leaves a visible edge. Every fade
     below ends on the same hue at zero alpha instead. */
  --paper-rgb:    14 17 22;
  --surface-rgb:  22 26 33;
  --border-rgb:   38 45 55;
  --ink-rgb:      232 237 243;

  --lift:        0 1px 2px rgb(0 0 0 / .4), 0 24px 48px -32px rgb(0 0 0 / .9);
  --lift-hover:  0 1px 2px rgb(0 0 0 / .5), 0 30px 60px -30px rgb(0 0 0 / 1);

  --radius:      1.5rem;
  --radius-sm:   1.125rem;

  --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-hand: "Caveat", ui-rounded, cursive;
  --ease: cubic-bezier(.22, 1, .36, 1);
}

/* ── Base ───────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; background: var(--paper); }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.72;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; display: block; }
a { color: inherit; }
::selection { background: rgb(var(--sky-rgb) / .22); }
:focus-visible { outline: 2px solid var(--sky); outline-offset: 3px; border-radius: 6px; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}
.page { position: relative; background: var(--paper); overflow-x: hidden; }

/* A vignette, not a light source. The reference's ground is a dark room
   photographed, so the corners fall off and the centre lifts by a few points
   — no hue shift, no glow, nothing that reads as a screen emitting light. It
   is fixed, so the whole document sits inside one room rather than each
   section carrying its own. */
.page::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(ellipse 92% 78% at 50% 34%,
    rgb(var(--sky-rgb) / .05)  0%,
    rgb(var(--sky-rgb) / .03) 26%,
    rgb(var(--sky-rgb) / .013) 48%,
    rgb(var(--sky-rgb) / 0)   68%),
    radial-gradient(ellipse 120% 100% at 50% 50%,
      rgb(0 0 0 / 0)    52%,
      rgb(0 0 0 / .18)  82%,
      rgb(0 0 0 / .34) 100%);
}

/* The texture the reference actually has: film grain you can see, not a
   dither hidden under a gradient. It is what keeps a flat warm charcoal from
   looking like flat #141414, and it is doing the job the two-hue washes used
   to do. */
.page::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: .19;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 240 240' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.72' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 200px 200px;
}

/* ── Sections ───────────────────────────────────────────────────────────── */
.section {
  position: relative;
  padding: 5.5rem 1.5rem;
  overflow: hidden;
  scroll-margin-top: 5rem;
}
@media (min-width: 1024px) { .section { padding: 7rem 2.5rem; } }

/* A rule that fades out at both ends instead of a hairline drawn edge to
   edge. Neutral — a rule between two sections is structure, not an accent. */
.section--ruled::before {
  content: "";
  position: absolute;
  top: 0; left: 50%;
  transform: translateX(-50%);
  width: min(calc(100% - 3rem), 62rem);
  height: 1px;
  background: linear-gradient(90deg,
    rgb(var(--border-rgb) / 0),
    rgb(var(--border-rgb) / .5) 14%,
    rgb(var(--border-rgb) / 1)  50%,
    rgb(var(--border-rgb) / .5) 86%,
    rgb(var(--border-rgb) / 0));
}
.wrap { position: relative; max-width: 62rem; margin-inline: auto; }
/* A narrow measure, but hung off the same left edge as the full-width
   sections rather than centred inside them. Centring it made the page's left
   margin jump by forty pixels every other section; the reference holds one
   column and this now does too. */
.wrap--narrow { max-width: 44rem; margin-left: max(0px, calc((100% - 62rem) / 2)); margin-right: auto; }

/* What is left of the washes: a barely-there lift in the ground, painted in
   off-white rather than in a hue, so a section reads as a slightly lit part
   of the same room instead of as its own coloured field. The `--w1`/`--w2`
   positions the pages already set still place it. */
.wash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  --a1: .05;
  --a2: .04;
  /* Sections clip their overflow, so a wash that still had light in it at the
     section edge would be cut off in a straight line. Fading it out before
     the edge is what removes the seam between two sections. */
  -webkit-mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0), #000 9%, #000 91%, rgb(0 0 0 / 0));
          mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0), #000 9%, #000 91%, rgb(0 0 0 / 0));
}
.wash::before, .wash::after { content: ""; position: absolute; inset: 0; }
.wash::before {
  background: radial-gradient(ellipse 66% 62% at var(--w1, 20% 30%),
    rgb(var(--sky-rgb) / var(--a1))                0%,
    rgb(var(--sky-rgb) / calc(var(--a1) * .78))   24%,
    rgb(var(--sky-rgb) / calc(var(--a1) * .42))   48%,
    rgb(var(--sky-rgb) / calc(var(--a1) * .14))   74%,
    rgb(var(--sky-rgb) / 0)                      100%);
}
.wash::after {
  background: radial-gradient(ellipse 64% 60% at var(--w2, 82% 74%),
    rgb(var(--sky-rgb) / var(--a2))                0%,
    rgb(var(--sky-rgb) / calc(var(--a2) * .78))   24%,
    rgb(var(--sky-rgb) / calc(var(--a2) * .42))   48%,
    rgb(var(--sky-rgb) / calc(var(--a2) * .14))   74%,
    rgb(var(--sky-rgb) / 0)                      100%);
}

/* The hero is the one section with nothing above it, so it keeps its light
   all the way to the top of the page and only fades where it meets the
   section below. */
.wash--hero {
  --a1: .075;
  --a2: .055;
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 74%, rgb(0 0 0 / 0) 100%);
          mask-image: linear-gradient(to bottom, #000 0, #000 74%, rgb(0 0 0 / 0) 100%);
}

/* ── Type ───────────────────────────────────────────────────────────────── */
.eyebrow {
  display: flex;
  align-items: center;
  gap: .45rem;
  margin: 0 0 1.1rem;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--slate);
  line-height: 1.4;
}
.eyebrow::before {
  content: "";
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--sky);
  flex: none;
}
/* The reference sets its labels against ruled structure. This is the small
   version of that: the label, then a hairline running off to the right. */
.eyebrow::after {
  content: "";
  flex: 1 1 auto;
  max-width: 7rem;
  height: 1px;
  margin-left: .3rem;
  background: linear-gradient(90deg,
    rgb(var(--border-rgb) / 1),
    rgb(var(--border-rgb) / .45) 55%,
    rgb(var(--border-rgb) / 0));
}
h1, h2, h3, h4 { margin: 0; font-weight: 500; letter-spacing: -.025em; }
h1 { font-size: 1.875rem; line-height: 1.18; }
h2 { font-size: 1.25rem; line-height: 1.34; }
@media (min-width: 768px) { h1 { font-size: 2.25rem; } h2 { font-size: 1.5rem; } }
@media (min-width: 1024px) { h1 { font-size: 2.75rem; } h2 { font-size: 1.75rem; } }
.line { display: block; }
.i { font-style: italic; }
.semi { font-weight: 600; }

/* The one place type itself carries the gradient — the emphasised phrase in
   the page's own headline. */
.grad {
  color: var(--ink);
}

.lede  { margin: 1.4rem 0 0; max-width: 25rem; font-size: 13px; color: var(--slate); }
.copy  { margin: 1.4rem 0 0; max-width: 29rem; font-size: 13px; color: var(--slate); }
.copy strong { font-weight: 600; color: var(--ink); }
.copy + .copy { margin-top: 1.1rem; }

/* ── Controls ───────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .7rem 1.35rem;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: transform .25s var(--ease), box-shadow .25s var(--ease), filter .25s var(--ease);
}
.btn:hover { transform: translateY(-1px); filter: brightness(1.04); }
.btn:active { transform: scale(.98); }
.link {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  font-size: 11px;
  font-weight: 500;
  color: var(--slate);
  text-decoration: none;
  transition: color .25s var(--ease);
}
.link:hover { color: var(--ink); }
.arrow { transition: transform .25s var(--ease); }
.btn:hover .arrow, .link:hover .arrow, .nav__cta:hover .arrow { transform: translateX(2px); }
.arrow--back { transition: transform .25s var(--ease); }
.link:hover .arrow--back { transform: translateX(-2px); }
.bounce { animation: bounce 1.4s infinite; }
@keyframes bounce {
  0%, 100% { transform: translateY(-12%); animation-timing-function: cubic-bezier(.8, 0, 1, 1); }
  50% { transform: none; animation-timing-function: cubic-bezier(0, 0, .2, 1); }
}

/* ── Brand ──────────────────────────────────────────────────────────────── */
.brand { display: inline-flex; align-items: center; gap: .4rem; text-decoration: none; color: var(--ink); line-height: 1; }
.brand svg { width: 1.6em; height: 1.6em; flex: none; }
.brand span { font-weight: 600; letter-spacing: -.025em; }
.brand--lg { font-size: 1.125rem; }
.brand--sm { font-size: .75rem; }

/* ── Nav ────────────────────────────────────────────────────────────────── */
.nav { position: fixed; inset: 0 0 auto; z-index: 50; transition: background .4s var(--ease); }
.nav::after {
  content: "";
  position: absolute;
  bottom: 0; left: 50%;
  transform: translateX(-50%);
  width: min(calc(100% - 3rem), 62rem);
  height: 1px;
  opacity: 0;
  background: linear-gradient(90deg,
    rgb(var(--border-rgb) / 0),
    rgb(var(--border-rgb) / .5) 14%,
    rgb(var(--border-rgb) / 1)  50%,
    rgb(var(--border-rgb) / .5) 86%,
    rgb(var(--border-rgb) / 0));
  transition: opacity .4s var(--ease);
}
.nav.is-scrolled::after { opacity: 1; }
.nav.is-scrolled { background: var(--paper); }
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav.is-scrolled {
    background: color-mix(in srgb, var(--paper) 76%, transparent);
    -webkit-backdrop-filter: blur(16px);
    backdrop-filter: blur(16px);
  }
}
.nav__inner {
  max-width: 62rem;
  margin-inline: auto;
  height: 4rem;
  padding-inline: 1.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
@media (min-width: 1024px) { .nav__inner { padding-inline: 2.5rem; } }
.nav__links { display: none; gap: 1.35rem; font-size: 11px; font-weight: 500; color: var(--slate); }
.nav__links a { text-decoration: none; transition: color .25s var(--ease); }
.nav__links a:hover { color: var(--ink); }
.nav__links a[aria-current="page"] { color: var(--ink); }
@media (min-width: 768px) { .nav__links { display: flex; } }
.nav__cta { display: inline-flex; align-items: center; gap: .3rem; font-size: 11px; font-weight: 600; color: var(--ink); text-decoration: none; }

/* ── Hero ───────────────────────────────────────────────────────────────── */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 8rem 1.5rem 6rem;
  overflow: hidden;
}
.hero--full { min-height: 100vh; min-height: 100svh; padding-block: 6rem 5rem; }
@media (min-width: 1024px) { .hero { padding-inline: 2.5rem; } }
.hero__inner { position: relative; max-width: 62rem; margin-inline: auto; width: 100%; }
.hero__actions { margin-top: 2rem; display: flex; flex-wrap: wrap; align-items: center; gap: .9rem; }
.scroll-cue {
  position: absolute; left: 50%; bottom: 1.75rem;
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: .4rem;
  color: var(--slate);
}
.scroll-cue span { font-size: 9px; letter-spacing: .2em; text-transform: uppercase; }
.scroll-cue i {
  display: block;
  width: 1px;
  height: 1.75rem;
  background: linear-gradient(to bottom,
    rgb(var(--ink-rgb) / .5),
    rgb(var(--ink-rgb) / .22) 45%,
    rgb(var(--ink-rgb) / .06) 75%,
    rgb(var(--ink-rgb) / 0));
}

/* ── Spec line ───────────────────────────────────────────────────────────
   This was a cell lattice, and it read as a table dropped into the hero.
   The facts are the same; the box is gone. Three items on open ground with
   a lot of air between them — nothing to fence them in, nothing for the eye
   to have to parse before it reaches the words. */
.spec {
  display: flex;
  flex-wrap: wrap;
  gap: 1.6rem 3.25rem;
  margin: 3.25rem 0 0;
  padding: 0;
  list-style: none;
  max-width: 38rem;
}
.spec li { min-width: 0; }
.spec__k, .stack__k {
  display: block;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--slate);
}
.spec__v { display: block; margin-top: .45rem; font-size: 13px; font-weight: 500; letter-spacing: -.02em; color: rgb(var(--ink-rgb) / .82); }
.spec__v i { font-style: normal; color: inherit; }

/* ── The stack ───────────────────────────────────────────────────────────
   The product page opens by claiming two products sit on one layer. A row of
   three facts said that flatly and could have been about anything; this
   draws it instead. Two names side by side, a single rule running under both,
   and one line naming what the rule is. The rule is the whole argument — it
   is the only thing on the page that belongs to both columns at once, and it
   arrives last, after there are two things for it to join. */
.stack { margin: 3.25rem 0 0; max-width: 38rem; }
.stack__tops {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 560px) { .stack__tops { grid-template-columns: 1fr 1fr; gap: 2.75rem; } }
.stack__v {
  display: block;
  margin-top: .5rem;
  font-size: 1.0625rem;
  font-weight: 500;
  letter-spacing: -.025em;
  color: var(--ink);
}
.stack__v i { font-style: normal; color: inherit; }
.stack__note { display: block; margin-top: .3rem; font-size: 12px; color: var(--slate); }

.stack__seam {
  height: 1px;
  margin: 1.35rem 0 1.05rem;
  background: linear-gradient(90deg,
    rgb(var(--sky-rgb) / .5),
    rgb(var(--sky-rgb) / .38) 55%,
    rgb(var(--sky-rgb) / .16) 85%,
    rgb(var(--sky-rgb) / 0)  100%);
}
.stack__base { margin: 0; max-width: 30rem; font-size: 12px; color: var(--slate); }
.stack__base .stack__k { margin-bottom: .4rem; }

/* ── Notes ───────────────────────────────────────────────────────────────
   Open text under a rule that fades away, so a row reads as related
   paragraphs rather than three boxes. */
.notes { display: grid; gap: 2.25rem; margin-top: 3rem; }
@media (min-width: 860px) {
  .notes--3 { grid-template-columns: repeat(3, 1fr); gap: 2rem 2.75rem; }
  .notes--2 { grid-template-columns: repeat(2, 1fr); gap: 2rem 3rem; }
}
.note { position: relative; padding-top: 1.35rem; }
.note::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1px;
  background: linear-gradient(90deg,
    rgb(var(--border-rgb) / 1),
    rgb(var(--border-rgb) / .55) 45%,
    rgb(var(--border-rgb) / .18) 76%,
    rgb(var(--border-rgb) / 0)   96%);
}
.note__label {
  display: block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--slate);
}
.note h3 { margin: .55rem 0 .4rem; font-size: 13px; font-weight: 600; letter-spacing: -.015em; }
.note p { margin: 0; font-size: 12px; color: var(--slate); }
.note__meta { display: inline-block; margin-left: .5rem; font-size: 10px; font-weight: 600; color: var(--sky); letter-spacing: .04em; }

/* A numbered row: the figure that used to sit above these points is gone, so
   the numerals do the counting the drawing used to do. */
.notes--num .note { padding-right: 2.75rem; }
.note__num {
  position: absolute;
  top: .5rem; right: 0;
  font-size: 1.75rem;
  font-weight: 600;
  letter-spacing: -.05em;
  line-height: 1;
  color: rgb(var(--ink-rgb) / .13);
  pointer-events: none;
}

/* ── The naming ──────────────────────────────────────────────────────────
   This was a card, and the card was the problem: a box, a label, a coloured
   prefix and a hairline seam, all to say one thing that type can say by
   itself. What is left is the word, large and in one ink, with its front
   quietly changing. Nothing frames it. */
.naming { margin-top: 2.75rem; }
.naming .copy { margin-top: 1.1rem; max-width: 27rem; }
.naming .eyebrow { margin-bottom: .9rem; }

.wordmark {
  display: flex;
  align-items: baseline;
  margin: 0;
  font-size: 1.75rem;
  font-weight: 500;
  letter-spacing: -.035em;
  line-height: 1.15;
  color: var(--ink);
}
.wordmark--lg { font-size: 2.25rem; }
@media (min-width: 768px) { .wordmark--lg { font-size: 3rem; } }
/* Only the front of the word moves, which is the whole idea of the naming —
   so it is set in the same ink as the ending and simply swaps. */
.wordmark__prefix {
  transition: opacity .45s var(--ease), transform .45s var(--ease);
}
.wordmark__prefix.is-out { opacity: 0; transform: translateY(-.12em); }
.wordmark__blank { width: 2.6em; align-self: center; border-bottom: 1px solid rgb(var(--ink-rgb) / .26); }

/* ── Pulled quote ────────────────────────────────────────────────────────
   A line the product would actually say, set as a quote rather than mocked
   up inside a fake card. */
.quote { margin: 2.5rem 0 0; padding-left: 1.35rem; max-width: 30rem; border-left: 1px solid rgb(var(--ink-rgb) / .14); }
.quote p { margin: 0; font-size: 1.0625rem; font-weight: 400; letter-spacing: -.02em; line-height: 1.45; color: var(--ink); }
.quote cite { display: block; margin-top: .7rem; font-size: 12px; font-style: normal; color: var(--slate); }

/* ── The swap ────────────────────────────────────────────────────────────
   The page claims the model underneath is replaceable and the layer on top
   is not. Two ruled rows say it in the fewest words available, and the top
   row proves it by actually changing while the bottom one never does — the
   same device as the naming, pointed at a different sentence. */
.swap {
  margin: 2.75rem 0 0;
  max-width: 34rem;
  padding: 0;
}
.swap__row {
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
  gap: .3rem;
  padding: 1.15rem 0 1.2rem;
}
@media (min-width: 560px) {
  .swap__row { grid-template-columns: 7.5rem 1fr; gap: 1rem; align-items: baseline; }
}
.swap__row::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 1px;
  background: linear-gradient(90deg,
    rgb(var(--sky-rgb) / .4),
    rgb(var(--sky-rgb) / .1) 55%,
    rgb(var(--sky-rgb) / 0)  92%);
}
.swap__k {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--slate);
  line-height: 2.2;
}
.swap__v {
  margin: 0;
  font-size: 1.0625rem;
  font-weight: 500;
  letter-spacing: -.02em;
  line-height: 1.45;
  color: var(--ink);
}
.copy--tight { margin-top: 1.15rem; }

/* ── Figures ─────────────────────────────────────────────────────────────
   Two drawings on the whole site, and only where a sentence genuinely cannot
   do the job: a proportion on `studinari.html`, a curve on `ruminari.html`.
   An earlier pass had them boxed, filled and lit from two hues, and they read
   as charts pasted into a page that is otherwise all air — so they are line
   work now. Structure is `currentColor` at a tenth to a fifth; the sky is
   spent on the single thing each drawing argues and on nothing else; there is
   no frame, no plot box, and no fill that could be mistaken for a glow. The
   one filled region on the site is the band between two lines on the decay
   chart, because that band *is* the subject. */
.fig { margin: 3rem 0 0; }
.fig__scroll { overflow-x: auto; overscroll-behavior-x: contain; }
.fig__scroll > svg { display: block; width: 100%; height: auto; color: var(--ink); }
.fig figcaption {
  margin-top: 1.4rem;
  max-width: 29rem;
  font-size: 12px;
  line-height: 1.75;
  color: var(--slate);
}

/* Scaling a 560-unit drawing into a 327px phone would take its 11px labels
   down to about six. Below that width the drawing keeps its size and scrolls
   inside its own box; the page itself never scrolls sideways. */
@media (max-width: 640px) {
  .fig__scroll { padding-bottom: .5rem; }
  .fig__scroll > svg { min-width: 500px; }
}

.fig text  { font-family: var(--font-sans); fill: currentColor; }
.fig .t     { font-size: 11px; fill-opacity: .78; }
.fig .t-key { font-size: 9px; font-weight: 700; letter-spacing: .16em; fill-opacity: .5; }
.fig .t-sky { fill: var(--sky); fill-opacity: .85; }

.fig .ln      { stroke: currentColor; stroke-opacity: .2;  fill: none; }
.fig .ln-soft { stroke: currentColor; stroke-opacity: .1;  fill: none; }
.fig .ln-dash { stroke: currentColor; stroke-opacity: .22; fill: none; stroke-dasharray: 3 4; }
.fig .ln-sky  { stroke: var(--sky); stroke-opacity: .85; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.fig .dot-sky { fill: var(--sky); }
.fig .band    { fill: var(--sky); fill-opacity: .07; }

/* ── Links inside prose ──────────────────────────────────────────────────
   A link in the middle of a paragraph was being styled inline on the one
   page that had them, which meant it could not pick up the underline the
   rest of the site gives a link on hover. It is a class now: the sky marks
   it, and the rule underneath draws on hover like every other rule here. */
.tie {
  position: relative;
  color: var(--sky);
  font-weight: 600;
  text-decoration: none;
}
.tie::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 1px;
  background: currentColor;
  opacity: 0;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .35s var(--ease), opacity .35s var(--ease);
}
.tie:hover::after { opacity: .6; transform: none; }

/* ── Project previews (index) ───────────────────────────────────────────── */
.previews { display: grid; gap: 1.25rem; margin-top: 3rem; }
@media (min-width: 820px) { .previews { grid-template-columns: 1fr 1fr; gap: 1.5rem; } }
.preview {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 1.9rem;
  border-radius: var(--radius);
  background:
    linear-gradient(165deg,
      rgb(var(--ink-rgb) / .045) 0%,
      rgb(var(--ink-rgb) / .018) 40%,
      rgb(var(--ink-rgb) / 0)   100%),
    var(--surface);
  box-shadow: var(--lift);
  text-decoration: none;
  color: inherit;
  overflow: hidden;
  transition: transform .35s var(--ease), box-shadow .35s var(--ease);
}
.preview:hover { transform: translateY(-3px); box-shadow: var(--lift-hover); }
.preview__head { display: flex; align-items: center; gap: .55rem; flex-wrap: wrap; }
.preview__name { font-size: 1.0625rem; font-weight: 600; letter-spacing: -.03em; }
.preview__name i { font-style: normal; color: inherit; }
.preview__what { margin: .6rem 0 0; font-size: 10px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; color: var(--slate); }
.preview__desc { margin: .8rem 0 0; font-size: 12px; color: var(--slate); }
.preview__go {
  position: relative;
  margin-top: auto;
  padding-top: 1.75rem;
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink);
}
.preview__go::before {
  content: "";
  position: absolute;
  top: .85rem; left: 0; right: 0;
  height: 1px;
  background: linear-gradient(90deg,
    rgb(var(--border-rgb) / 1),
    rgb(var(--border-rgb) / .45) 46%,
    rgb(var(--border-rgb) / 0)   88%);
}
.preview:hover .preview__go { color: var(--ink); }
.preview:hover .arrow { transform: translateX(3px); }

/* ── Roster (studinari team) ─────────────────────────────────────────────
   One lead and a bench, said with hierarchy instead of an org chart: the
   Consultant gets a real card, the specialists soft tinted blocks. */
.roster { display: grid; gap: 1.25rem; margin-top: 3rem; }
@media (min-width: 860px) { .roster { grid-template-columns: 19rem 1fr; gap: 1.75rem; align-items: start; } }

.lead {
  position: relative;
  padding: 1.9rem;
  border-radius: var(--radius);
  background:
    linear-gradient(165deg,
      rgb(var(--ink-rgb) / .05)  0%,
      rgb(var(--ink-rgb) / .02) 40%,
      rgb(var(--ink-rgb) / 0)  100%),
    var(--surface);
  box-shadow: var(--lift);
  overflow: hidden;
}
.lead::before {
  content: "";
  position: absolute;
  top: 0; left: 10%; right: 10%;
  height: 1px;
  background: linear-gradient(90deg,
    rgb(var(--ink-rgb) / 0),
    rgb(var(--ink-rgb) / .04) 14%,
    rgb(var(--ink-rgb) / .13) 50%,
    rgb(var(--ink-rgb) / .04) 86%,
    rgb(var(--ink-rgb) / 0));
}
.lead__role { display: flex; align-items: center; gap: .45rem; font-size: 10px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; color: var(--slate); }
.lead__role::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--sky); flex: none; }
.lead h3 { margin: .8rem 0 .5rem; font-size: 14px; font-weight: 600; }
.lead p { margin: 0; font-size: 12px; color: var(--slate); }
.lead__flow {
  position: relative;
  display: flex;
  align-items: center;
  gap: .45rem;
  margin-top: 1.35rem;
  padding-top: 1.05rem;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.lead__flow::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, rgb(var(--border-rgb) / 1), rgb(var(--border-rgb) / .4) 56%, rgb(var(--border-rgb) / 0) 88%); }
.lead__flow i { font-style: normal; color: var(--sky); }

.roles { display: grid; gap: .75rem; }
@media (min-width: 560px) { .roles { grid-template-columns: 1fr 1fr; } }
.role {
  position: relative;
  padding: 1rem 1.15rem 1.05rem 2rem;
  border-radius: var(--radius-sm);
  background: rgb(var(--surface-rgb) / .55);
  transition: transform .3s var(--ease), background .3s var(--ease);
}
.role::before { content: ""; position: absolute; left: 1.15rem; top: 1.35rem; width: 5px; height: 5px; border-radius: 50%; background: var(--sky); opacity: .75; }
.role:hover {
  transform: translateY(-2px);
  background: var(--surface-2);
}
.role__name { display: block; font-size: 12px; font-weight: 600; letter-spacing: -.01em; }
.role p { margin: .3rem 0 0; font-size: 11px; color: var(--slate); }

/* ── Founder ────────────────────────────────────────────────────────────── */
.founder { display: grid; gap: 2.25rem; align-items: start; margin-top: 2.5rem; }
@media (min-width: 768px) { .founder { grid-template-columns: 11.5rem 1fr; gap: 3rem; } }
.portrait {
  position: relative;
  margin: 0;
  width: 100%;
  max-width: 11.5rem;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--lift);
}
.portrait img { width: 100%; height: 100%; object-fit: cover; position: relative; z-index: 1; }
.portrait__fallback {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--font-hand);
  font-size: 2.5rem;
  color: var(--sky);
  background: linear-gradient(155deg,
    rgb(var(--ink-rgb) / .07)  0%,
    rgb(var(--ink-rgb) / .028) 44%,
    rgb(var(--ink-rgb) / 0)   100%);
}
.statement { margin: 0; display: flex; flex-direction: column; gap: 1.1rem; max-width: 29rem; }
.statement p { margin: 0; font-size: 13px; color: var(--slate); }
.statement .semi { color: var(--ink); }
.sign { position: relative; margin-top: .75rem; padding-top: 1.2rem; }
.sign::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, rgb(var(--border-rgb) / 1), rgb(var(--border-rgb) / .45) 50%, rgb(var(--border-rgb) / .12) 78%, rgb(var(--border-rgb) / 0) 96%); }
.sign__name { display: block; font-family: var(--font-hand); font-size: 1.6rem; font-weight: 600; line-height: 1.2; color: var(--ink); }
.sign__role { display: block; margin-top: .1rem; font-size: 10px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; color: var(--slate); }

/* ── Footer ─────────────────────────────────────────────────────────────── */
.footer { position: relative; padding: 3rem 1.5rem 2rem; background: var(--paper-2); }
.footer::before {
  content: "";
  position: absolute; top: 0; left: 50%;
  transform: translateX(-50%);
  width: min(calc(100% - 3rem), 62rem);
  height: 1px;
  background: linear-gradient(90deg,
    rgb(var(--border-rgb) / 0),
    rgb(var(--border-rgb) / .5) 14%,
    rgb(var(--border-rgb) / 1)  50%,
    rgb(var(--border-rgb) / .5) 86%,
    rgb(var(--border-rgb) / 0));
}
@media (min-width: 1024px) { .footer { padding-inline: 2.5rem; } }
.footer__inner { max-width: 62rem; margin-inline: auto; display: grid; gap: 2.25rem; }
@media (min-width: 640px) { .footer__inner { grid-template-columns: 1.7fr 1fr 1fr; } }
.footer__tag { margin: .9rem 0 0; max-width: 21rem; font-size: 11px; color: var(--slate); }
.footer h4 { margin: 0 0 .9rem; font-size: 10px; font-weight: 700; letter-spacing: .2em; text-transform: uppercase; color: var(--slate); }
.footer ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .55rem; }
.footer li a { font-size: 11px; font-weight: 500; text-decoration: none; transition: color .25s var(--ease); }
.footer li a:hover { color: var(--sky); }
.footer__base {
  position: relative;
  max-width: 62rem;
  margin: 2.5rem auto 0;
  padding-top: 1.2rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: .5rem;
  font-size: 10px;
  color: var(--slate);
}
.footer__base::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, rgb(var(--border-rgb) / 0), rgb(var(--border-rgb) / .9) 50%, rgb(var(--border-rgb) / 0)); }

/* ── Reveal ─────────────────────────────────────────────────────────────── */
.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
  transition-delay: var(--d, 0s);
}
.reveal--sm { transform: translateY(9px); }
.reveal.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .reveal { opacity: 1; transform: none; } }

/* ══════════════════════════════════════════════════════════════════════════
   Motion

   The page was already fading things in; this is the layer that makes it feel
   alive rather than merely arriving. Three rules held throughout:

   1. MOTION CARRIES MEANING OR IT DOES NOT HAPPEN. Headline lines rise out of
      a mask because a sentence is read top-down. Hairlines draw outward from
      where the eye lands. The prefix swaps because the prefix is the idea.
      Nothing spins, nothing pops, nothing slides in from off-screen because
      it looked good in a demo.

   2. IT IS A DARK ROOM, NOT A LIGHT SHOW. The same restraint the palette
      keeps: the ambient drift in the hero moves a couple of percent over
      twenty-odd seconds, the pointer light inside a card is five points of
      off-white, and the only saturated moving thing on the page is the sky
      dot in the scroll cue.

   3. EVERY ANIMATION IS COMPOSITED OR CHEAP. Transform and opacity only, on a
      handful of layers, and the ambient loops are gated on the section being
      on screen so an idle tab is not painting six of them.

   Reduced motion is not an afterthought here — the block at the bottom
   returns every element to its resting state rather than merely shortening
   the durations.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Page-to-page ────────────────────────────────────────────────────────
   The three pages share a shell, so a hard swap between them throws away the
   one thing the reader is holding on to: that the nav and the ground did not
   move. Where the browser supports it, they cross-fade instead. */
@view-transition { navigation: auto; }
/* A cross-fade is not movement, but it is still a transition the reader did
   not ask for; the preference turns it off along with everything else. */
@media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }
::view-transition-old(root) { animation-duration: .32s; animation-timing-function: var(--ease); }
::view-transition-new(root) { animation-duration: .42s; animation-timing-function: var(--ease); }

/* ── Headline lines ──────────────────────────────────────────────────────
   A line of a headline rises into its own mask. The script wraps the text and
   adds `line--mask`, so a page whose script never ran simply shows the words:
   the outer fade is switched off here precisely because the inner element is
   now the thing that moves. The padding/margin pair gives descenders and the
   italic's overhang somewhere to live before the clip. */
.line--mask {
  overflow: hidden;
  padding-bottom: .16em;
  margin-bottom: -.16em;
  padding-right: .08em;
  margin-right: -.08em;
}
.line--mask.reveal { opacity: 1; transform: none; }
.line__in {
  display: block;
  transform: translateY(110%);
  transition: transform .95s var(--ease);
  transition-delay: var(--d, 0s);
}
.line--mask.reveal.is-in .line__in { transform: none; }

/* ── Hairlines that draw ─────────────────────────────────────────────────
   Every rule on this page fades out at one end, which means it already has a
   direction. Drawing it in that direction costs nothing and turns a static
   divider into the thing that introduces the block under it. */
.eyebrow::after {
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .9s var(--ease);
  transition-delay: calc(var(--d, 0s) + .16s);
}
.eyebrow.is-in::after, .eyebrow:not(.reveal)::after { transform: none; }

.note::before {
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 1s var(--ease);
  transition-delay: calc(var(--d, 0s) + .08s);
}
.note.is-in::before, .note:not(.reveal)::before { transform: none; }

/* The section rule grows from the middle out — it is centred and symmetric,
   so it is the one hairline with no side to start from. */
.section--ruled::before {
  opacity: 0;
  transform: translateX(-50%) scaleX(.18);
  transition: transform 1.3s var(--ease), opacity .8s var(--ease);
}
.section--ruled.is-lit::before { opacity: 1; transform: translateX(-50%) scaleX(1); }

/* ── Nav ─────────────────────────────────────────────────────────────────
   Once the nav has a background it also has a job: say how far through the
   page you are, and which section you are standing in. The progress line
   replaces nothing — it rides the hairline that was already there. */
.nav__progress {
  position: absolute;
  bottom: 0; left: 50%;
  transform: translateX(-50%);
  width: min(calc(100% - 3rem), 62rem);
  height: 1px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .4s var(--ease);
}
.nav.is-scrolled .nav__progress { opacity: 1; }
.nav__progress i {
  display: block;
  height: 100%;
  transform: scaleX(var(--p, 0));
  transform-origin: 0 50%;
  background: linear-gradient(90deg,
    rgb(var(--sky-rgb) / .25),
    rgb(var(--sky-rgb) / .8));
}

.nav__links a { position: relative; }
.nav__links a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -.4rem;
  height: 1px;
  background: currentColor;
  opacity: 0;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .35s var(--ease), opacity .35s var(--ease);
}
.nav__links a:hover::after,
.nav__links a.is-active::after,
.nav__links a[aria-current="page"]::after { opacity: .45; transform: none; }
.nav__links a.is-active { color: var(--ink); }

/* The mark's ring is drawn as a dash with a gap; rotating it on hover is the
   one place the logo admits it is a loop. */
.brand svg circle { transform-origin: 50% 50%; transition: transform .8s var(--ease); }
.brand:hover svg circle:first-child { transform: rotate(140deg); }

/* ── Buttons and links ───────────────────────────────────────────────────── */
.btn { position: relative; overflow: hidden; }
.btn::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-130%) skewX(-14deg);
  background: linear-gradient(90deg,
    rgb(var(--paper-rgb) / 0),
    rgb(var(--paper-rgb) / .14) 50%,
    rgb(var(--paper-rgb) / 0));
  transition: transform .8s var(--ease);
}
.btn:hover::after { transform: translateX(130%) skewX(-14deg); }

.footer li a { position: relative; }
.footer li a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 1px;
  background: currentColor;
  opacity: 0;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .35s var(--ease), opacity .35s var(--ease);
}
.footer li a:hover::after { opacity: .55; transform: none; }

/* ── Cards under the pointer ─────────────────────────────────────────────
   A card lifting on hover says it is clickable. The light following the
   pointer says the surface has a shape. It is five points of off-white and
   nothing else — a lamp in the room, not a glow coming off the screen. */
.preview::before {
  content: "";
  position: absolute;
  top: 0; left: 12%; right: 12%;
  height: 1px;
  opacity: 0;
  background: linear-gradient(90deg,
    rgb(var(--ink-rgb) / 0),
    rgb(var(--ink-rgb) / .2) 50%,
    rgb(var(--ink-rgb) / 0));
  transition: opacity .45s var(--ease);
  pointer-events: none;
}
.preview:hover::before { opacity: 1; }
.preview::after, .lead::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity .45s var(--ease);
  background: radial-gradient(16rem 16rem at var(--mx, 50%) var(--my, 0%),
    rgb(var(--ink-rgb) / .055),
    rgb(var(--ink-rgb) / .022) 42%,
    rgb(var(--ink-rgb) / 0)    72%);
}
.preview:hover::after, .lead:hover::after { opacity: 1; }
.preview__name, .preview__what, .preview__desc, .preview__go { position: relative; }

/* ── Hero ────────────────────────────────────────────────────────────────
   The status dot in the eyebrow is the page's one live marker, so it is the
   one thing allowed to pulse. */
.hero .eyebrow::before { animation: dot 3.2s ease-in-out infinite; }
@keyframes dot {
  0%, 100% { box-shadow: 0 0 0 0 rgb(var(--sky-rgb) / .3); }
  70%      { box-shadow: 0 0 0 6px rgb(var(--sky-rgb) / 0); }
}

/* The cue was mis-centred the moment it became a reveal — `.reveal` wrote
   over its `translateX(-50%)`. Both states carry the centring now, and the
   cue steps out of the way as soon as the reader takes its advice. */
.scroll-cue.reveal { transform: translateX(-50%) translateY(14px); }
.scroll-cue.reveal.is-in { transform: translateX(-50%); }
.scroll-cue { transition: opacity .5s var(--ease), transform .7s var(--ease); }
.nav.is-scrolled ~ .hero .scroll-cue {
  opacity: 0;
  transform: translateX(-50%) translateY(10px);
  transition-delay: 0s;
}
.scroll-cue i { position: relative; overflow: hidden; }
.scroll-cue i::after {
  content: "";
  position: absolute;
  top: 0; left: 0;
  width: 1px; height: .55rem;
  background: linear-gradient(to bottom, rgb(var(--sky-rgb) / 0), var(--sky));
  animation: cue 2.4s var(--ease) infinite;
}
@keyframes cue {
  0%       { transform: translateY(-.55rem); opacity: 0; }
  22%      { opacity: 1; }
  75%      { opacity: 1; }
  100%     { transform: translateY(1.75rem); opacity: 0; }
}

/* Two facts on open ground: they land one after the other rather than as a
   pair, which is roughly how they get read. */
.spec.reveal li {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
  transition-delay: calc(var(--d, 0s) + .1s);
}
.spec.reveal li:nth-child(2) { transition-delay: calc(var(--d, 0s) + .2s); }
.spec.reveal li:nth-child(3) { transition-delay: calc(var(--d, 0s) + .3s); }
.spec.reveal.is-in li { opacity: 1; transform: none; }

/* ── Ambient drift ───────────────────────────────────────────────────────
   The washes move — barely. A couple of percent over twenty-odd seconds is
   below the threshold at which anyone would call it an animation, which is
   exactly the point: the ground stops reading as a flat fill. The script
   marks a section live only while it is on screen, so nothing off screen is
   still painting. */
.is-live .wash::before { animation: drift-a 27s ease-in-out infinite alternate; }
.is-live .wash::after  { animation: drift-b 34s ease-in-out infinite alternate; }
@keyframes drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(2.6%, -2%, 0) scale(1.09); }
}
@keyframes drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.07); }
  to   { transform: translate3d(-2.4%, 2.2%, 0) scale(1); }
}

/* ── Portrait ────────────────────────────────────────────────────────────── */
.portrait img { transition: transform .7s var(--ease); }
.portrait:hover img { transform: scale(1.035); }

/* ── Scroll-linked ───────────────────────────────────────────────────────
   Where the browser can drive an animation off the scroll position itself,
   the hero hands the page over instead of just leaving the viewport: the
   words settle back and dim, the light behind them lags. Everything here is
   an enhancement — no fallback is needed because the resting state is the
   design as it already stood. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero--full .hero__inner {
      animation: hero-settle linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 70vh;
    }
    .hero .wash--hero {
      animation: hero-lag linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 100vh;
    }
  }
}
@keyframes hero-settle {
  to { opacity: .18; transform: translateY(-2.5rem); }
}
@keyframes hero-lag {
  to { transform: translateY(9%); }
}

/* ── Reduced motion ──────────────────────────────────────────────────────
   Not shortened durations — resting states. Anything that would still be
   mid-transform when the transition is cut to nothing is put back by hand. */
@media (prefers-reduced-motion: reduce) {
  .line__in { transform: none; }
  .eyebrow::after, .note::before { transform: none; }
  .section--ruled::before { opacity: 1; transform: translateX(-50%); }
  .scroll-cue.reveal, .scroll-cue.reveal.is-in { transform: translateX(-50%); }
  .scroll-cue i::after, .hero .eyebrow::before { animation: none; }
  .scroll-cue i::after { display: none; }
  .spec.reveal li { opacity: 1; transform: none; }
  .is-live .wash::before, .is-live .wash::after { animation: none; }
  .nav__progress { display: none; }
}

/* ── The prefix swap ─────────────────────────────────────────────────────
   The one animation on the page that is an argument rather than a flourish:
   the front of the word changes, the ending never does. Two things were
   missing from the first pass. It left upward and arrived from above, so it
   read as a ticker rolling one way instead of one word being replaced by
   another. And the ending jumped sideways the instant the text changed,
   because a wider prefix is simply wider — the width is animated now, so
   `nari` slides to its new place while the prefix is out of sight. */
.wordmark__prefix {
  display: inline-block;
  flex: none;
  white-space: pre;
  text-align: left;
  will-change: opacity, transform;
  transition: opacity .42s var(--ease),
              transform .42s var(--ease),
              filter .42s var(--ease),
              width .52s var(--ease);
}
.wordmark__prefix.is-out { opacity: 0; transform: translateY(-.3em); filter: blur(3px); }
.wordmark__prefix.is-pre { opacity: 0; transform: translateY(.3em);  filter: blur(3px); }
@media (prefers-reduced-motion: reduce) {
  .wordmark__prefix { width: auto !important; }
}

/* ── The swap ────────────────────────────────────────────────────────────
   Same hairline treatment as the notes, and the same reason: the rule
   introduces the row under it rather than sitting there already drawn. */
.swap.reveal .swap__row::before {
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 1s var(--ease);
  transition-delay: calc(var(--d, 0s) + .08s);
}
.swap.reveal .swap__row:nth-child(2)::before { transition-delay: calc(var(--d, 0s) + .22s); }
.swap.reveal.is-in .swap__row::before { transform: none; }

/* The live half of the argument. It leaves upward and the replacement
   arrives from below — a substitution, not a ticker — and the width travels
   with it so the row does not jump. */
.swap__live {
  display: inline-block;
  white-space: pre;
  will-change: opacity, transform;
  transition: opacity .42s var(--ease),
              transform .42s var(--ease),
              filter .42s var(--ease),
              width .52s var(--ease);
}
.swap__live.is-out { opacity: 0; transform: translateY(-.3em); filter: blur(3px); }
.swap__live.is-pre { opacity: 0; transform: translateY(.3em);  filter: blur(3px); }

/* ── The signature ───────────────────────────────────────────────────────
   The one hand-written thing on the site, so it is the one thing allowed to
   arrive as if it were being written: a mask wipes left to right across it
   once the statement above has landed. */
.sign__name {
  -webkit-mask-image: linear-gradient(90deg, #000 0 40%, rgb(0 0 0 / 0) 62%);
          mask-image: linear-gradient(90deg, #000 0 40%, rgb(0 0 0 / 0) 62%);
  -webkit-mask-size: 260% 100%;
          mask-size: 260% 100%;
  -webkit-mask-position: 100% 0;
          mask-position: 100% 0;
  transition: -webkit-mask-position 1.5s var(--ease), mask-position 1.5s var(--ease);
  transition-delay: calc(var(--d, 0s) + .35s);
}
.statement.is-in .sign__name { -webkit-mask-position: 0 0; mask-position: 0 0; }

/* ── The founder's letter ────────────────────────────────────────────────
   Three paragraphs and a signature arriving together is a block landing. One
   after another is somebody talking, which is what the section is. The outer
   fade is switched off so only the parts move. */
.statement.reveal { opacity: 1; transform: none; }
.statement.reveal > * {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity .75s var(--ease), transform .75s var(--ease);
}
.statement.reveal > :nth-child(1) { transition-delay: calc(var(--d, 0s) + .04s); }
.statement.reveal > :nth-child(2) { transition-delay: calc(var(--d, 0s) + .16s); }
.statement.reveal > :nth-child(3) { transition-delay: calc(var(--d, 0s) + .28s); }
.statement.reveal > :nth-child(4) { transition-delay: calc(var(--d, 0s) + .44s); }
.statement.reveal.is-in > * { opacity: 1; transform: none; }
/* …and the hand-written name is wiped in last, after its block has settled. */
.sign__name { transition-delay: calc(var(--d, 0s) + .95s); }

/* ── The numerals ────────────────────────────────────────────────────────
   The number is the quietest thing in the cell and it arrives last, so the
   heading is read before the count rather than with it. */
.notes--num .note.reveal .note__num {
  opacity: 0;
  transform: translateY(.22em);
  transition: opacity .85s var(--ease), transform .85s var(--ease);
  transition-delay: calc(var(--d, 0s) + .32s);
}
.notes--num .note.reveal.is-in .note__num { opacity: 1; transform: none; }

/* ── The pulled quote ────────────────────────────────────────────────────
   The rule down its left side draws top to bottom, the direction the quote
   is about to be read in. A border cannot be transformed, so it is a column
   of its own. */
.quote { position: relative; border-left: 0; }
.quote::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 1px;
  background: rgb(var(--ink-rgb) / .14);
  transform: scaleY(0);
  transform-origin: 50% 0;
  transition: transform .9s var(--ease);
  transition-delay: var(--d, 0s);
}
.quote.is-in::before, .quote:not(.reveal)::before { transform: none; }

/* ── The name that is not written yet ────────────────────────────────────
   The blank stands in for a name nobody has been told. Drawing it in is the
   closest a rule gets to being about to be filled. */
.wordmark__blank {
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 1s var(--ease);
  transition-delay: calc(var(--d, 0s) + .18s);
}
.naming.is-in .wordmark__blank, .naming:not(.reveal) .wordmark__blank { transform: none; }

/* ── Figures draw themselves ─────────────────────────────────────────────
   The same idea as the hairlines above, given a whole drawing: a curve is
   drawn in the direction it is read, the blocks grow from the start of the
   day, and the labels arrive once there is something for them to label.
   `pathLength="1"` normalises every path so one dash rule covers all of
   them without measuring anything. */
.fig .draw {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 1.7s var(--ease);
  transition-delay: calc(var(--d, 0s) + .1s);
}
.fig.is-in .draw { stroke-dashoffset: 0; }
/* The lit line is the answer to the grey one, so it is drawn second. */
.fig .draw--late { transition-delay: calc(var(--d, 0s) + .5s); }

.fig .grow {
  transform: scaleX(0);
  transform-origin: 0 50%;
  transform-box: fill-box;
  transition: transform 1s var(--ease);
  transition-delay: calc(var(--d, 0s) + .2s + var(--i, 0) * .13s);
}
.fig.is-in .grow { transform: none; }

.fig .pop {
  opacity: 0;
  transform: scale(0);
  transform-box: fill-box;
  transform-origin: 50% 50%;
  transition: opacity .4s var(--ease), transform .55s var(--ease);
  transition-delay: calc(var(--d, 0s) + .75s + var(--i, 0) * .13s);
}
.fig.is-in .pop { opacity: 1; transform: none; }

.fig .fade {
  opacity: 0;
  transition: opacity .8s var(--ease);
  transition-delay: calc(var(--d, 0s) + .6s + var(--i, 0) * .07s);
}
.fig.is-in .fade { opacity: 1; }

/* ── The stack ───────────────────────────────────────────────────────────
   One name, then the other, then the rule that joins them, then what the
   rule is. The order is the sentence. */
.stack.reveal { opacity: 1; transform: none; }
.stack.reveal .stack__tops li,
.stack.reveal .stack__base {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
  transition-delay: calc(var(--d, 0s) + .06s);
}
.stack.reveal .stack__tops li:nth-child(2) { transition-delay: calc(var(--d, 0s) + .18s); }
.stack.reveal .stack__base { transition-delay: calc(var(--d, 0s) + .62s); }
.stack.reveal.is-in .stack__tops li,
.stack.reveal.is-in .stack__base { opacity: 1; transform: none; }

.stack.reveal .stack__seam {
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 1.1s var(--ease);
  transition-delay: calc(var(--d, 0s) + .38s);
}
.stack.reveal.is-in .stack__seam { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .swap.reveal .swap__row::before { transform: none; }
  .swap__live { width: auto !important; }
  .sign__name {
    -webkit-mask-image: none;
            mask-image: none;
  }
  .statement.reveal > * { opacity: 1; transform: none; }
  .notes--num .note.reveal .note__num { opacity: 1; transform: none; }
  .quote::before { transform: none; }
  .wordmark__blank { transform: none; }
  .fig .draw { stroke-dashoffset: 0; }
  .fig .grow { transform: none; }
  .fig .pop, .fig .fade { opacity: 1; transform: none; }
  .stack.reveal .stack__tops li,
  .stack.reveal .stack__base { opacity: 1; transform: none; }
  .stack.reveal .stack__seam { transform: none; }
}
