/* ============================================================================
   DOCUMENT — page registers, masthead, sections, statistics, tables, contact.
   ----------------------------------------------------------------------------
   The page is a stack of full-width registers separated by 3px black rules.
   Inside a register, content sits in .wrap and prose is capped at a measure.
   Nothing is boxed, nothing is carded, nothing has a shadow.
   ========================================================================= */

.wrap {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 0.5rem + 3vw, 4rem);
}

/* ── running head ────────────────────────────────────────────────────────
   A hard black bar, not a floating pill. Appears once the masthead is gone. */
.runhead {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  height: 3rem;
  padding-inline: clamp(1.25rem, 0.5rem + 3vw, 4rem);
  background: #0a0a0a;
  color: #fff;
  transform: translateY(-100%);
  transition: transform 0.35s var(--ease);
  pointer-events: none;
}
.runhead.is-shown { transform: none; }
.runhead span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.runhead .sec { color: var(--accent); flex: none; }
.runhead__bar {
  position: absolute;
  left: 0; bottom: 0;
  height: 3px;
  width: 0;
  background: var(--accent);
  transition: width 0.1s linear;
}

/* ── registers ──────────────────────────────────────────────────────────── */
.reg {
  padding-block: clamp(3.5rem, 2rem + 6vw, 8rem);
  border-top: 3px solid var(--rule-2);
  position: relative;
}

/* The register rule draws itself as you arrive. Rather than animating the
   border (which cannot be transitioned), a paper-coloured bar sits on top of
   it and retracts to the right — so the rule is *uncovered* left to right.
   It inherits --paper, so it works unchanged inside .invert. */
.js .reg::before {
  content: "";
  position: absolute;
  top: -3px;
  left: 0;
  width: 100%;
  height: 3px;
  background: var(--paper);
  transform-origin: right;
  transition: transform 0.9s var(--ease-soft);
  pointer-events: none;
}
.js .reg.is-ruled::before { transform: scaleX(0); }
.js .reg--first::before { display: none; }
.reg--first { border-top: 0; padding-top: clamp(2.5rem, 1.5rem + 4vw, 5rem); }

/* The document closes on a rule, the way every register opens on one.
   Without it the last register trails off into whatever whitespace the
   viewport happens to leave, and the page reads as unfinished rather than
   ended. The bottom padding is tightened to match — a closing rule floating
   120px below the last line is not a close. */
.reg:last-child {
  padding-bottom: clamp(2rem, 1.4rem + 1.6vw, 3rem);
  border-bottom: 3px solid var(--rule-2);
}

/* ── masthead ────────────────────────────────────────────────────────────
   Name at poster scale. The claim carries one marked phrase and nothing else
   competes with it. */
.masthead .role {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.4rem;
  color: var(--accent-dark);
}
.masthead .role .loc { color: var(--ink-3); }

.masthead h1 {
  margin: clamp(0.8rem, 0.4rem + 1.4vw, 1.5rem) 0 0;
  font-size: var(--t-mega);
}

/* ── folding name ────────────────────────────────────────────────────────
   Each character hinges down into place on load, like a folded sheet opening.
   Ported from a React + GSAP component; the markup is generated at runtime by
   js/foldtext.js from the heading's own text, so with JavaScript off the name
   renders as an ordinary heading.

   Everything here is gated on .js for that reason: no script, no hidden text. */
.fold { display: inline-block; }

/* The real string, for screen readers and for select-and-copy. */
.fold-sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.fold-vis, .fold-space { display: inline; }

/* Perspective sits on the segment, not the piece — one vanishing point per
   glyph, or the outer letters shear badly across a nine-character line. */
.fold-seg {
  display: inline-block;
  line-height: inherit;
  vertical-align: baseline;
  perspective: var(--fold-perspective, 700px);
  transform-style: preserve-3d;
}

.fold-piece {
  position: relative;
  display: inline-block;
  line-height: inherit;
  transform-style: preserve-3d;
  backface-visibility: hidden;
}

.js .fold-piece {
  opacity: 0;
  transform: var(--fold-from, rotateX(-92deg));
  will-change: transform, opacity;
}
.js .fold.is-open .fold-piece {
  opacity: 1;
  transform: none;
  transition:
    opacity   var(--fold-dur, 0.65s) var(--ease),
    transform var(--fold-dur, 0.65s) var(--ease);
  transition-delay: var(--d, 0ms);
}
.js .fold.is-done .fold-piece { will-change: auto; }

/* The crease: a shadow raked across the panel while it is still angled, fading
   to nothing as it flattens. multiply so it darkens the glyph rather than
   greying the paper around it. */
.fold-piece::after {
  content: "";
  position: absolute;
  inset: -0.08em -0.02em;
  pointer-events: none;
  border-radius: 0.08em;
  mix-blend-mode: multiply;
  opacity: 0;
}
.fold-piece[data-fold-hinge="top"]::after {
  background: linear-gradient(180deg, rgba(0,0,0,0.58) 0%, rgba(0,0,0,0.22) 42%, rgba(255,255,255,0.26) 100%);
}
.fold-piece[data-fold-hinge="bottom"]::after {
  background: linear-gradient(0deg, rgba(0,0,0,0.58) 0%, rgba(0,0,0,0.22) 42%, rgba(255,255,255,0.26) 100%);
}
.fold-piece[data-fold-hinge="left"]::after {
  background: linear-gradient(90deg, rgba(0,0,0,0.58) 0%, rgba(0,0,0,0.22) 42%, rgba(255,255,255,0.26) 100%);
}
.fold-piece[data-fold-hinge="right"]::after {
  background: linear-gradient(270deg, rgba(0,0,0,0.58) 0%, rgba(0,0,0,0.22) 42%, rgba(255,255,255,0.26) 100%);
}
.js .fold-piece::after { opacity: var(--fold-crease, 0.55); }
.js .fold.is-open .fold-piece::after {
  opacity: 0;
  transition: opacity var(--fold-dur, 0.65s) var(--ease);
  transition-delay: var(--d, 0ms);
}

/* foldtext.js skips splitting entirely under reduced motion, so this is only
   a backstop in case the markup is ever authored by hand. */
@media (prefers-reduced-motion: reduce) {
  .js .fold-piece { opacity: 1; transform: none; }
  .js .fold-piece::after { opacity: 0 !important; }
}

/* Claim left, supporting detail right — otherwise the right half of a poster
   masthead is dead space at desktop widths. */
.mast-cols {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: clamp(1.6rem, 0.8rem + 3vw, 4.5rem);
  align-items: start;
  margin-top: clamp(1.5rem, 1rem + 1.6vw, 2.4rem);
}

.claim {
  max-width: 21ch;
  font-size: var(--t-lead);
  font-weight: 500;
  line-height: 1.28;
  letter-spacing: -0.02em;
}

.sub {
  max-width: 46ch;
  color: var(--ink-2);
  font-size: 1.05rem;
  line-height: 1.55;
}

.links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.6rem;
  margin-top: clamp(1.4rem, 1rem + 1vw, 1.9rem);
}
.links a {
  padding-bottom: 3px;
  border-bottom: 2px solid var(--ink);
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.links a:hover { color: var(--accent-dark); border-color: var(--accent); }

/* Every underlined link shares one hover: the accent wipes across the rule
   left to right, the same gesture as the marker pen. background-size rather
   than a pseudo-element, so it survives wrapping. */
.links a, .avail a, .idx .go, .also a {
  background-image: linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 2px;
  transition: background-size 0.32s var(--ease), color 0.2s var(--ease);
}
.links a:hover, .avail a:hover, .idx .go:hover, .also a:hover { background-size: 100% 2px; }

@media (prefers-reduced-motion: reduce) {
  .links a, .avail a, .idx .go, .also a { transition: color 0.2s; }
}

/* ── statistics row ──────────────────────────────────────────────────────
   The device the whole design is built around: big numbers under a heavy
   rule, divided by hairlines. Reused verbatim in every Results block. */
.stats {
  display: flex;
  flex-wrap: wrap;
  margin-top: clamp(2.2rem, 1.5rem + 2vw, 3.6rem);
  border-top: 3px solid var(--rule-2);
}
.stats > * {
  flex: 1 1 150px;
  padding: clamp(0.9rem, 0.7rem + 0.6vw, 1.3rem) 1.2rem 0 0;
  border-right: 1px solid var(--rule);
}
.stats > *:last-child { border-right: 0; padding-right: 0; }
.stats b {
  display: block;
  /* Counting changes the string width every frame; tabular figures stop the
     label underneath from shuffling sideways while it runs. */
  font-variant-numeric: tabular-nums lining-nums;
  display: block;
  font-size: var(--t-num);
  font-weight: 800;
  letter-spacing: -0.045em;
  line-height: 1;
  font-variant-numeric: tabular-nums lining-nums;
}
.stats span {
  display: block;
  margin-top: 0.5rem;
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink-2);
}

/* ── what I build ────────────────────────────────────────────────────────
   Same anatomy as .stats — heavy rule, hairline dividers — but the lead line
   is a word rather than a number, so it is set smaller. */
.builds {
  display: flex;
  flex-wrap: wrap;
  border-top: 3px solid var(--rule-2);
}
.builds > * {
  flex: 1 1 240px;
  padding: clamp(0.9rem, 0.7rem + 0.6vw, 1.3rem) 1.5rem 0 0;
  border-right: 1px solid var(--rule);
}
.builds > *:last-child { border-right: 0; padding-right: 0; }
.builds b {
  display: block;
  font-size: clamp(1.15rem, 0.95rem + 0.9vw, 1.7rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -0.03em;
  line-height: 1.05;
}
.builds span {
  display: block;
  margin-top: 0.6rem;
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink-2);
}

/* ── stack ───────────────────────────────────────────────────────────────
   A typographic index, not a chart. No bars, no dots, no percentages — a
   proficiency meter is a claim you cannot substantiate, and the case studies
   below are doing that job properly. */
/* Fixed 3 columns rather than auto-fit: with six categories that gives two
   even rows, where auto-fit leaves a lone orphan on the second. */
.stack {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: clamp(2rem, 1.4rem + 2vw, 3.2rem);
  border-top: 1px solid var(--rule);
}
.stack > div {
  padding: 1.1rem 1.6rem 1.3rem 0;
  border-bottom: 1px solid var(--rule);
}
.stack dt { margin-bottom: 0.6rem; color: var(--accent-dark); }
.stack dd {
  margin: 0;
  font-family: var(--mono);
  font-size: 13.5px;
  line-height: 1.85;
  letter-spacing: 0.01em;
}

/* ── section heads ──────────────────────────────────────────────────────── */
.sec-h { display: grid; gap: 0.6rem; margin-bottom: clamp(2rem, 1.4rem + 2vw, 3.4rem); }
.sec-h .n {
  font-family: var(--mono);
  font-size: clamp(2.4rem, 1.6rem + 3vw, 4.2rem);
  font-weight: 700;
  line-height: 0.9;
  letter-spacing: -0.04em;
  color: var(--accent);
}
.sec-h h2 { font-size: var(--t-h2); max-width: 15ch; }
.sec-h .deck {
  margin-top: 0.4rem;
  max-width: 46ch;
  font-size: 1.08rem;
  line-height: 1.5;
  color: var(--ink-2);
}
.sec-h .chip {
  justify-self: start;
  margin-top: 0.5rem;
  padding: 0.45rem 0.8rem;
  background: var(--ink);
  color: var(--paper);
}

/* ── work index ──────────────────────────────────────────────────────────
   Large rows rather than a grid of cards. Cards would flatten six things of
   very different weight into six identical tiles; rows let the two with case
   studies below carry more text than the ones that don't. */
.idx { border-top: 1px solid var(--rule); }
.idx > li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  gap: clamp(1rem, 0.5rem + 2vw, 3rem);
  padding: clamp(1.3rem, 1rem + 1.2vw, 2.1rem) 0;
  border-bottom: 1px solid var(--rule);
}
.idx h3 { font-size: var(--t-h3); text-transform: uppercase; letter-spacing: -0.03em; }
.idx .st { margin-top: 0.5rem; color: var(--accent-dark); }
.idx p { color: var(--ink-2); }
.idx .tech {
  margin-top: 0.9rem;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--ink-3);
}

/* Live URL, or a jump to the case study further down. */
.idx .go {
  display: inline-block;
  margin-top: 0.7rem;
  padding-bottom: 2px;
  border-bottom: 2px solid var(--ink);
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.idx .go:hover { color: var(--accent-dark); border-color: var(--accent); }

/* ── also shipped ────────────────────────────────────────────────────────
   A second tier, deliberately compact. Thirteen identical full-width rows
   would give a Weebly site the same visual weight as a two-tower recommender;
   this keeps the smaller builds present and findable without letting them
   flatten the two that carry the case studies. */
.also { margin-top: clamp(2.2rem, 1.6rem + 2vw, 3.4rem); }
.also > .label {
  display: block;
  padding-bottom: 0.7rem;
  border-bottom: 3px solid var(--rule-2);
  color: var(--ink-3);
}
/* Full-width rows rather than a column grid: five items in four columns
   leaves an orphan on the second row, and rows echo .idx above. */
.also li {
  display: grid;
  grid-template-columns: minmax(0, 10rem) minmax(0, 1fr) minmax(0, auto);
  gap: 0.3rem 1.6rem;
  align-items: baseline;
  padding: 0.85rem 0;
  border-bottom: 1px solid var(--rule);
}
.also b { font-size: 15px; font-weight: 700; letter-spacing: -0.02em; }
.also span { font-size: 13.5px; line-height: 1.45; color: var(--ink-2); }
.also a {
  justify-self: end;
  padding-bottom: 2px;
  border-bottom: 2px solid var(--ink);
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.03em;
  white-space: nowrap;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.also a:hover { color: var(--accent-dark); border-color: var(--accent); }

@media (max-width: 820px) {
  .also li { grid-template-columns: 1fr; gap: 0.35rem; }
  .also a { justify-self: start; margin-top: 0.35rem; white-space: normal; }
}

/* ── beats (problem / constraint / architecture / results) ──────────────── */
.beat { margin-top: clamp(2.4rem, 1.8rem + 2vw, 4rem); }
.beat > .label {
  display: block;
  padding-bottom: 0.7rem;
  margin-bottom: 1.3rem;
  border-bottom: 1px solid var(--rule);
  color: var(--accent-dark);
}
.beat h3 { margin: 2.2rem 0 0.8rem; font-size: var(--t-h3); }

.prose { max-width: var(--measure); }
.prose p + p { margin-top: 1.1em; }
.prose p { color: var(--ink-2); }
.prose strong, .prose em { color: var(--ink); }

/* ── key statement ───────────────────────────────────────────────────────
   The marker pen at full width. One per case study, on the sentence every
   other decision follows from. */
.key {
  margin-top: clamp(1.6rem, 1.2rem + 1.4vw, 2.4rem);
  padding: clamp(1.4rem, 1rem + 1.6vw, 2.2rem) clamp(1.4rem, 1rem + 1.6vw, 2.4rem);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: clamp(1.05rem, 0.95rem + 0.6vw, 1.4rem);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.015em;
  max-width: 46ch;
}
.key strong { font-weight: 800; }

/* ── numbered constraint list ───────────────────────────────────────────── */
.enum { counter-reset: e; margin-top: 1.3rem; max-width: var(--measure); }
.enum > li {
  counter-increment: e;
  position: relative;
  padding: 0.85rem 0 0.85rem 3rem;
  border-top: 1px solid var(--rule);
  color: var(--ink-2);
}
.enum > li:last-child { border-bottom: 1px solid var(--rule); }
.enum > li::before {
  content: counter(e, decimal-leading-zero);
  position: absolute;
  left: 0; top: 0.95rem;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 700;
  color: var(--accent-dark);
}

/* ── method principles ──────────────────────────────────────────────────── */
.principles { counter-reset: p; display: grid; gap: 0; border-top: 1px solid var(--rule); }
.principles > li {
  counter-increment: p;
  display: grid;
  grid-template-columns: 5rem minmax(0, 1fr);
  gap: clamp(1rem, 0.5rem + 2vw, 2.5rem);
  padding: clamp(1.6rem, 1.2rem + 1.4vw, 2.6rem) 0;
  border-bottom: 1px solid var(--rule);
}
.principles > li::before {
  content: "M" counter(p);
  font-family: var(--mono);
  font-size: clamp(1.4rem, 1rem + 1.4vw, 2.2rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--accent);
}
.principles h3 { font-size: var(--t-h3); margin-bottom: 0.6rem; max-width: 24ch; }
.principles p { color: var(--ink-2); max-width: 58ch; }
.principles .ev { display: block; margin-top: 0.8rem; color: var(--ink-3); }

/* ── contact ────────────────────────────────────────────────────────────── */
/* Sits between the section title and body copy. Sizing it at --t-h2 makes it
   compete with the heading directly above it, which reads as two headlines. */
.shout {
  font-size: clamp(1.55rem, 1.1rem + 2vw, 2.75rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.035em;
  max-width: 24ch;
  margin-bottom: clamp(1.6rem, 1.2rem + 1.6vw, 2.6rem);
}
.avail {
  margin-top: clamp(2rem, 1.5rem + 2vw, 3.2rem);
  border-top: 3px solid var(--rule-2);
}
.avail > div {
  display: grid;
  grid-template-columns: 9rem minmax(0, 1fr);
  gap: 1rem;
  padding: 0.95rem 0;
  border-bottom: 1px solid var(--rule);
}
.avail dt { color: var(--ink-3); padding-top: 0.2em; }
.avail dd { margin: 0; font-size: 1.05rem; font-weight: 500; }
.avail a {
  border-bottom: 2px solid currentColor;
  padding-bottom: 2px;
  transition: color 0.2s var(--ease);
}
.avail a:hover { color: var(--accent-dark); }


/* ── scroll reveal ───────────────────────────────────────────────────────
   Scoped to .js — set by the inline script in <head> before first paint — so
   with JavaScript off the page renders complete rather than blank. Content
   must never be able to get stuck invisible. */
.js [data-r] {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
  will-change: opacity, transform;
}
.js [data-r].is-in { opacity: 1; transform: none; }
.js [data-r].is-done { will-change: auto; }

@media (prefers-reduced-motion: reduce) {
  .js [data-r] { opacity: 1; transform: none; }
}

/* ══ RESPONSIVE ═══════════════════════════════════════════════════════════ */
@media (max-width: 900px) {
  .stack { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 820px) {
  .idx > li { grid-template-columns: 1fr; }
  .principles > li { grid-template-columns: 3.5rem minmax(0, 1fr); }
  .mast-cols { grid-template-columns: 1fr; gap: 1.4rem; }
  .claim, .sub { max-width: none; }
}

@media (max-width: 560px) {
  .stats > *, .builds > * {
    flex-basis: 100%;
    border-right: 0;
    border-bottom: 1px solid var(--rule);
    padding-bottom: 1rem;
  }
  .stats > *:last-child, .builds > *:last-child { border-bottom: 0; }
  .stack { grid-template-columns: 1fr; }
  .avail > div { grid-template-columns: 1fr; gap: 0.3rem; }
  .principles > li { grid-template-columns: 1fr; gap: 0.5rem; }
  .runhead .head { display: none; }
}

/* ══ PRINT ════════════════════════════════════════════════════════════════ */
@media print {
  .runhead, .skip { display: none !important; }
  html { scroll-behavior: auto; }
  .invert { background: #fff !important; color: #0a0a0a !important; }
  .invert * { color: inherit !important; }
  .key { background: #fff !important; color: #0a0a0a !important; border-left: 4px solid #0a0a0a; }
  .js [data-r] { opacity: 1 !important; transform: none !important; }
  .reg { padding-block: 1.5rem; break-inside: avoid; }
  h1, h2, h3 { break-after: avoid; }
  figure, table { break-inside: avoid; }
  a[href^="http"]::after, a[href^="mailto"]::after {
    content: " <" attr(href) ">";
    font-family: var(--mono);
    font-size: 0.75em;
  }
}
