/* ============================================================
   Burnsridge Systems — the public site
   ============================================================

   THE ONE RULE THIS FILE ENFORCES

   Gold is reserved for THE LINE and for what the line becomes.
   Nothing else on this site is ever gold — not a heading, not a
   link, not a border. The brand guide says it twice:

     "Gold appears on ONE element per view. Its authority comes
      from being rare."

   Type is Bone and Ash. Rules are Edge. The ground is black.
   The line is gold.

   THE THIRD VALUE. v1 of this page had exactly two values: pure
   black, and a 1px line or a letterform. Measured off the
   screenshots, 97% of it was near-black — not restrained, thin.
   A composition needs three values to read as composed. The
   third one here is the terrain, which now runs behind the whole
   page, plus --coal finally doing the job the brand guide's
   76/14/8/2 ratio always gave it and which v1 skipped entirely.

   Colours are the IDENTITY palette from BRAND-GUIDE.html, NOT
   the app's brand.css. The app darkens its gold (#c9a227) so
   small UI text clears a contrast floor on a white ground. This
   document has no white ground and no small gold text, so it
   uses the identity gold (#D4AF37) the logo is drawn in.
   ============================================================ */

/* ---- 1. Tokens ------------------------------------------- */

:root {
  /* ground, in the guide's own order */
  --black:    #000000;
  --pitch:    #070707;
  --coal:     #101010;

  /* rules — warm, deliberately not neutral grey */
  --edge:      #2B2723;
  --edge-soft: #221F1C;

  /* the accent. The line, and only the line. */
  --gold:     #D4AF37;
  --gold-lt:  #EBD68F;
  --gold-dim: #7A5E17;

  /* type */
  --bone:    #F5F2EA;
  --ash:     #9A948A;

  /* NOT A TEXT COLOUR. #6B665E on black is 3.69 : 1, under the
     4.5 : 1 floor for normal-size text — and every place it was
     wanted here (11px mono labels, 10px SVG peak names, captions)
     is exactly the size that floor exists for.

     This page claims, in the console section, that the desk's test
     suite fails a build when a colour drops below its contrast
     floor. A site making that claim in type nobody can read would
     be the most embarrassing possible way to be wrong.

     So all text is Bone (18.77 : 1) or Ash (6.98 : 1), and the
     hierarchy between a label and a paragraph comes from size,
     weight, letter-spacing and position instead of from a second
     grey. Kept declared because the identity defines it. */
  --ash-dim: #6B665E;

  --font-display: "Charis SIL", Charter, Georgia, "Times New Roman", serif;
  --font-body:    Archivo, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono:    "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* The x-position of the spine. Every act's line meets it here,
     which is the whole continuity trick. */
  --rail: clamp(22px, 6vw, 92px);
  --gap:  clamp(20px, 4vw, 64px);

  /* One easing curve for the whole site. A page with four easings
     reads as four pages. */
  --ease: cubic-bezier(.7, 0, .2, 1);

  --measure: 34rem;
}

/* ---- 2. Fonts -------------------------------------------- */

@font-face {
  font-family: "Charis SIL";
  src: url("fonts/charis-sil-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Charis SIL";
  src: url("fonts/charis-sil-700.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
/* One variable file covers every weight Archivo is used at. The
   three static weights Google serves are byte-identical to it. */
@font-face {
  font-family: Archivo;
  src: url("fonts/archivo-var.woff2") format("woff2-variations");
  font-weight: 100 900; font-stretch: 100%; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("fonts/ibm-plex-mono-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("fonts/ibm-plex-mono-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}

/* ---- 3. Reset -------------------------------------------- */

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

html {
  background: var(--black);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--black);
  color: var(--bone);
  font-family: var(--font-body);
  font-size: clamp(0.95rem, 0.9rem + 0.2vw, 1.0625rem);
  line-height: 1.65;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img, svg { max-width: 100%; }
h1, h2, h3, p, blockquote, figure, dl, dd { margin: 0; }

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

:focus-visible {
  outline: 1px solid var(--gold);
  outline-offset: 4px;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* The skip link. In v1 this pointed at a section that was
   opacity:0 until it intersected and had no tabindex, so neither
   half of it worked — focus never moved and there was nothing to
   see if it had. Its target now carries tabindex="-1". */
.skip {
  position: fixed;
  top: 0; left: 0;
  z-index: 100;
  transform: translateY(-120%);
  background: var(--coal);
  color: var(--bone);
  border: 1px solid var(--gold);
  padding: .7rem 1.1rem;
  font-family: var(--font-mono);
  font-size: .75rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  text-decoration: none;
  transition: transform .25s var(--ease);
}
.skip:focus { transform: none; }

/* ---- 4. The ground and the terrain ----------------------- */

/* The traverse warms as it descends. Pure black at the summit,
   a few units of warmth at the foot. It is almost subliminal and
   it is half of what stops the page reading as one flat value.
   --p is whole-document scroll progress, published by line.js. */
.ground {
  position: fixed;
  inset: 0;
  z-index: 0;
  background: #0B0907;
  opacity: var(--p, 0);
  pointer-events: none;
}

/* Fixed, full-viewport, transparent, and running for the entire
   page rather than just the hero. See the header comment in
   ridge.js for what pays for that. */
#ridge, .ridge-fallback {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  display: block;
  pointer-events: none;
}
#ridge { opacity: 0; transition: opacity .9s var(--ease); }
#ridge.live { opacity: 1; }

/* Quiet enough to be the ground for the whole page, because in
   this state it cannot sink or dim on scroll the way the shader
   does — it is one static drawing behind six screens of text. */
.ridge-fallback { opacity: .5; transition: opacity .9s var(--ease); }
.webgl .ridge-fallback { opacity: 0; }

/* Content sits above both. Every section says so explicitly rather
   than relying on paint order against a z-indexed fixed canvas. */
.hero, .act, .act-mark, .act-contact {
  position: relative;
  z-index: 2;
}

/* ---- 5. Grain -------------------------------------------- */
/* A ~300-byte inline feTurbulence, the same trick the storefront
   template uses. design-rules.md is right that it does more for
   the printed feeling than anything else on a page. */

body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 90;
  opacity: 0.042;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---- 6. The spine ---------------------------------------- */
/* The line, made continuous. Fixed to the viewport at a known x,
   so every act can meet it at exactly the same place without any
   of them knowing about each other.

   Deliberately NOT one morphing path. A morphing path breaks the
   moment a section reflows — a headline wrapping to three lines
   on a narrow phone is enough. */

.spine {
  position: fixed;
  left: var(--rail);
  top: 0;
  bottom: 0;
  width: 1px;
  z-index: 40;
  pointer-events: none;
  background: var(--gold);
  transform-origin: top center;
  transform: scaleY(var(--spine-draw, 0));
  opacity: var(--spine-on, 0);
  transition: opacity .6s var(--ease);
}

/* A short brighter run tracking the scroll position down the rail,
   so the line reads as alive rather than painted on. */
.spine::after {
  content: "";
  position: absolute;
  left: -1px;
  width: 3px;
  height: 22vh;
  top: calc(var(--spine-head, 0) * 100% - 11vh);
  background: linear-gradient(
    to bottom,
    transparent,
    var(--gold-lt) 45%,
    var(--gold-lt) 55%,
    transparent
  );
  opacity: .5;
  filter: blur(1px);
}

/* ---- 7. Layout ------------------------------------------- */

.act {
  padding-left: calc(var(--rail) + var(--gap));
  padding-right: clamp(20px, 5vw, 96px);
}

/* Every act's horizontal line starts flush at the spine and runs
   right. This is the join that makes the whole thing read as one
   stroke. */
.branch {
  position: absolute;
  left: var(--rail);
  height: 1px;
  background: var(--gold);
  transform-origin: left center;
  transform: scaleX(var(--branch, 0));
  transition: transform 1.1s var(--ease);
}

.label {
  font-family: var(--font-mono);
  font-size: .6875rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ash);
}

.prose {
  font-family: var(--font-body);
  color: var(--ash);
  max-width: var(--measure);
  font-size: 1.0625rem;
  line-height: 1.7;
}
.prose strong { color: var(--bone); font-weight: 500; }

/* ---- 8. Act 1 — the hero --------------------------------- */

.hero {
  height: 100svh;
  min-height: 34rem;
}

.hero-inner {
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: clamp(20px, 3.4vh, 40px) clamp(20px, 5vw, 96px)
           clamp(20px, 3.4vh, 40px) var(--rail);
}

.hero-mark {
  width: clamp(190px, 24vw, 330px);
  transition: opacity 1s var(--ease) .55s, transform 1s var(--ease) .55s;
}
.js .hero-mark { opacity: 0; transform: translateY(8px); }
.ready .hero-mark { opacity: 1; transform: none; }
.hero-mark img { display: block; width: 100%; height: auto; }

/* THE STATEMENT. It is the <h1> and it is on the FIRST screen —
   in v1 the hero had no headline at all and this sentence lived a
   full screen down, so the first thing anybody read was 11px of
   grey mono.

   The words are spans authored by hand in the HTML, each carrying
   an --i, and they wipe in left to right in sequence. The wipe is
   deliberately the same gesture as the line drawing itself. */
.statement {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.75rem, 6.6vw, 6.5rem);
  line-height: 1.0;
  letter-spacing: -0.022em;
  color: var(--bone);
  max-width: 17ch;
  margin: auto 0;
}

.statement .w {
  display: inline-block;
  transition:
    clip-path .85s var(--ease) calc(var(--i, 0) * 52ms),
    opacity   .5s  var(--ease) calc(var(--i, 0) * 52ms);
}
.js .statement .w { clip-path: inset(-12% 100% -12% -4%); opacity: 0; }
.js.ready .statement .w { clip-path: inset(-12% -4% -12% -4%); opacity: 1; }

/* The one moment of real scale on the page. Everything else on
   this site sits between 11px and 42px; this is 194px at 1440.
   A hierarchy needs one thing that is not merely larger. */
.statement .huge {
  display: block;
  font-size: clamp(3.5rem, 13.5vw, 13rem);
  line-height: .86;
  letter-spacing: -0.035em;
  margin-top: .06em;
}

.hero-foot {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem;
  flex-wrap: wrap;
  transition: opacity 1s var(--ease) 1.1s;
}
.js .hero-foot { opacity: 0; }
.ready .hero-foot { opacity: 1; }

/* Was 11px in v1 — the register of a copyright line, carrying the
   single most differentiating fact on the page. */
.hero-meta {
  font-family: var(--font-mono);
  font-size: .8125rem;
  letter-spacing: .07em;
  color: var(--ash);
  line-height: 1.9;
}
.hero-meta b { color: var(--bone); font-weight: 500; }

.cue {
  font-family: var(--font-mono);
  font-size: .6875rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ash);
  display: flex;
  align-items: center;
  gap: .7rem;
}
.cue i {
  display: block;
  width: 1px;
  height: 34px;
  background: linear-gradient(to bottom, var(--edge), var(--gold));
  animation: cue 2.6s var(--ease) infinite;
  transform-origin: top;
}
@keyframes cue {
  0%, 100% { transform: scaleY(.35); opacity: .5; }
  50%      { transform: scaleY(1);   opacity: 1; }
}

/* ---- 9. Act 2 — the terms -------------------------------- */

.act-terms { padding-top: 14vh; padding-bottom: 12vh; }
.act-terms > .label { margin-bottom: 4rem; }

/* The box reaches back to the rail and pads the text forward again,
   so the branch below can live at left:0 — INSIDE the border box.
   That matters because a mask clips to the border box, and a branch
   at a negative offset would be masked away for good. */
/* Two columns, and the second one is the point.
   v1 set every block on this page flush left at the same x and
   capped it around 46rem, so the right 39% of a 1440px viewport was
   empty black for all 6,200px. A numeral in that column costs
   nothing, breaks the one layout this page was using five times
   over, and gives the terms the register of a contract rather than
   a list of quotes. It is --edge, which is texture rather than
   text, and it is aria-hidden: the captions already name them. */
.term {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 44rem) 1fr;
  align-items: start;
  column-gap: clamp(1.5rem, 5vw, 5rem);
  margin-left: calc(-1 * var(--gap));
  padding: 2.4rem 0 2.4rem var(--gap);
  max-width: calc(76rem + var(--gap));
  transition: border-color .45s var(--ease);
}
.term q, .term figcaption { grid-column: 1; }
.term-no {
  grid-column: 2;
  grid-row: 1 / span 2;
  justify-self: end;
  align-self: start;
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 7.5vw, 6.5rem);
  line-height: .78;
  letter-spacing: -0.03em;
  color: var(--edge);
  transition: color .5s var(--ease);
  user-select: none;
}
.term:hover .term-no { color: var(--gold-dim); }
.term + .term { border-top: 1px solid var(--edge-soft); }
.term:hover { border-color: var(--edge); }

.term .branch {
  top: 0;
  left: 0;
  width: clamp(28px, 5vw, 76px);
  transition: transform 1.1s var(--ease), width .45s var(--ease);
}
.term:hover .branch { width: clamp(44px, 9vw, 150px); }

.term q {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 3.1vw, 2.35rem);
  line-height: 1.28;
  letter-spacing: -0.012em;
  color: var(--bone);
  quotes: none;
  text-wrap: pretty;
}
.term figcaption {
  margin-top: 1.1rem;
  font-family: var(--font-mono);
  font-size: .6875rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ash);
}

/* ---- 10. Act 3 — the elevation profile ------------------- */

.act-profile { padding-top: 10vh; padding-bottom: 12vh; }
.act-profile > .label { margin-bottom: 1.4rem; }

/* One rule, two users. In v1 these were declared twice, eight
   identical properties apart, which is how acts 3 and 4 became the
   same slide with different words. */
.profile-lede, .console-lede {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3.4vw, 2.6rem);
  line-height: 1.2;
  letter-spacing: -0.014em;
  color: var(--bone);
  margin-bottom: 3.5rem;
}
.profile-lede { max-width: 20ch; }
.console-lede { max-width: 22ch; margin-bottom: 2.6rem; }

.profile-wrap {
  margin-left: calc(-1 * var(--gap));
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--edge) transparent;
}
.profile-svg {
  display: block;
  width: 100%;
  min-width: 40rem;
  height: auto;
}

.profile-svg .terrain { fill: none; stroke: var(--edge); stroke-width: 1; }
.profile-svg .crest {
  fill: none;
  stroke: var(--gold);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
  transition: stroke-dashoffset 2.2s var(--ease);
}
.profile-svg.drawn .crest { stroke-dashoffset: 0; }

.profile-svg .hit { fill: transparent; stroke: none; cursor: pointer; }
.profile-svg .peak { outline: none; }
.profile-svg .peak:focus-visible .node { stroke: var(--gold-lt); r: 7; }

.profile-svg .datum { stroke: var(--edge-soft); stroke-width: 1; }
.profile-svg .drop  { stroke: var(--edge-soft); stroke-width: 1; stroke-dasharray: 2 4; }

.profile-svg .node {
  fill: var(--black);
  stroke: var(--gold-dim);
  stroke-width: 1.5;
  r: 4.5;
  transition: stroke .35s var(--ease), fill .35s var(--ease), r .35s var(--ease);
}
.profile-svg .peak.on .node { fill: var(--gold); stroke: var(--gold); r: 5.5; }

.profile-svg .peak-name {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .15em;
  text-transform: uppercase;
  fill: var(--ash);
  transition: fill .35s var(--ease);
}
.profile-svg .peak.on .peak-name { fill: var(--bone); }

.profile-svg .peak-price {
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 500;
  fill: var(--ash);
  font-variant-numeric: tabular-nums;
  transition: fill .35s var(--ease);
}
.profile-svg .peak.on .peak-price { fill: var(--bone); }

.profile-svg .peak-note {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  fill: var(--ash);
}

.profile-hint {
  display: none;
  margin-top: .9rem;
  font-family: var(--font-mono);
  font-size: .6875rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ash);
}

.profile-scope {
  margin-top: 2.6rem;
  min-height: 5.2rem;
  max-width: 40rem;
  border-top: 1px solid var(--edge-soft);
  padding-top: 1.4rem;
}
.profile-scope .scope-name {
  font-family: var(--font-mono);
  font-size: .6875rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ash);
  display: block;
  margin-bottom: .55rem;
}
.profile-scope .scope-text {
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 2vw, 1.45rem);
  line-height: 1.45;
  color: var(--bone);
}

.ladder {
  margin-top: 4.4rem;
  max-width: 44rem;
  border-top: 1px solid var(--edge-soft);
}
.ladder > div {
  display: grid;
  grid-template-columns: minmax(9rem, 14rem) 1fr;
  gap: 1rem 2rem;
  padding: 1.05rem 0;
  border-bottom: 1px solid var(--edge-soft);
  font-family: var(--font-mono);
  font-size: .8125rem;
  letter-spacing: .02em;
}
.ladder dt { color: var(--ash); }
.ladder dd { color: var(--bone); margin: 0; }

.anchor {
  margin-top: 3.4rem;
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2.9vw, 2.15rem);
  line-height: 1.3;
  letter-spacing: -0.012em;
  color: var(--bone);
  max-width: 24ch;
}
.anchor b { font-weight: 700; }
.anchor-note { margin-top: 1.1rem; }

/* ---- 11. Act 4 — the console ----------------------------- */

.act-console { padding-top: 11vh; padding-bottom: 12vh; }
.act-console > .label { margin-bottom: 1.4rem; }

.console-horizon {
  position: relative;
  height: 1px;
  margin-left: calc(-1 * var(--gap));
  margin-bottom: 2.2rem;
}
.console-horizon .branch { top: 0; left: 0; width: min(88vw, 62rem); }

/* --coal, at last doing the job the brand guide's 76/14/8/2 ratio
   always gave it. In v1 this panel was --pitch (#070707), seven
   units above pure black, so the only non-black surface on 6,200px
   of page was for practical purposes invisible and the border was
   the only thing that made it a panel at all. */
.term-panel {
  background: var(--coal);
  border: 1px solid var(--edge);
  max-width: 62rem;
  padding: 1.5rem clamp(1rem, 2.4vw, 2rem) 1.3rem;
  font-family: var(--font-mono);
  font-size: clamp(.75rem, 1.55vw, .875rem);
  line-height: 1.95;
  color: var(--ash);
  overflow-x: auto;
}
.term-panel .out { white-space: pre; margin: 0; }
.term-panel .cmd { color: var(--bone); }

.term-prompt {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  margin-top: .3rem;
}
.term-prompt > span { color: var(--ash); }
.term-input {
  flex: 1 1 auto;
  background: transparent;
  border: 0;
  padding: 0;
  color: var(--bone);
  font: inherit;
  caret-color: var(--gold);
  min-width: 6rem;
}
.term-input::placeholder { color: var(--ash); opacity: 1; }
.term-input:focus { outline: none; }
.term-hint {
  margin-top: 1.1rem;
  font-family: var(--font-mono);
  font-size: .6875rem;
  letter-spacing: .1em;
  color: var(--ash);
}

.console-note { margin-top: 2.8rem; }

/* ---- 12. Act 5 — the mark -------------------------------- */

.act-mark {
  padding-top: 20vh;
  padding-bottom: 8vh;
  display: flex;
  justify-content: center;
}

/* Was capped at 150px in a 1440px viewport — the payoff of the
   whole "one line becomes the B" concept, at 10% of the screen
   width. It is the second scale event on the page and it should
   behave like one. */
.mark-svg {
  width: clamp(180px, 40vw, 560px);
  height: auto;
  overflow: visible;
}
.mark-svg .b-stroke {
  fill: none;
  stroke: var(--gold);
  stroke-width: 14.3;
  stroke-linejoin: round;
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
  transition: stroke-dashoffset 2.4s var(--ease);
}
.mark-svg.drawn .b-stroke { stroke-dashoffset: 0; }
.mark-svg .b-fill {
  fill: var(--gold);
  opacity: 0;
  transition: opacity 1.1s var(--ease) 1.8s;
}
.mark-svg.drawn .b-fill { opacity: 1; }

/* The curve that carries the line off the rail and into the mark.
   Its viewBox and path are written in real pixels by line.js and
   rewritten on resize, because the rail is a clamp() and the mark
   is a percentage — both move when the window does. */
.mark-lead {
  position: absolute;
  left: var(--rail);
  top: 0;
  pointer-events: none;
}
.mark-lead svg { display: block; overflow: visible; }
.mark-lead path {
  fill: none;
  stroke: var(--gold);
  stroke-width: 1;
  transition: stroke-dashoffset 1.6s var(--ease);
}
.js .mark-lead path {
  stroke-dasharray: var(--lead-len, 400);
  stroke-dashoffset: var(--lead-len, 400);
}
.act-mark.in .mark-lead path { stroke-dashoffset: 0; }

/* ---- 13. Act 6 — contact --------------------------------- */

.act-contact {
  padding-top: 4vh;
  padding-bottom: 12vh;
  text-align: center;
  padding-left: clamp(20px, 5vw, 96px);
  padding-right: clamp(20px, 5vw, 96px);
}

.sendoff {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 5vw, 3.6rem);
  line-height: 1.12;
  letter-spacing: -0.018em;
  color: var(--bone);
  margin-bottom: 2.8rem;
}

.mailto {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: clamp(.9rem, 2.4vw, 1.3rem);
  letter-spacing: .02em;
  color: var(--bone);
  text-decoration: none;
  padding-bottom: .55rem;
  position: relative;
  transition: color .3s var(--ease);
}
/* The spine's last job: the underline under the address. */
.mailto::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--gold);
  transform: scaleX(var(--branch, 0));
  transform-origin: left center;
  transition: transform 1.2s var(--ease);
}
.mailto:hover { color: var(--gold-lt); }

.colophon {
  margin-top: 5.5rem;
  font-family: var(--font-mono);
  font-size: .6875rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ash);
  line-height: 2.2;
}

/* ---- 14. Reveals ----------------------------------------- */

/* A wipe, not a fade. The whole page is about a line being drawn
   from the left; content that fades up is a different gesture
   arguing with it.

   A MASK, NOT clip-path — and that is not a style preference.
   IntersectionObserver computes its intersection rectangle AFTER
   clipping, so an element wearing `clip-path: inset(0 100% 0 0)`
   has an empty intersection rect, can never reach any threshold,
   and therefore never receives the class that would reveal it.
   Written with clip-path, this page rendered the hero and then
   five screens of nothing. A mask is a paint operation, leaves
   the intersection rect alone, and wipes identically.

   The gradient is 250% of the element wide: black across its
   first 40% — one full element width — then fading out. Sliding
   it from 100% to 0% walks the hard edge across the content. */
.rise {
  transition: -webkit-mask-position 1.05s var(--ease),
              mask-position 1.05s var(--ease),
              opacity .5s var(--ease);
}
.js .rise {
  -webkit-mask-image: linear-gradient(90deg, #000 40%, rgba(0,0,0,0) 62%);
          mask-image: linear-gradient(90deg, #000 40%, rgba(0,0,0,0) 62%);
  -webkit-mask-size: 250% 100%;
          mask-size: 250% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: 100% 0;
          mask-position: 100% 0;
  opacity: 0;
}
.js .rise.in {
  -webkit-mask-position: 0 0;
          mask-position: 0 0;
  opacity: 1;
}
.rise.d1 { transition-delay: .1s; }
.rise.d2 { transition-delay: .2s; }
.rise.d3 { transition-delay: .3s; }

/* ---- 15. Narrow screens ---------------------------------- */

@media (max-width: 40rem) {
  .hero-foot { flex-direction: column; align-items: flex-start; gap: 1.4rem; }
  .cue { display: none; }
  .ladder > div { grid-template-columns: 1fr; gap: .2rem; padding: .9rem 0; }
  .term {
    grid-template-columns: 1fr;
    padding: 2rem 0 2rem var(--gap);
  }
  .term-no { grid-column: 1; grid-row: auto; justify-self: start; margin-bottom: .4rem; }
  .profile-scope { min-height: 7rem; }
  .profile-hint { display: block; }
  .statement { max-width: none; }
}

/* ---- 16. Reduced motion ---------------------------------- */
/* Everything arrives, nothing travels. The composition has to
   read with every animation switched off, which is also the
   state a screenshot is taken in. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  /* The spine is NOT forced on: its visibility is driven by scroll
     position rather than by an animation, so it already behaves,
     and forcing it drew the rail through the hero where the line
     has not been handed over yet. */
  .spine::after { display: none; }
  .branch { transform: scaleX(1); }
  .js .rise { -webkit-mask-image: none; mask-image: none; opacity: 1; }
  .statement .w { clip-path: none; opacity: 1; }
  .hero-mark, .hero-foot { opacity: 1; transform: none; }
  .profile-svg .crest { stroke-dashoffset: 0; }
  .mark-svg .b-stroke { stroke-dashoffset: 0; }
  .mark-svg .b-fill { opacity: 1; }
  .mark-lead path { stroke-dashoffset: 0; }
  .mailto::after { transform: scaleX(1); }
  .cue i { animation: none; transform: scaleY(1); }
}

/* ---- 17. Print ------------------------------------------- */

@media print {
  .spine, #ridge, .ridge-fallback, .ground, body::after, .cue { display: none; }
  body { background: #fff; color: #111; }
  .statement, .term q, .sendoff { color: #111; }
}
