/* ==========================================================================
   Reset, typography, layout spine and motion utilities.
   ========================================================================== */

/* Cross-document view transitions (Chrome, Safari 18+). Browsers without it
   fall back to the JS veil in motion.js. */
@view-transition { navigation: auto; }

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

/* Everything on this page is sized in rem — the six type steps, the gutters,
   the rhythm between blocks, the page's own maximum width. All of them stop
   growing at their ceilings, which is why on a 27-inch screen the site used
   to sit as a 1680px column in the middle of a black field, at the same type
   size it uses on a laptop: measured, 66% of the width at 2560 and 49% at
   3440, with the heading still 50px.

   Raising the root above 1800px raises every one of those ceilings at once,
   so the whole design scales rather than a few parts of it. Because the
   column and the type grow by the same factor, the line stays the same length
   in characters — which is the number that actually governs reading. Below
   1800px nothing changes at all. */
html {
  font-size: clamp(16px, calc(16px + (100vw - 1800px) * 0.0055), 26px);
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-md);
  font-weight: 400;
  line-height: var(--leading-normal);
  letter-spacing: -.011em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  text-rendering: optimizeLegibility;
}

img, picture, video, canvas, svg, iframe { display: block; max-width: 100%; }
img { height: auto; }
input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
ul, ol { list-style: none; }
p, h1, h2, h3, h4, blockquote { overflow-wrap: break-word; }

/* --- The six sizes, applied ---------------------------------------------- */

h1, h2, h3, h4 {
  font-weight: 500;
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-tight);
  text-wrap: balance;
}
h1 { font-size: var(--fs-display); text-transform: uppercase; }
h2 { font-size: var(--fs-xl); }
h3 { font-size: var(--fs-lg); line-height: var(--leading-snug); letter-spacing: -.028em; }

/* Every label, number and piece of metadata on the site. */
.mono {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: 400;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--text-faint);
}

/* One step up from body — used for a page's opening statement, once per page. */
.statement {
  font-size: var(--fs-lg);
  line-height: 1.32;
  letter-spacing: -.024em;
  color: var(--text);
  text-wrap: pretty;
}
.statement strong { font-weight: 500; }

.prose {
  font-size: var(--fs-md);
  line-height: var(--leading-normal);
  color: var(--text-dim);
  text-wrap: pretty;
}
.prose p + p { margin-top: 1.15em; }
.prose strong { color: var(--text); font-weight: 500; }

.dim   { color: var(--text-dim); }
.faint { color: var(--text-faint); }

/* --- Links --------------------------------------------------------------- */

a { color: inherit; text-decoration: none; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

::selection { background: var(--accent); color: #000000; }

.link {
  position: relative;
  padding-bottom: 1px;
  transition: color var(--dur-fast) var(--ease-out);
}
.link::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: currentColor;
  transform-origin: right;
  transition: transform var(--dur) var(--ease);
}
.link:hover { color: var(--accent); }
.link:hover::after { transform: scaleX(0); }

.skip-link {
  position: absolute;
  top: 0; left: 50%;
  transform: translate(-50%, -120%);
  z-index: calc(var(--z-overlay) + 1);
  background: var(--accent);
  color: #000000;
  padding: .75rem 1.25rem;
  font-size: var(--fs-sm);
  transition: transform var(--dur-fast) var(--ease-out);
}
.skip-link:focus { transform: translate(-50%, 0); }

/* --------------------------------------------------------------------------
   Layout: one 12-column spine that every block on every page aligns to.
   Labels start at column 1, reading text at column 3, images at column 9 —
   so the whole site lines up vertically no matter what the block contains.
   -------------------------------------------------------------------------- */

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

.section { padding-block: var(--section-y); }
.section--tight { padding-block: clamp(3rem, 7vw, 6rem); }

/* Two blocks that belong together: the second follows the first at the
   distance the cards inside them are set apart, not at a section's distance. */
.section--joined { padding-bottom: clamp(.75rem, 1.5vw, 1.375rem); }
.section--flush  { padding-top: clamp(.75rem, 1.5vw, 1.375rem); }

.spine {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: var(--col-gap);
  row-gap: clamp(2rem, 4vw, 3.5rem);
  align-items: start;
}

.spine__label { grid-column: 1 / 3; }
.spine__text  { grid-column: 3 / 9; }
.spine__wide  { grid-column: 3 / 12; }
.spine__media { grid-column: 9 / 13; }
.spine__full  { grid-column: 1 / -1; }

@media (max-width: 900px) {
  .spine { grid-template-columns: 1fr; row-gap: 1.75rem; }
  .spine__label, .spine__text, .spine__wide, .spine__media, .spine__full {
    grid-column: 1 / -1;
  }
}

.rule { height: 1px; background: var(--rule); border: 0; width: 100%; }

/* --------------------------------------------------------------------------
   Home page — locked to a single screen, footer included
   -------------------------------------------------------------------------- */

body.home {
  height: 100svh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
body.home main { flex: 1; min-height: 0; }

/* No link back to the page you are already on. */
body.home .nav__mark { display: none; }
body.home .nav__inner { justify-content: flex-end; }

@media (max-width: 900px) {
  body.home { height: auto; overflow: visible; }
}

/* ==========================================================================
   Motion utilities — driven by data-attributes, wired up in js/motion.js
   ========================================================================== */

.js [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 24px, 0);
  transition: opacity var(--dur-slow) var(--ease),
              transform var(--dur-slow) var(--ease);
  transition-delay: calc(var(--i, 0) * .07s);
}
.js [data-reveal].is-in { opacity: 1; transform: none; }

.split__line { display: block; overflow: hidden; }
.split__word { display: inline-block; will-change: transform; }

/* Hidden until it is in — which is the state its words are in anyway.

   The rule below this one moves `.split__word`, and those spans do not exist
   until the splitter has run. So a heading arrived as plain text at full
   strength, was cut into spans the moment the module landed, dropped out of
   sight behind its own mask, and rose again: three states where there is
   meant to be one. On the front page, where the name is the largest thing on
   the site, it was impossible to miss.

   `[data-split]` is in the markup from the first byte, so this catches the
   heading before there is anything to split, and `is-split` is written by the
   splitter itself the moment it has finished with that element.

   Deliberately not `.is-in`, which is what the reveal observer writes. A
   heading gated on the observer stays invisible until an IntersectionObserver
   callback arrives, and there are real conditions — a tab restored in the
   background, a first frame the browser never painted — where that callback
   is late or does not come at all. The words behind the mask are still gated
   on `.is-in`; only whether the heading exists at all is gated on the one
   thing that always runs. */
.js [data-split]:not(.is-split) { visibility: hidden; }
.js [data-split]:not(.is-in) .split__word { transform: translate3d(0, 105%, 0); }
.split__word {
  transition: transform 1s var(--ease);
  transition-delay: calc(var(--i, 0) * .045s);
}

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] { opacity: 1; transform: none; }
  /* Nothing moves here, so splitting changes nothing that can be seen and
     there is no arrival to protect. Hiding the heading until the observer
     reaches it would only make it appear late, for no reason. */
  .js [data-split]:not(.is-split) { visibility: visible; }
  .js [data-split]:not(.is-in) .split__word { transform: none; }
  [data-parallax] { transform: none !important; }
}
