/* =============================================================================
   manif.ai — page stylesheet
   -----------------------------------------------------------------------------
   Layered on /tokens.css, which supplies the brand palette and Public Sans and
   is global-safe. This file is the whole of the element and component layer for
   manif.ai and is shared by all three pages; site.css is deliberately not
   loaded, being the element layer for the coming-soon shell this replaced.

   Order: tokens → reset → primitives → layout → sections.
   Type rules follow packages/brand/TYPOGRAPHY.md.
   ========================================================================== */

/* =============================================================================
   manif — stealth site prototype
   Single stylesheet. Order: tokens → reset → primitives → layout → sections.
   No build step, no external dependencies.
   ========================================================================== */

/* ── Fonts ──────────────────────────────────────────────────────────────────
   Public Sans arrives from tokens.css as a variable font served out of
   /fonts/, so there are no @font-face rules here. See the head. */

/* ── Tokens ─────────────────────────────────────────────────────────────── */
:root {
  /* Surface */
  --black:        #08090a;
  --black-deep:   #050506;
  --graphite:     #1f2933;
  --graphite-dim: #14181d;

  /* Ink */
  --off-white:    #f4f3f0;
  --steel:        #8d949b;
  --steel-dim:    #666c73;
  --muted:        #9aa0a6;

  /* Accent — used sparingly */
  --terracotta:   #c65a2e;
  --terracotta-dim: rgba(198, 90, 46, 0.42);

  /* Hairlines */
  --line:         rgba(244, 243, 240, 0.09);
  --line-strong:  rgba(244, 243, 240, 0.18);

  /* Type */
  --font-sans: 'Public Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;

  /* Metrics */
  --gutter: clamp(20px, 5vw, 72px);
  --container: 1280px;
  --header-h: 74px;
  --section-y: clamp(88px, 12vw, 176px);
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

@media (max-width: 700px) {
  :root { --header-h: 62px; }
}

/* ── Reset ──────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body, h1, h2, h3, p, ul, figure { margin: 0; }
ul { padding: 0; list-style: none; }
img, svg { display: block; max-width: 100%; }
button, input, textarea { font: inherit; color: inherit; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

body {
  background: var(--black);
  color: var(--off-white);
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 16px;
  line-height: 1.55;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

::selection { background: rgba(198, 90, 46, 0.32); color: var(--off-white); }

/* ── Focus ──────────────────────────────────────────────────────────────── */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--terracotta);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip-link {
  position: fixed;
  top: 12px; left: 12px;
  z-index: 90;
  padding: 10px 16px;
  background: var(--off-white);
  color: var(--black);
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform 0.2s var(--ease);
}
.skip-link:focus-visible { transform: translateY(0); }

/* ── Atmosphere ─────────────────────────────────────────────────────────── */
/* A single soft light over the first screen only.

   This used to be `position: fixed` across the whole viewport, with a graphite
   ellipse anchored below the fold. On a black page that lifted the floor by
   ~10 levels and, being fixed, stayed glued to the viewport while the content
   scrolled past — which read as a glow travelling with the window rather than
   depth belonging to the page. It is now absolute and one screen tall, so it
   scrolls away with the hero and the rest of the page is flat black. */
.atmosphere {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  height: 100svh;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(110% 80% at 50% -12%, rgba(244, 243, 240, 0.028), transparent 60%),
    radial-gradient(90% 70% at 8% 22%, rgba(141, 148, 155, 0.014), transparent 62%);
}

/* Infrastructure spine — continues the hero rail down the page.
   Positioned in the document rather than fixed, so the browser scrolls it
   natively and it stays locked to the content (see spine.js). */
.spine {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  width: 100%;
  overflow: hidden;
  pointer-events: none;
}

/* Dispatch — the signal a submitted form sends off the page.
   The homepage answers a click with light on the board; the interior pages have
   no board, so the run is drawn on demand into this layer and removed when it
   leaves the frame. A canvas, not an SVG: this is the same signal the netting
   draws, so it is drawn by the same code with the same exponential tail. A
   dashed SVG path gave it a hard-ended bar of flat light, and the signal
   visibly changed character the moment it reached the grid.

   It sits between the wiring and the components, which is the whole point: a
   run passes BEHIND a part, so it reads as going through the board rather than
   across the front of it. The layer is a child of .page-head for exactly this
   reason — see the canvas in contact/index.html.

   The stack inside .page-head, low to high:
     .netting          0   the resting wiring
     .dispatch         1   the signal, over the track it runs on
     .netting--solids  2   the components, which occlude it
     > .container      3   the copy, over everything

   It was z-index 40 on a <body> child, so it painted over the lot. */
.dispatch {
  position: fixed;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* Netting — the board's wiring, continued onto an interior page.
   -----------------------------------------------------------------------------
   Same grammar as the hero field: right angles, 18px fillets, one hairline
   weight, and a resting alpha of about 3% so it is felt before it is read. It
   is not a decoration bolted to the right margin — the runs enter from off the
   right edge and leave through the top, the bottom or each other, exactly as
   the board's do, so nothing begins or ends in open space.

   The triangle is a mask, not a clip. A polygon would cut the runs off along a
   hard edge and read as a crop; a diagonal gradient lets them thin out into the
   page, which is what makes the wedge look like the near corner of something
   larger rather than a shape someone drew. */
.netting {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 0;
  width: 66%;
  height: 100%;
  pointer-events: none;
  /* Two masks, intersected.
     The first is the wedge. The second matters just as much: a canvas clips to
     its own box, and this one's bottom edge is a long way above the bottom of
     the page — so the runs that leave through it were stopping in the middle of
     the page rather than exiting it. Fading the layer out before its own edge
     means they thin away instead of being cut, which is the same thing
     .hero__plate does for the same reason.
     `intersect` is required: layered masks default to `add`, which would union
     the two and undo both. */
  -webkit-mask-image:
    linear-gradient(112deg, transparent 16%, rgba(0, 0, 0, 0.55) 42%, #000 70%),
    linear-gradient(to bottom, #000 76%, transparent 99%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(112deg, transparent 16%, rgba(0, 0, 0, 0.55) 42%, #000 70%),
    linear-gradient(to bottom, #000 76%, transparent 99%);
  mask-composite: intersect;
}
@media (max-width: 900px) {
  /* No room beside the copy, and the runs would sit behind it rather than
     beside it. The page is the type at this width. */
  .netting { display: none; }
}

/* Film grain over the whole page — keeps flat black from banding. */
.grain {
  position: fixed;
  inset: -50%;
  z-index: 1;
  pointer-events: none;
  opacity: 0.048;
  mix-blend-mode: screen;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ── Primitives ─────────────────────────────────────────────────────────── */
.container {
  width: min(var(--container), 100% - (var(--gutter) * 2));
  margin-inline: auto;
}

/* The label voice, canonical across every manif surface: 11px, 600, tracked
   0.08em, uppercase by CSS and sentence case in the source. Set in the sans —
   the mono face is kept for identifiers and machine values, which is the whole
   of its job. Matches .doc-nav a and .prose h3 on manif.bot; the guide is
   packages/brand/TYPOGRAPHY.md §3. */
.mono {
  font-family: var(--font-sans);
  font-size: 0.6875rem;               /* 11px */
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--steel-dim);
}
a { color: inherit; }

/* The boxed .button primitive is gone. There was exactly one on the site — the
   contact form's submit — and it was the only control that did not speak the
   bracket grammar every other call to action uses. It is a .link-cta now, and
   the rules below carry the three declarations a <button> needs that an <a>
   does not. */
button.link-cta {
  background: none;
  border: 0;
  cursor: pointer;
}
button.link-cta:disabled {
  opacity: 0.45;
  cursor: default;
}
/* A disabled control should not answer, so the brackets stay away. */
button.link-cta:disabled::before,
button.link-cta:disabled::after { opacity: 0 !important; }

/* Text-only call to action: no box. It takes the same label voice as every
   other piece of UI text on the page — 11px, 600, tracked 0.08em, uppercase
   by CSS — so a CTA, a field label and the copyright line are one treatment
   rather than three. TYPOGRAPHY.md §3. The rank between them is carried by
   colour alone: a CTA sits at --steel and lifts to --off-white when it is
   live, static notes stay at --steel-dim. */
.link-cta {
  position: relative;
  display: inline-block;
  padding: 12px 2px;
  font-family: var(--font-sans);
  /* The label voice a third up: 11px * 1.3. A CTA is the one piece of UI text
     on the page that has to be found rather than read, and at 11px it was
     losing to the copyright line beside it. Tracking is in em, so it rides
     the size up on its own. */
  font-size: 0.894rem;                /* 14.3px */
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--steel);
  /* Same motion as a word in the headline: a small, slow swell. */
  transform: translateZ(0);
  transition: transform 0.42s var(--ease), color 0.42s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .link-cta:hover { color: var(--off-white); }
}
.link-cta:focus-visible { color: var(--off-white); }
@media (hover: hover) and (pointer: fine) {
  .link-cta:hover { transform: scale(1.024); }
}
.link-cta:focus-visible { transform: scale(1.024); }
@media (prefers-reduced-motion: reduce) {
  .link-cta:hover, .link-cta:focus-visible { transform: none; }
}
/* Brackets, not an underline: on hover the label is framed the way the
   wordmark is. They sit outside the text box and are laid out at rest, so
   nothing reflows when they appear — only their opacity and position move. */
.link-cta::before,
.link-cta::after {
  position: absolute;
  top: 50%;
  color: var(--terracotta);
  /* Heavier than the label, like the brackets in the wordmark. */
  font-weight: 700;
  opacity: 0;
  transition: opacity 0.26s var(--ease), transform 0.42s var(--ease);
  pointer-events: none;
}
/* They arrive by closing in on the label — out and faint at rest, tight to the
   text when lit, so it ends up set as [Get in touch] rather than decorated. */
.link-cta::before {
  content: '[';
  right: 100%;
  transform: translate(-7px, -54%);
}
.link-cta::after {
  content: ']';
  left: 100%;
  transform: translate(7px, -54%);
}
.link-cta:hover::before,
.link-cta:focus-visible::before { opacity: 1; transform: translate(4px, -54%); }
.link-cta:hover::after,
.link-cta:focus-visible::after { opacity: 1; transform: translate(-4px, -54%); }

/* A section heading, in the manif.bot voice. Hierarchy on the interior pages
   comes from colour and case, not size. TYPOGRAPHY.md §1 and §3.

   It takes the same 16px the page title does, which puts the two accent ranks
   on one size — as they are on /problem, where both are 11px. The H1 is still
   distinguished, but by position and by being the only heading with a lede
   under it, not by scale. The third rank (.principle__title, subtle grey) sits
   a rung below at 13px, so the step down is still there where it works. */
.section-title {
  font-size: 1rem;                    /* 16px */
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--terracotta);
}
/* ── Header ─────────────────────────────────────────────────────────────── */
.site-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 60;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: transparent;
}

/* Rather than a bar with an edge, the top of the page dissolves into black:
   a soft scrim sits above the content and below the header, so anything
   scrolling upward is absorbed instead of sliding under a panel. It fades in
   once the page has moved, keeping the hero's light intact at rest. */
.header-scrim {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: calc(var(--header-h) + 78px);
  z-index: 50;
  pointer-events: none;
  /* Always on. The band the header sits in is solid down to the foot of the
     header, then falls away as a gradient — so the header has a floor to sit
     on and no edge to cut across the artwork. It deepens a little further on
     scroll, when there is content moving underneath it.

     The fill is #0E0F10, not the page's own #08090a, and the six levels are
     the point. Two layers lift the rest of the page and neither reaches under
     here: the atmosphere, which is absolute at z-index 0 and puts about four
     levels of warm light across the top of the hero, and the grain, fixed at
     z-index 1 and worth two or three more. The scrim sits at 50, above both.
     Filling it with the raw page colour therefore paints a band that is
     darker than the page it is supposed to disappear into — which read as a
     black bar across the top. This is the lit value, so it matches what is
     actually on screen either side of it rather than what the token says.

     The ramp starts falling at 48% rather than 58% for the same reason: a
     shorter solid section and a longer fade give the band no edge to be seen
     by. */
  opacity: 1;
  transition: height 0.45s var(--ease), opacity 0.45s var(--ease);
  background: linear-gradient(
    to bottom,
    rgb(14, 15, 16) 0%,
    rgb(14, 15, 16) 48%,
    rgba(14, 15, 16, 0.94) 62%,
    rgba(14, 15, 16, 0.74) 74%,
    rgba(14, 15, 16, 0.44) 85%,
    rgba(14, 15, 16, 0.16) 94%,
    rgba(14, 15, 16, 0) 100%
  );
}
.header-scrim.is-visible { height: calc(var(--header-h) + 132px); }
.site-header__inner {
  width: min(var(--container), 100% - (var(--gutter) * 2));
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.wordmark { display: inline-block; }
.wordmark img { width: clamp(84px, 8vw, 108px); height: auto; }

/* ── Hero ───────────────────────────────────────────────────────────────── */
.hero {
  position: relative;
  /* The board's layers are sized from the hero box. If that box changes after
     they are sized — fonts landing, the opening state being removed, a
     scrollbar appearing — a canvas can briefly extend past the hero and paint
     a run into the section below it. Clip it. */
  overflow: hidden;
  z-index: 2;
  min-height: 100svh;
  padding-top: calc(var(--header-h) + clamp(56px, 12vh, 132px));
  padding-bottom: clamp(96px, 14vh, 148px);
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* Routing diagram behind the hero content (see hero-field.js). Hidden on
   narrow screens, where the headline fills the viewport. */
.hero__plate {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0.026;
  mix-blend-mode: screen;
  pointer-events: none;
  /* The plate lifts the hero a fraction above pure black, so it has to fade
     out before the hero ends — otherwise its bottom edge reads as a seam
     across the page. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 62%, transparent 96%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 62%, transparent 96%);
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.62' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='260' height='260' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
}

/* The board's falloff toward the headline, and the ONLY place it happens.
   /contact learned this the hard way and says so in its own source: the resting
   alpha a segment is painted at and the value the lit solve subtracts from it
   have to be the same number, or a run comes back to rest unevenly along its
   own length. The hero used to compute a shade(x, y) per sample AND carry this
   mask, so the components were attenuated twice over — 0.06 mask x 0.26 core =
   1.6% at the centre, which is why they ghosted through the type instead of
   sitting behind it.

   Now the maths is flat and the falloff is here, once, over both canvases. Both
   must carry the identical gradient: the wires and the solids they punch out of
   are one drawing, and a mask on one but not the other pulls them apart. */
.hero__objects,
.hero__field {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* Zero at the centre, not a low value. A mask alpha of 0 removes the board
     outright, so the type sits on the page's own black with nothing behind it
     — at 0.16 the runs were still faintly legible through the lettering, which
     is the one place they must not be. The dead zone reaches 50% of the
     gradient before it starts to open, so the headline and the lede are both
     inside it at every viewport, and it does not reach full strength until the
     rim. */
  -webkit-mask-image: radial-gradient(ellipse 68% 57.5% at 50% 50%,
    rgba(0,0,0,0) 0%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.55) 78%, #000 100%);
  mask-image: radial-gradient(ellipse 68% 57.5% at 50% 50%,
    rgba(0,0,0,0) 0%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.55) 78%, #000 100%);
}
@media (max-width: 760px) { .hero__objects, .hero__plate { display: none; } }
.hero > .container { position: relative; z-index: 1; }
/* All that survived of a thirty-line block ported from the prototype. It
   re-declared .hero__plate and .hero__objects in full inside this query —
   including a second copy of the plate's noise data-URI — and nested another
   @media inside it repeating a `display: none` already set ten lines above.
   The wire canvas going away on a narrow screen was the only thing it did. */
@media (max-width: 760px) { .hero__field { display: none; } }

/* The headline sits in a centred, width-limited column. */
.hero__column {
  width: 100%;
  max-width: 1180px;
  margin-inline: auto;
  text-align: center;
}
/* Sized so the first line holds on one line from tablet upward: two lines
   total, the second in steel grey. Below 760px it wraps naturally. */
.hero__title {
  font-size: clamp(2.1rem, 4.75vw, 4.3rem);
  font-weight: 500;
  line-height: 1.06;
  letter-spacing: -0.035em;
}
.hero__title-line,
.hero__title-muted { display: block; }
/* Second line stays in the same ink; the hierarchy comes from weight and size,
   not from a colour change. */
.hero__title-muted {
  color: var(--off-white);
  font-weight: 300;
  font-size: 0.87em;
}

/* The only accent in the hero: a short terracotta rule between the headline
   and its supporting line. */
.hero__rule {
  display: block;
  width: 34px;
  height: 1px;
  margin: clamp(26px, 3vw, 38px) auto 0;
  background: var(--terracotta);
}
.hero__bracket { display: none; }   /* shown only by the ?type=c comparison */

/* Key words come forward very slightly on hover. `inline-block` + `transform`
   keeps this off the layout path, so nothing reflows and no neighbouring word
   moves. Precise, not springy: small scale, small brightness step, long ease.
   Fine pointers only; reduced motion drops the movement and keeps the tone. */
/* Clicking one of these answers on the board. The pointer is the only thing
   telling anyone that, so it is not decoration: hovering used to fire the
   spark by itself, which made the affordance unnecessary. Now that the board
   waits to be asked, the word has to look askable. */
.hero__word {
  display: inline-block;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transform: translateZ(0);
  transition: transform 0.42s var(--ease), color 0.42s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .hero__word:hover {
    transform: scale(1.024);
    color: #ffffff;
  }
}
@media (prefers-reduced-motion: reduce) {
  .hero__word:hover { transform: none; }
}

@media (min-width: 761px) {
  .hero__title-line,
  .hero__title-muted { white-space: nowrap; }
}
/* Measure is wide enough to hold one line on desktop rather than orphaning
   the final clause. */
.hero__lede {
  margin-top: clamp(18px, 2vw, 24px);
  margin-inline: auto;
  max-width: 74ch;
  font-size: clamp(1rem, 1.25vw, 1.2rem);
  line-height: 1.6;
  color: var(--muted);
  letter-spacing: -0.005em;
  text-wrap: pretty;
}
@media (max-width: 760px) {
  .hero { min-height: 88svh; padding-bottom: clamp(80px, 16vh, 120px); }
}

/* ── Sections ───────────────────────────────────────────────────────────── */
/* Sections carry no rules or frames — they are separated by space alone, so
   the page reads as one continuous surface. */
.section {
  position: relative;
  z-index: 2;
  padding-block: var(--section-y);
}

/* Backers — centred, and intentionally not framed as its own section. */
.section--backers {
  padding-top: clamp(72px, 11vw, 168px);
  padding-bottom: clamp(104px, 13vw, 208px);
  text-align: center;
}
.section--backers .section-title { margin-inline: auto; }

.backers__statement {
  position: relative;
  display: inline-block;
  margin-inline: auto;
  /* Wide enough to hold the whole sentence on one line on desktop; it still
     wraps naturally once the container is narrower than the line. */
  max-width: 64ch;
  font-size: clamp(1.25rem, 2.3vw, 1.9rem);
  font-weight: 300;
  line-height: 1.3;
  letter-spacing: -0.018em;
  color: var(--off-white);
}

/* A terracotta tick either side of the line, pointing outward. On hover they
   step further out — the sentence is being opened, not underlined. They are
   the accent in this section: there is no heading above the line, which is
   what keeps this inside the one-mark-per-view rule in TYPOGRAPHY.md §5. */
.backers__statement::before,
.backers__statement::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 0;
  height: 0;
  border-block: 5px solid transparent;
  transition: transform 0.42s var(--ease);
}
/* The scale is carried on the transform rather than taken off the border
   widths: a border resolves to whole pixels, so 5px/8px scaled by hand would
   land on 4px/7px — 20% off one axis and 12% off the other, and the tick would
   change shape rather than just size. Every transform below must keep the
   scale. */
.backers__statement::before {
  right: 100%;
  border-right: 8px solid var(--terracotta);
  transform: translate(-22px, -50%) scale(0.9);
}
.backers__statement::after {
  left: 100%;
  border-left: 8px solid var(--terracotta);
  transform: translate(22px, -50%) scale(0.9);
}
@media (hover: hover) and (pointer: fine) {
  .backers__statement:hover::before { transform: translate(-34px, -50%) scale(0.9); }
  .backers__statement:hover::after  { transform: translate(34px, -50%) scale(0.9); }
}
@media (prefers-reduced-motion: reduce) {
  .backers__statement::before, .backers__statement::after { transition: none; }
}
@media (max-width: 620px) {
  /* No room for them beside the line once it wraps to the full width. */
  .backers__statement::before, .backers__statement::after { display: none; }
}

/* Team.
   The heading used to be centred, which read fine when it was 1.75rem and the
   only thing on its line. At 11px it is identical to every other heading on
   the page, so centring just one of them reads as a mistake. */

/* Same 12px as .principles, and open at the head for the same reason. */
.register { margin-top: 12px; }

.register__link {
  position: relative;                 /* for the lit divider below */
  display: grid;
  grid-template-columns: minmax(0, 26ch) minmax(0, 1fr) auto;
  align-items: baseline;
  gap: clamp(16px, 3vw, 48px);
  padding: clamp(15px, 1.5vw, 21px) 2px;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.28s var(--ease);
}
/* The divider under a picked row, lit the way a run is lit.

   The resting rule is rgba(244,243,240,0.09), within a rounding of the board's
   own IDLE_TRACK of 0.085 — arrived at separately and in agreement, so the line
   under a name is already the weight of track a signal travels on. Lighting it
   to 0.60 is lighting it to MANIF_SIGNAL.LIVE_MAX, which is what the head of a
   signal reaches.

   Static, and the whole bar at once. A travelling head would be a fourth thing
   moving on a page that already carries the netting, the dispatch run and the
   scrim, and it would turn a hover into an animation to sit through. The rule
   simply comes on.

   An overlay rather than a transition on border-bottom-color, so the bloom has
   something to hang on: a 1px border cannot carry a box-shadow without the
   shadow reading as a second line under it. */
.register__link::after {
  content: '';
  position: absolute;
  /* The rule is the bar and stays exactly its width. Only the glow above
     runs past the ends — a light source does not get longer than the thing
     it is. */
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  background: rgba(244, 243, 240, 0.72);
  box-shadow: 0 0 3px rgba(244, 243, 240, 0.55);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s var(--ease);
}
.register__row.is-active .register__link::after { opacity: 1; }

/* The bleed around the lit rule, and it is a GRADIENT rather than a shadow.

   box-shadow was the wrong tool and it took two attempts to see why. A shadow
   is cast by the box it belongs to, and this box is one pixel tall — so
   whatever alpha it has gets spread across the blur radius and arrives at a
   few per cent. Stacking three of them made a slightly wider few per cent.
   What it looked like was a white line slightly out of focus, which is exactly
   what it was: blur, not light.

   A band with a gradient in it has no such constraint. The alpha at every
   distance from the rule is stated outright — 0.22 against the page's own
   0.047 at the centre, which is roughly five times the background rather than
   the fraction above it — and it falls to nothing over 20px either side.

   Two layers, because light has a bright middle and a soft surround, and one
   element cannot be both: ::after is the rule, ::before is what it throws. */
.register__link::before {
  content: '';
  position: absolute;
  /* Past both ends, and faded there by the mask below.

     The band used to stop at exactly the x the rule stops at, which gave the
     glow two hard vertical edges — a lit rule inside a rectangle of haze,
     with square corners at the tips. Light does not end on a straight edge;
     it carries past the thing emitting it and falls off. So the band runs
     22px beyond each end and the mask takes it to nothing over that distance,
     which is what makes the ends read as lit rather than cut. */
  left: -22px;
  right: -22px;
  -webkit-mask-image: linear-gradient(to right,
    transparent 0, #000 22px, #000 calc(100% - 22px), transparent 100%);
  mask-image: linear-gradient(to right,
    transparent 0, #000 22px, #000 calc(100% - 22px), transparent 100%);
  bottom: -21px;
  height: 42px;
  background: linear-gradient(to bottom,
    rgba(244, 243, 240, 0)    0%,
    rgba(244, 243, 240, 0.04) 28%,
    rgba(244, 243, 240, 0.12) 42%,
    rgba(244, 243, 240, 0.22) 50%,
    rgba(244, 243, 240, 0.12) 58%,
    rgba(244, 243, 240, 0.04) 72%,
    rgba(244, 243, 240, 0)    100%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s var(--ease);
}
.register__row.is-active .register__link::before { opacity: 1; }

/* A name is not a heading, so it stays in sentence case and at prose size —
   uppercasing people is a different house style. Dropped from ~18px so it
   does not become the one thing on the page above the body. */
.register__name {
  position: relative;
  display: inline-block;
  font-size: 1.0625rem;               /* 17px */
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.012em;
  /* Steel, always. Inverted against the sections above, where the label is the
     grey and the prose is the ink: here the role carries the ink and the name
     sits behind it. Colour only — size, weight and casing are unchanged, so
     the rank still reads off the type.

     No transition, because nothing changes it any more. */
  color: var(--steel);
}
/* The whole row is the target, so the name does not carry a mark of its own —
   it simply comes forward with the row it is in. The brackets that used to
   close around it said "this is a link" a second time, in the same terracotta
   the "read more" beside it now uses, which put two accents in one line. */
/* No lift on the picked row. The name is steel whether the row is picked or
   not — the role is the ink here, and having the name jump to off-white on
   hover put two full-strength values on one line and undid the inversion the
   moment anyone pointed at it.

   The row still answers: the lit divider under it, the mark appearing at the
   end, and the siblings dropping back. Colour is not carrying any of that. */

/* The job title, in the label voice: 13px, 600, tracked, uppercase by CSS and
   sentence case in the roster. It is a label rather than prose — a role, not a
   sentence about one — and setting it as one makes the register read as a
   table of records instead of a list of people with notes after their names.

   Identical to the "read more" beside it in everything but colour, which is
   the §3 model working as intended: one treatment across the UI layer, and
   rank carried by the grey ramp. Steel here, terracotta there. */
.register__role {
  font-size: 0.8125rem;               /* 13px */
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--off-white);
}

.register__id {
  justify-self: end;
  font-family: var(--font-mono);
  font-size: 0.75rem;                 /* 12px */
  letter-spacing: 0.14em;
  color: var(--steel-dim);
  transition: color 0.28s var(--ease);
}
.register__link:hover .register__id { color: var(--terracotta); }

@media (max-width: 720px) {
  /* Three columns will not hold; the row becomes a stacked entry instead. */
  .register__link {
    grid-template-columns: 1fr;
    gap: 5px;
    padding: 16px 2px;
  }
  .register__id { justify-self: start; padding-top: 3px; }
  .register__meta { flex-wrap: wrap; }
}

/* ── Picking a name ────────────────────────────────────────────────────────
   Pointing at a name lifts that row out of the register and takes the rest of
   the list back — a card used to open over the whole page instead, which
   covered the thing you were reading to tell you about it.

   The motion is the page's: 0.42s on the standard ease at scale 1.028, the
   same swell a headline word and a call to action take. The others go to 28%
   and 2.5px of blur rather than out, so the list is still visibly a list and
   the eye has somewhere to go next.

   State is set by class from the script, not by :hover in the sheet, because
   keyboard focus has to produce exactly the same thing and the trigger is the
   name while the effect is the whole row. */
.register__row {
  position: relative;
  transform: translateZ(0);
  transition: transform 0.42s var(--ease),
              opacity 0.42s var(--ease),
              filter 0.42s var(--ease);
}
.register.is-picking .register__row {
  opacity: 0.28;
  filter: blur(2.5px);
}
.register.is-picking .register__row.is-active {
  opacity: 1;
  filter: none;
  transform: scale(1.028);
  z-index: 2;
}

/* Beside the title it belongs to, not stranded at the far end of the line
   where the eye has already stopped reading. Laid out at rest and only faded,
   so the row does not reflow when it appears — the same reason the CTA's
   brackets are always in the box. Terracotta, and now the only accent in the
   row: the brackets that used to sit on the name are gone. */
.register__meta {
  display: flex;
  align-items: baseline;
  gap: clamp(12px, 1.6vw, 22px);
  min-width: 0;
}
/* The only link in the row. Everything else is a record. */
.register__more {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
  color: var(--terracotta);
  opacity: 0;
  transition: opacity 0.26s var(--ease), color 0.26s var(--ease);
}
.register__more svg { display: block; }

/* On the row: the mark appears. */
.register__row.is-active .register__more { opacity: 1; }

/* On the mark itself: it goes white and the arrow arrives. The arrow is laid
   out at rest and only faded, so nothing shifts when it does — the same reason
   the CTA keeps its brackets in the box. */
.register__out { opacity: 0; transition: opacity 0.22s var(--ease); }
.register__more:hover,
.register__more:focus-visible { color: #ffffff; }
.register__more:hover .register__out,
.register__more:focus-visible .register__out { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  /* The blur and the fade still say which row is picked; the movement does not
     need to. */
  .register.is-picking .register__row.is-active { transform: none; }
}

.team-fallback { margin-top: 32px; display: grid; gap: 8px; color: var(--steel); }

/* ── Footer ─────────────────────────────────────────────────────────────── */
.site-footer {
  position: relative;
  z-index: 2;
  padding-top: clamp(72px, 9vw, 128px);
  padding-bottom: clamp(56px, 7vw, 96px);
}
/* One row, two ends: links left, copyright right. The mark used to hold the
   left corner and the links sat on the true centre between two equal columns;
   with the mark gone the links take the corner themselves, which keeps the
   footer reading as a base rather than as something floating in the middle.
   No rule on top — the spine's traverse is already the boundary. */
.site-footer__inner {
  /* The container's measure, exactly — the same one the header wordmark and
     every line of copy on the page sit on. The footer used to run on its own
     inset, which put the links on a margin nothing else shared; at a glance
     that reads as a misalignment rather than as a decision, because it is one.
     The links now start on the same vertical as the mark above them. */
  width: min(var(--container), 100% - (var(--gutter) * 2));
  max-width: none;
  margin-inline: auto;
  display: grid;
  grid-template-columns: auto auto;
  justify-content: space-between;
  align-items: center;
  gap: clamp(20px, 4vw, 48px);
}

.site-footer__nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: clamp(24px, 3.4vw, 48px);
  /* No inset of its own. The links start on the container's left edge so they
     line up with the wordmark and the copy; the homepage spine's left rail
     runs at 3.2vw, which lands within a couple of pixels of that edge on a
     wide screen — a 1px hairline at 5% behind uppercase type, which is not
     worth breaking the alignment for. */
  margin-left: 0;
}
/* No size step in the footer: the links carry the same 11px label voice they
   do in the header, which is what puts them and the copyright on one line of
   type rather than two. */

.site-footer__note {
  justify-self: end;
  text-align: right;
  color: var(--steel-dim);
}

@media (max-width: 780px) {
  /* Two ends stop fitting side by side, so each takes its own row. They keep
     their alignments — both to the left edge, which is where the links
     already are, rather than one corner each with nothing between them. */
  .site-footer__inner {
    grid-template-columns: 1fr;
    row-gap: clamp(24px, 5vw, 40px);
    justify-items: start;
  }
  .site-footer__nav { margin-left: 0; gap: 24px; }
  .site-footer__note { justify-self: start; text-align: left; }
}

/* ── Interior pages ─────────────────────────────────────────────────────── */
/* No hero and no spine: the page opens on type, under the same header. */
/* The header is fixed and 74px tall, so this padding is the whole of the gap
   between it and the first line of the page. 64px put the title just under the
   header's foot and the page opened cramped; 104px lets it clear the header
   band and the scrim's falloff before anything starts. */
.page-head {
  position: relative;
  z-index: 2;
  padding-top: calc(var(--header-h) + clamp(64px, 7vw, 104px));
  /* Nothing below the title: the 12px gap to its content is set by the rule
     under `.page-head +`, so the H1 sits on its list exactly the way an H2
     sits on its own. Splitting the gap across two paddings is what left the
     page title further off its content than every other heading on the page. */
  padding-bottom: 0;
}
/* The interior pages run the §3 scale one rung up from manif.bot's — 13/16/17
   where /problem is 11/14.3/15 — because they are read at arm's length on a
   marketing surface rather than scanned as documentation. The ratios and the
   ordering are identical, which is what keeps it one system at two sizes.

   16px is the ceiling here for the same reason 14.3px was there: prose is
   17px, so a heading still sits under the body and §1 holds — body copy is the
   largest text on the page. A heading at or above prose is a display tier
   under another name.

   The head still carries a lede, and still needs one. Size alone does not make
   a title read as a title at this scale; the pairing does most of the work,
   which is why every .page-head on this site has both. The lede is prose-sized
   and therefore larger than the title above it — the same relationship
   /problem has between its .sub and its h1, and the reason the 13px lede under
   a 14.3px title read as an afterthought.

   No max-width and no balancing: a title that wraps at caps has too many words
   in it. */
.page-head__title {
  font-size: 1rem;                    /* 16px */
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--terracotta);
}
/* The lede is the page summary, one step below prose and in the muted ink.
   12px off the title rather than 32: at 11px the title belongs to the sentence
   under it, and the old gap left the two reading as separate blocks. */
.page-head__lede {
  margin-top: 12px;
  max-width: 66ch;
  font-size: 1.0625rem;               /* 17px — a sentence, so prose */
  line-height: 1.6;
  color: var(--muted);
}

.section--principles { padding-block: clamp(56px, 7vw, 96px); }
.section--principles + .section--principles { padding-top: clamp(48px, 6vw, 84px); }
/* The head and the first list belong together — no section gap between them. */
/* Zero, so the page title sits on its list at exactly the 12px a section
   heading does. The gap here is `.principles`' own margin and nothing else —
   this padding used to add up to 40px on top of that, which is why the H1 read
   as further off its content than every other heading on the page. */
.page-head + .section--principles { padding-top: 0; }

/* Same register grammar as the founding team: a hairline per entry, the label
   on the left and the substance on the right. */
/* 12px off the heading, not 44. A heading belongs to what follows it, and at
   11px the old gap left the two reading as unrelated blocks. TYPOGRAPHY.md §6.

   No rule at the head either: the heading already marks where the list starts,
   and a hairline directly under it was drawing that boundary twice. What is
   left are the rules between entries, which are the only ones dividing
   anything. */
.principles { margin-top: 12px; }
/* 24ch, and measured rather than reasoned.

   It was 19ch, and the note here said that was sized for "an 11px caps label".
   That was true when it was written and stopped being true when manif.ai moved
   to its own rung of the scale — .principle__title is 13px, not 11, and the
   column never followed. 19ch x 13/11 is 22.5ch, which is roughly where the
   arithmetic lands and close to what the browser reports.

   Measured on the page: the widest label the site has, "Quality Is Our
   Identity", renders at 189px against a 19ch column of 154px, so it wrapped to
   two lines while the other six sat on one — a single ragged item in a list
   that is otherwise a clean ladder. 24ch is 195px: enough for the longest
   label with a little over, and not so much that the gutter opens up again.
   Public Sans is self-hosted, so those metrics are the same everywhere. */
.principle {
  display: grid;
  grid-template-columns: minmax(0, 24ch) minmax(0, 1fr);
  gap: clamp(10px, 3vw, 48px);
  /* Centred against the body, not hung off its first line. The label is 13px
     on 1.2 and the prose is 17px on 1.65, so top-aligning the two cells left
     the label sitting inside the first line's leading rather than level with
     anything — close enough to alignment to read as a mistake. Centring gives
     it a relationship to the whole entry, which is what it labels. */
  align-items: center;
  padding: clamp(20px, 2.2vw, 30px) 2px;
  border-bottom: 1px solid var(--line);
}
/* The entry label is the third heading rank, so it takes the same 11px caps in
   the subtle grey rather than the accent — the section heading above it is
   already the accent, and if everything is terracotta the accent has stopped
   ranking anything. TYPOGRAPHY.md §5. */
.principle__title {
  font-size: 0.8125rem;               /* 13px */
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--steel-dim);
}
/* Prose: the largest text on the page, which is the whole idea.
   76ch, not the canon 66. `ch` is the width of a zero, which in Public Sans is
   wider than the average lowercase letter, so 66ch renders around 72 characters
   — and the longest entry here is 165, which lands it on three lines with four
   words on the last. 76ch takes the same sentence to two. It is at the top of
   the comfortable measure and should not go further; the fix for a longer
   entry is a shorter entry. Recorded in TYPOGRAPHY.md §8. */
.principle__body {
  font-size: 1.0625rem;               /* 17px */
  line-height: 1.65;
  color: var(--off-white);
  max-width: 76ch;
}
/* The principles close on space, not on a rule: every entry carries a hairline
   under it, so the last one would draw a line across the foot of the section,
   and a section ending in a rule reads as cut off rather than finished. The
   rules between entries separate things; the one after the last separates
   nothing.

   The register keeps its last rule, and that is a deliberate exception rather
   than an oversight. Every row there is hoverable and the rule under it is
   what lights — so dropping it left the final name as the one entry in the
   list that could not answer, which is a worse inconsistency than a line at
   the foot of a list. The register is a table of people, not a closing
   statement; it can end on a rule. */
.principle:last-child { border-bottom: 0; }

@media (max-width: 720px) {
  .principle { grid-template-columns: 1fr; gap: 8px; }
}

/* ── Contact page ───────────────────────────────────────────────────────── */
/* The page holds one short block, so it is set in the screen rather than at the
   top of it — but above the true centre, not on it. Optical centre sits higher
   than geometric centre, and a block pinned to the middle of a tall viewport
   reads as having drifted there. The oversized padding-bottom is what does it:
   the content centres in what is left after it, which lands it below the middle
   without sitting on the floor of the screen. */
/* Anchored to the top, not centred.
   Centring reads better on a page whose content never changes size — this one's
   does: the form is replaced by a shorter success block, and with
   `justify-content: center` that made the whole column re-centre, so pressing
   the button walked the title and the sentence above it down the screen. The
   position is set by padding instead, which does not move when the content
   under it changes. */
.section--contact {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding-top: calc(var(--header-h) + clamp(140px, 30vh, 340px));
  padding-bottom: clamp(56px, 8vh, 96px);
}

/* One column: the field and the button sit under the sentence that motivates
   them. The measure is on the form, not on the column. */
.contact {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(32px, 5vw, 48px);
  align-items: start;
}
/* Above the netting. */
.section--contact > .container { position: relative; z-index: 3; }

/* The mail link stands alone in its column, where the address field used to
   sit. No wrapper rules survive it: .interest carried a max-width for a text
   input and .interest__foot was a flex row for a button beside a note, and
   neither has anything left to hold. */
.interest__mail {
  margin-top: 0;

  /* 16.9px, which is the size the guide already had waiting for it.

     TYPOGRAPHY.md §3: manif.ai's pages run manif.bot's scale one rung up —
     labels 13px against 11, prose 17px against 15 — and a CTA takes the same
     1.3 step off the label rank that an accent heading does, because both are
     things that have to be FOUND rather than read. 13 x 1.3 = 16.9px. The
     shared .link-cta is 14.3px, which is 11 x 1.3: manif.bot's number, correct
     on manif.bot and one rung low here.

     It also holds §1's ceiling, and only just. Prose is 17px, so at 16.9 the
     control still sits under body copy and the claim that body copy is the
     largest text on the page survives. Anything past 17px would be a display
     tier under another name, and there is exactly one of those — the hero.

     Only this control moves. The nav and footer CTAs are labels among labels
     and stay where they are; this one is the page's single action. */
  font-size: 1.056rem;                /* 16.9px */

  /* An address, not a label — §2 lists email among the identifiers that are
     never transformed, and .link-cta uppercases, which would ship
     CONNECT@MANIF.AI. Tracking comes down with it: 0.08em is set to open out
     uppercase labels, and on mixed case at this size it pulls the address
     apart until it stops reading as one string. */
  text-transform: none;
  letter-spacing: 0.01em;
  -webkit-user-select: text;
  user-select: text;
}

[hidden] { display: none !important; }

/* ── Cursor ─────────────────────────────────────────────────────────────── */
/* A plain circle in place of the arrow. Native cursor images rather than a
   JavaScript-driven element, so there is no lag and nothing extra moving on
   the page. 24×24 SVG with the hotspot at its centre; precise pointers only —
   touch devices fall through to the default.
   Kept last in the file so it wins over the component-level `cursor` rules. */
@media (hover: hover) and (pointer: fine) {
  body {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Ccircle cx='12' cy='12' r='7' fill='rgba(198,90,46,0.16)' stroke='rgb(198,90,46)' stroke-width='1.3'/%3E%3C/svg%3E") 12 12, auto;
  }
  a, button, .button, summary, label, [role='button'] {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Ccircle cx='12' cy='12' r='8.8' fill='rgb(198,90,46)' stroke='rgb(198,90,46)' stroke-width='1.4'/%3E%3C/svg%3E") 12 12, pointer;
  }
  input, textarea { cursor: text; }
}

/* ── Print ──────────────────────────────────────────────────────────────── */
@media print {
  .atmosphere, .grain, .site-header { display: none !important; }
  body { background: #fff; color: #000; }
}

/* ── Team card ─────────────────────────────────────────────────────────────
   One person, at /team/<id>. Same shell and the same register grammar as the
   list it came from — a hairline per entry, the label left and the substance
   right — so arriving here reads as opening a row rather than as landing on a
   different kind of page. */

/* The employee id, above the name. Mono because it is an identifier, and the
   one accent on the page apart from the section headings. TYPOGRAPHY.md §3. */

/* A person is not a label, so the page title keeps its casing here and drops
   the caps tracking with it — §2 exempts names and identifiers from the
   uppercase rule. It also takes the display tier, which §4 otherwise reserves
   for the landing hero: the subject of a card should be the largest thing on
   it. Recorded in §8. */

/* The same treatment the role takes in the register. */

/* Where else this person is. A row of text CTAs, not a list of URLs. */

/* Back to the register. Bottom of the page, quiet, and the only way out that
   is not the header. */

/* The solids share the wiring's box and mask; only the compositing differs.
   Above the wiring so a component covers the run it sits on, still under the
   copy. See the netting-solids script. */
.netting--solids { z-index: 2; }

/* ── Arrival ───────────────────────────────────────────────────────────────
   The screen on a first visit. Black, the [m] mark, and a bar built on the
   same signal model as the board — see the script at the foot of index.html.

   Only ever visible under <html class="arriving">, which the gate in the head
   sets before the first paint and only for a reader with no record of having
   been here. Everyone else never renders it at all: display: none, no canvas,
   no work.

   THE FAILSAFE MATTERS MORE THAN THE ANIMATION. A curtain over the page is the
   one component whose failure mode is the whole site being unusable, so it is
   built to leave on its own. `arrival-failsafe` fades it out at 6s and sets
   pointer-events: none, in CSS, with no script involved. If the loader throws,
   if a font never resolves, if window.load never fires behind a hung request —
   the screen still goes. The script's own dismissal is the fast path, not the
   only path. */
.arrival { display: none; }

.arriving .arrival {
  display: flex;
  position: fixed;
  inset: 0;
  z-index: 100;               /* over the skip-link at 90 — nothing outranks it */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 34px;
  background: var(--black);
  animation: arrival-failsafe 0.4s var(--ease) 6s forwards;
}

/* Dismissed. The class is set by the script; the transition is what the reader
   sees, and removing the node waits for it to finish. */
.arriving .arrival.is-done {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.34s var(--ease);
}

@keyframes arrival-failsafe {
  to { opacity: 0; pointer-events: none; visibility: hidden; }
}

/* The mark, at the wordmark's own weight and letterfit. Public Sans may not
   have landed yet — this is the first paint of the whole visit — so it is
   drawn in the stack's fallback and swaps when the face arrives. On a black
   field at this size the swap is a reflow of three glyphs, which is why the
   motion below runs long enough to cover it. */
.arriving .arrival__mark {
  font-family: var(--font-sans);
  font-size: clamp(2.6rem, 7vw, 4rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--off-white);
  animation: arrival-mark 0.75s var(--ease) both;
}

/* The m arrives on its own and the brackets close on it — they frame it rather
   than appear beside it.

   This was one letter-spacing animation across the whole mark, which is a
   blunter instrument than it looks: letter-spacing adds its track AFTER every
   glyph, so the trailing bracket carried a gap the leading one did not and the
   group drifted left as it settled. Moving the two brackets against each other
   keeps the m fixed on the centre line, which is the thing the eye is actually
   holding on to.

   0.34em each, over 1.3s, starting once the m is already there.

   All three numbers were smaller and it could not be seen. The travel was the
   least of it: the easing was the site's --ease, a cubic-bezier(0.22, 0.61,
   0.36, 1) that is nearly finished in its first fifth. Whatever distance the
   brackets covered, they covered it in about 150ms and then crept. A motion
   that is over before the eye has found it did not need to be bigger so much
   as it needed to be spread out.

   So: a symmetric ease that starts slow and lands slow, and enough delay for
   the m to arrive first — the brackets then close on something already there,
   which is what makes it read as framing rather than as three glyphs fading up
   together. In em, so it holds proportion through the clamp() above. */
.arriving .arrival__bracket {
  display: inline-block;
  color: var(--terracotta);
  animation: arrival-bracket-left 1.3s cubic-bezier(0.4, 0, 0.2, 1) 0.1s both;
}
.arriving .arrival__bracket:last-child {
  animation-name: arrival-bracket-right;
}

@keyframes arrival-mark {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes arrival-bracket-left {
  from { transform: translateX(-0.34em); opacity: 0.2; }
  to   { transform: none;                opacity: 1; }
}
@keyframes arrival-bracket-right {
  from { transform: translateX(0.34em);  opacity: 0.2; }
  to   { transform: none;                opacity: 1; }
}

.arriving .arrival__bar {
  width: 220px;
  height: 3px;
  animation: arrival-bar 0.5s var(--ease) 0.28s both;
}
@keyframes arrival-bar {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Reduced motion: the screen still happens — it is information, not
   decoration — but nothing travels. The mark is simply there and the bar
   crossfades in place. */
@media (prefers-reduced-motion: reduce) {
  .arriving .arrival__mark,
  .arriving .arrival__bracket,
  .arriving .arrival__bar { animation: none; }
}

@media (max-width: 760px) {
  .arriving .arrival__bar { width: 168px; }
}

