/* ══════════════════════════════════════════════════════════════════
   v26 — components. Token-only; works on light field and .mode-ink.
   ══════════════════════════════════════════════════════════════════ */

/* ── sheet header ───────────────────────────────────────────────── */

/* Full container width. The heading and lede inside carry their own
   measures; capping the wrapper as well meant the percentage-based title
   measure was a share of 56rem rather than of the section, and section
   openers stayed narrow no matter how wide the screen got. */
.sheet-head { max-width: var(--maxw); }
.sheet-head__lede { max-width: var(--measure-lede); }
.sheet-head__title { margin-top: 0; }
.sheet-head .label + .sheet-head__title,
.sheet-head .brand-mark + .sheet-head__title { margin-top: var(--s-4); }
.sheet-head__lede  { margin-top: var(--s-4); }
.sheet-head__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-top: var(--s-5);
}
.sheet-head .brand-mark { margin-bottom: var(--s-2); }

.sheet-head__rule {
  margin-top: var(--s-5);
  height: var(--hair);
  background: var(--line-quiet);
  transform-origin: left center;
}
html.no-motion .sheet-head__rule { transform: none !important; }

/* ══ SCHEDULE ═════════════════════════════════════════════════════ */

.schedule {
  width: min(100%, var(--maxw));
  margin-top: var(--s-6);
}

.schedule__head {
  display: flex; justify-content: space-between; align-items: baseline;
  padding-bottom: var(--s-3);
  border-bottom: var(--hair) solid var(--text);
}

.schedule table { width: 100%; border-collapse: collapse; }

.schedule tr {
  border-bottom: var(--hair) solid var(--line-quiet);
  transition: background-color var(--d-fast) var(--ease);
}
.schedule tr:hover { background: var(--field-raise); }

.schedule td { padding: var(--s-4) 0; vertical-align: baseline; }

.schedule__ref {
  width: 4.5rem;
  font: 500 0.6875rem/1 var(--mono);
  letter-spacing: 0.08em;
  color: var(--line);
}
.schedule__name {
  font-size: 1.125rem;
  font-weight: 500;
  letter-spacing: -0.016em;
  line-height: 1.35;
}
.schedule__note {
  display: block;
  margin-top: var(--s-1);
  font-size: var(--t-small);
  line-height: 1.55;
  color: var(--text-dim);
  max-width: 46ch;
}
.schedule__num {
  text-align: right;
  white-space: nowrap;
  font-size: 1.125rem;
}
.schedule__bar { width: 26%; padding-left: var(--s-5); }
.schedule__bar svg {
  display: block;
  width: 100%;
  height: 6px;
  transform-origin: left center;
}
.schedule__bar rect { fill: var(--line); }
.schedule__foot {
  display: flex; justify-content: space-between; align-items: baseline;
  padding-top: var(--s-4);
}

/* ══ PLOT ═════════════════════════════════════════════════════════ */

.plot {
  width: min(100%, var(--maxw));
  margin-top: var(--s-6);
}

.plot__chart {
  border-block: var(--hair) solid var(--line-quiet);
  padding-block: var(--s-5);
}
.plot__chart svg { width: 100%; height: 148px; }

.plot__trace {
  fill: none;
  stroke: var(--signal);
  stroke-width: 1.5;
  stroke-linejoin: round;
}
html.no-motion .plot__trace { stroke-dashoffset: 0 !important; }

.plot__grid { stroke: var(--line-quiet); stroke-width: 1; }

.readout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: var(--hair);
  background: var(--line-quiet);
}
.readout__cell {
  background: var(--field);
  padding: var(--s-4) var(--s-4) var(--s-5);
  transition: background-color var(--d-fast) var(--ease);
}
.readout__cell:hover { background: var(--field-raise); }
.readout__val {
  font-size: clamp(1.75rem, 2.8vw, 2.25rem);
  line-height: 1;
  margin-top: var(--s-3);
  color: var(--text);
}
.readout__val small {
  font-size: 0.8125rem;
  color: var(--text-dim);
  letter-spacing: 0;
}
.readout__cell--signal .readout__val { color: var(--signal); }

/* ══ STICKY STACK ═════════════════════════════════════════════════ */

.stack { padding-block: var(--section-y); }

@media (min-width: 900px) {
}

.stack__card {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  min-height: 17.5rem;
  padding: clamp(1.5rem, 2.5vw, 2rem);
  background: var(--field);
  border: var(--hair) solid var(--line-quiet);
  border-radius: var(--radius);
  transform-origin: 50% 0%;
}
.stack__card:nth-child(1) { z-index: 1; }
.stack__card:nth-child(2) { z-index: 2; }
.stack__card:nth-child(3) { z-index: 3; }

.stack__num {
  font: 500 var(--t-label)/1 var(--mono);
  letter-spacing: var(--track-label);
  color: var(--line);
}
.stack__title {
  margin-top: var(--s-4);
  max-width: 22ch;
  font-size: clamp(1.45rem, 2.4vw, 1.9rem);
  font-weight: 550;
  font-variation-settings: "wdth" 96, "wght" 550;
}
.stack__body  {
  margin-top: var(--s-4);
  color: var(--text-dim);
  max-width: 52ch;
  font-size: var(--t-small);
  line-height: 1.65;
}
.stack__meta  { margin-top: auto; padding-top: var(--s-4); }

@media (max-width: 899px) {
.stack__card {
    grid-area: auto;
    transform: none !important;
    opacity: 1 !important;
    min-height: 0;
  }
}

/* ══ PARALLAX / INK ═══════════════════════════════════════════════ */

[data-parallax] { will-change: auto; }

.mode-ink.field-grid::before { opacity: 0.35; }

@media (max-width: 760px) {
  .schedule__bar { display: none; }
  .schedule__ref { width: 3.25rem; }
  .plot__chart svg { height: 96px; }
}

/* ── standfirst ─────────────────────────────────────────────────── */

/* ── form fields ────────────────────────────────────────────────── */
/* Shared by the public contact form and every admin form (login, content
   editor). One control vocabulary, one place it's defined. */

.field { margin-bottom: var(--s-5); }

.field__label {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  margin-bottom: var(--s-3);
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--text);
}
.field__req, .field__opt {
  font: 500 var(--t-label)/1 var(--mono);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.field__req { color: var(--line); }
.field__opt { color: var(--text-faint); }

.field__input {
  display: block;
  width: 100%;
  min-height: 48px;
  padding: var(--s-3) var(--s-4);
  background: var(--field);
  border: var(--hair) solid var(--line-quiet);
  border-radius: var(--radius);
  color: var(--text);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.6;
  transition:
    border-color var(--d-fast) var(--ease),
    box-shadow var(--d-fast) var(--ease),
    background-color var(--d-fast) var(--ease);
}
.field__input:hover {
  border-color: color-mix(in oklab, var(--line-quiet) 45%, var(--text-faint));
  background: var(--field-raise);
}
.field__input:focus {
  outline: none;
  border-color: var(--line);
  box-shadow: 0 0 0 1px var(--line);
  background: var(--field);
}
.field__input--area { resize: vertical; min-height: 12rem; }
.field__input::placeholder { color: var(--text-faint); }
.field__input.is-invalid {
  border-color: var(--danger);
  box-shadow: 0 0 0 1px color-mix(in oklab, var(--danger) 35%, transparent);
  background: color-mix(in oklab, var(--danger-soft) 55%, var(--field));
}

.field__error {
  margin-top: var(--s-2);
  font-size: var(--t-small);
  color: var(--danger);
  font-weight: 500;
}
.field.is-invalid .field__hint { display: none; }

.field__hint {
  margin-top: var(--s-2);
  font-size: var(--t-small);
  color: var(--text-dim);
}

/* ── buttons ────────────────────────────────────────────────────── */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  min-height: 44px;
  padding: 0.7rem 1.1rem;
  border: var(--hair) solid var(--line);
  border-radius: var(--radius);
  color: var(--line);
  background: transparent;
  font: 600 var(--t-label)/1 var(--mono);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  cursor: pointer;
  appearance: none;
  transition:
    background-color var(--d-fast) var(--ease),
    color var(--d-fast) var(--ease),
    border-color var(--d-fast) var(--ease),
    transform var(--d-fast) var(--ease);
}
.button::after {
  content: "\2192";
  transition: transform var(--d-fast) var(--ease);
}
.button:hover {
  background: color-mix(in oklab, var(--line) 8%, var(--field));
  color: var(--line-hi);
  border-color: var(--line-hi);
}
.button:hover::after { transform: translateX(3px); }
.button:active { transform: translateY(1px); }

.button--solid {
  background: var(--line);
  color: var(--field);
  border-color: var(--line);
}
.button--solid:hover {
  background: var(--line-hi);
  border-color: var(--line-hi);
  color: var(--field);
}
.button--solid:disabled { opacity: 0.55; cursor: default; }
.button--solid:disabled:hover {
  background: var(--line);
  border-color: var(--line);
  transform: none;
}
.button--solid:disabled:hover::after { transform: none; }

/* A second bordered button next to the primary reads as two competing
   asks stacked right under a headline — the exact "won't let me just
   read" feeling a hero shouldn't have. This variant keeps the same arrow
   and click target size but drops the box, so one action leads and the
   other is available without shouting equally loud. */
.button--text {
  min-height: auto;
  padding: var(--s-2) 0;
  border-color: transparent;
  background: transparent;
  color: var(--text-dim);
}
.button--text:hover {
  background: transparent;
  border-color: transparent;
  color: var(--line);
  text-decoration: underline;
  text-underline-offset: 4px;
}
.button--text:active { transform: none; }

/* ══ REGISTER ═════════════════════════════════════════════════════ */

.register {
  margin-top: var(--s-7);
  width: min(100%, var(--maxw));
}

.register__head {
  display: grid;
  grid-template-columns: minmax(0, 18rem) minmax(0, 1fr) auto;
  gap: var(--s-5);
  align-items: baseline;
  padding-bottom: var(--s-4);
  border-bottom: var(--hair) solid var(--text);
}
.register__title {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  font-size: var(--t-h2);
  font-weight: 550;
  letter-spacing: -0.02em;
  font-variation-settings: "wdth" 98, "wght" 550;
}
.register__note { font-size: var(--t-small); color: var(--text-dim); max-width: 48ch; }
.register__count { white-space: nowrap; }

.register__list { list-style: none; margin: 0; padding: 0; }

.entry { border-bottom: var(--hair) solid var(--line-quiet); }
.entry__link {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr) 9rem;
  gap: var(--s-4);
  align-items: baseline;
  padding-block: 0.95rem;
  padding-inline: 0;
  transition:
    background-color var(--d-fast) var(--ease),
    padding-inline var(--d-base) var(--ease);
}
.entry__link:hover {
  background: var(--field-raise);
  padding-inline: var(--s-3);
}
.entry__ref { padding-top: 0.35em; }
.entry__body { display: block; }
.entry__title {
  display: block;
  font-size: 1.125rem;
  font-weight: 500;
  letter-spacing: -0.016em;
  transition: color var(--d-fast) var(--ease);
}
.entry__link:hover .entry__title { color: var(--line); }
.entry__summary {
  display: block;
  margin-top: var(--s-2);
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--text-dim);
  max-width: 62ch;
}
.entry__stack { display: block; margin-top: var(--s-3); color: var(--text-faint); }
.entry__meta { display: flex; flex-direction: column; align-items: flex-end; gap: var(--s-2); }

/* ══ PROJECT SHEET ════════════════════════════════════════════════ */

/* max-width lives on the section itself here (unlike its siblings, whose
   max-width sits on a child), so it needs its own centering margin — the
   generic `.section > *` rule only fires for children of a .section. */
.detail-head { padding-bottom: var(--s-3); max-width: var(--maxw); margin-inline: auto; }

/* The next section (.detail-drawing) already carries a full --section-y of
   its own top padding. Stacking detail-head's old --s-6 bottom padding on
   top of that left ~170px of dead air between the meta chips and "How it
   works" — the tallest gap on the page for the least reason. */
.detail-drawing { padding-top: var(--s-5) !important; }

.detail-drawing__frame {
  margin-top: var(--s-5);
  border: var(--hair) solid var(--line-quiet);
  border-radius: var(--radius);
  padding: clamp(1.75rem, 5vw, 3.75rem) clamp(1.5rem, 4vw, 3.25rem);
  background: var(--field-deep);
  width: min(100%, var(--maxw));
}

/* ── gallery ──────────────────────────────────────────────────────
   Diagram first, real screenshots after: the drawing explains the system,
   the screenshots prove it shipped. Native scroll-snap does the swiping, so
   a thumb on a phone and a trackpad on a laptop both just work and there is
   no hand-rolled drag physics to get wrong.

   The whole thing is one framed unit: a fixed-height stage on top, then a
   near-black filmstrip tray flush against its bottom edge. The tray is the
   only dark surface on this page on purpose. It reads as a contact sheet,
   and it stops sixteen screenshots reading as sixteen page elements. */

.gallery {
  /* The tray palette lives here, not in tokens.css, because it is the one
     deliberate inversion on the site and nothing else should reach for it.
     Near-black carrying a little blue (hue 255, the same family as --text)
     rather than #000: a true black against this paper reads as a hole
     punched through the page instead of as a surface sitting on it. */
  --tray:        oklch(0.165 0.016 255);
  --tray-raise:  oklch(0.255 0.018 255);
  --tray-ink:    oklch(0.68 0.02 255);
  --tray-active: oklch(0.88 0.03 255);

  /* One height for every slide, committed before a single image byte
     arrives. This is what makes cumulative layout shift zero, and it is also
     what keeps the tray from bobbing as the reader swipes from a tall phone
     capture to a wide desktop one. */
  --stage-h: clamp(15rem, 54vh, 34rem);

  position: relative;
  margin-top: var(--s-5);
  width: min(100%, var(--maxw));
  border: var(--hair) solid var(--line-quiet);
  border-radius: var(--radius);
  background: var(--field-deep);
  overflow: clip;
}

/* svh rather than vh: on iOS the URL bar collapsing changes vh mid-scroll,
   which would resize the stage under the reader's thumb while they swipe. */
@supports (height: 1svh) {
  .gallery { --stage-h: clamp(15rem, 54svh, 34rem); }
}

.gallery__stage { position: relative; }

.gallery__track {
  /* position: relative so the track, and not .gallery, is the offsetParent of
     the slides. gallery.js scrolls with track.scrollTo({ left:
     slide.offsetLeft }), and .gallery carries a border, so leaving it as the
     offsetParent would push every scroll target off by the border width and
     leave each slide sitting a pixel short of its snap point. */
  position: relative;
  display: flex;
  height: var(--stage-h);
  overflow-x: auto;
  overflow-y: hidden;
  /* Without this, overscrolling past the last slide hands the gesture to the
     browser's back-swipe and the reader leaves the page mid-carousel. */
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* No touch-action declaration on purpose. Pinning it to pan-x would claim
     the gesture from the document and make the page impossible to scroll
     vertically with a thumb resting anywhere on the gallery. */
}
.gallery__track::-webkit-scrollbar { display: none; }
.gallery__track:focus-visible { outline: 2px solid var(--line); outline-offset: -3px; }

.gallery__slide {
  flex: 0 0 100%;
  min-width: 0;
  height: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: grid;
  place-items: center;
  padding: var(--s-4);
}

.gallery__img {
  /* Deliberately no aspect-ratio. The real set runs from a 1284x2778 phone
     capture to a 1901x778 desktop strip, and forcing one ratio letterboxes
     every one of them into a compromise nobody asked for. Containing them
     inside a stage that already has its height means any ratio lands
     correctly and none of them can move the page. */
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--radius);
  background: var(--field);
  border: var(--hair) solid var(--line-quiet);
}

/* The diagram slide fills its slot edge to edge: .gallery already draws the
   frame, so the inner frame's own border would double the hairline. */
.gallery__slide--drawing { padding: 0; }
.gallery__slide--drawing .detail-drawing__frame {
  margin-top: 0;
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 3.5vw, 2.5rem);
  border: 0;
}
/* Three classes deep so this still wins if drawings.css, which loads after
   components.css, sizes .drawing from inside the frame at equal specificity.
   max-height is the load-bearing part: a wide viewBox scaled to 100% width
   would otherwise run past the bottom of a fixed-height slide and get clipped. */
.gallery__slide .detail-drawing__frame .drawing {
  width: 100%;
  height: auto;
  max-height: 100%;
}

.gallery__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  /* 44px is a floor, not a taste call: anything smaller is a miss on a touch
     screen. These are hidden under 760px anyway, where swipe is the
     affordance, but the size holds for stylus and coarse-pointer laptops. */
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: var(--field);
  border: var(--hair) solid var(--line-quiet);
  border-radius: 50%;
  color: var(--text);
  cursor: pointer;
  z-index: 2;
  transition:
    border-color var(--d-fast) var(--ease),
    background-color var(--d-fast) var(--ease),
    opacity var(--d-fast) var(--ease);
}
.gallery__nav svg { width: 11px; height: 18px; }
.gallery__nav:hover { border-color: var(--line); background: var(--field-deep); }
.gallery__nav:focus-visible { outline: 2px solid var(--line); outline-offset: 2px; }
.gallery__nav:disabled { opacity: 0.28; pointer-events: none; }
.gallery__nav--prev { left: var(--s-3); }
.gallery__nav--next { right: var(--s-3); }

.gallery__counter {
  position: absolute;
  top: var(--s-3);
  right: var(--s-3);
  z-index: 2;
  margin: 0;
  padding: 3px var(--s-2);
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  color: var(--text-dim);
  background: var(--field);
  border: var(--hair) solid var(--line-quiet);
  border-radius: var(--radius);
  /* Never intercepts a swipe that starts in the top right of the stage. */
  pointer-events: none;
}

/* Off-screen but still read aloud. Scoped to the gallery rather than added as
   a global utility, because this file owns only the gallery block. */
.gallery__srtext {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── the tray ─────────────────────────────────────────────────────
   Flush under the stage, horizontally scrollable in its own right because
   sixteen screenshots will never fit across a phone. */
.gallery__strip {
  /* offsetParent for the thumbs, for the same reason the track is one: the
     script scrolls this element by thumb.offsetLeft. */
  position: relative;
  display: flex;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-behavior: smooth;
  scrollbar-width: none;
  background: var(--tray);
}
.gallery__strip::-webkit-scrollbar { display: none; }

.gallery__thumb {
  position: relative;
  flex: 0 0 auto;
  /* Same 44px floor as the nav buttons. The visible thumbnail is the whole
     button, so the ink and the hit area are the same box and there is no gap
     between what the reader aims at and what responds. */
  width: 62px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: var(--tray-raise);
  color: var(--tray-ink);
  overflow: hidden;
  cursor: pointer;
  opacity: 0.55;
  transition: opacity var(--d-fast) var(--ease);
}
.gallery__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gallery__thumb--drawing { display: grid; place-items: center; }
.gallery__thumb svg { width: 44%; height: 44%; }
.gallery__thumb:hover { opacity: 0.88; }
.gallery__thumb[aria-current="true"] { opacity: 1; }

/* The active ring is an overlay, not a border and not an inset shadow. A
   border would resize the image inside a 62px box every time the selection
   moved, and an inset shadow paints under the img rather than over it.
   Leaving outline unused also means the focus ring can never be mistaken for
   the active marker, or vice versa. */
.gallery__thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 2px solid transparent;
  border-radius: inherit;
  pointer-events: none;
  transition: border-color var(--d-fast) var(--ease);
}
.gallery__thumb[aria-current="true"]::after { border-color: var(--tray-active); }
.gallery__thumb:focus-visible { outline: 2px solid var(--tray-active); outline-offset: 2px; }

@media (min-width: 760px) {
  .gallery__strip { gap: var(--s-3); padding: var(--s-3); }
  .gallery__thumb { width: 80px; height: 54px; }
}

/* Below 760px swipe and the tray are the affordance, so the arrows are not
   worth the 44px of stage they would sit on top of. */
@media (max-width: 759px) {
  .gallery__nav { display: none; }
}

/* .no-motion is not only the media query: base.php re-adds the class if the
   motion layer has not reported ready within 2.5s, so the reduced-motion
   media query in tokens.css alone would miss that path. */
html.no-motion .gallery__track,
html.no-motion .gallery__strip { scroll-behavior: auto; }
html.no-motion .gallery__thumb,
html.no-motion .gallery__thumb::after,
html.no-motion .gallery__nav { transition: none; }

.outcome {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--hair);
  background: var(--line-quiet);
  margin-top: var(--s-6);
  margin-inline: auto;
  border: var(--hair) solid var(--line-quiet);
  border-radius: var(--radius);
  overflow: clip;
  max-width: var(--maxw);
}
.outcome__cell { background: var(--field); padding: var(--s-4) var(--s-4) var(--s-5); }
.outcome__cell dt { margin: 0; }
.outcome__val { margin: var(--s-3) 0 0; font-size: 1.2rem; font-weight: 500; }

/* ── sheet navigation ─────────────────────────────────────────────── */

.sheetnav {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-5);
  border-top: var(--hair) solid var(--line-quiet);
  width: min(100%, var(--maxw));
  margin-inline: auto;
}
/* First and last projects have only one adjacent sheet; let it take the
   full row instead of leaving the other column dead and empty. */
.sheetnav:has(> :only-child) { grid-template-columns: 1fr; }
.sheetnav__link {
  display: block;
  padding: var(--s-5) 0;
  transition: color var(--d-fast) var(--ease);
}
.sheetnav__link--next { text-align: right; }
.sheetnav__title {
  display: block;
  margin-top: var(--s-3);
  font-size: 1.125rem;
  font-weight: 500;
  letter-spacing: -0.014em;
}
.sheetnav__link:hover .sheetnav__title {
  color: var(--line);
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* ══ PROSE ════════════════════════════════════════════════════════ */

.prose { max-width: 44rem; }
.prose p { max-width: 62ch; font-size: 1.0625rem; line-height: 1.78; }
.prose p + p { margin-top: var(--s-4); }
.prose__lead {
  font-size: clamp(1.15rem, 2vw, 1.45rem) !important;
  line-height: 1.55 !important;
  font-weight: 400;
  letter-spacing: -0.016em;
  max-width: 52ch !important;
}
.prose .label { display: block; margin-bottom: var(--s-4); }

.note-block {
  margin-top: var(--s-7);
  padding-top: var(--s-4);
  border-top: var(--hair) solid var(--line-quiet);
  max-width: 44rem;
}
.note-block__text {
  margin-top: var(--s-3);
  font-size: var(--t-small);
  line-height: 1.7;
  color: var(--text-dim);
  max-width: 62ch;
}

/* ══ COMPETENCIES ═════════════════════════════════════════════════ */

.competencies {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--s-6) var(--s-5);
  margin-top: var(--s-6);
  width: min(100%, var(--maxw));
}
.competency__title {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  padding-bottom: var(--s-3);
  border-bottom: var(--hair) solid var(--line-quiet);
  font-size: var(--t-h3);
  font-weight: 500;
}
.competency__list { list-style: none; margin: var(--s-4) 0 0; padding: 0; }
.competency__list li {
  padding-block: var(--s-2);
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--text-dim);
  border-bottom: var(--hair) solid transparent;
  transition: color var(--d-fast) var(--ease);
}
.competency__list li:hover { color: var(--text); }

/* ══ REASONS ══════════════════════════════════════════════════════ */

.reasons { list-style: none; margin: var(--s-6) 0 0; padding: 0; max-width: 58rem; }
.reason {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  gap: var(--s-4);
  padding-block: var(--s-5);
  border-top: var(--hair) solid var(--line-quiet);
}
.reason:last-child { border-bottom: var(--hair) solid var(--line-quiet); }

/* Explicit placement: three children in a two-column grid meant the text
   was auto-placed into the 3rem number column on the next row, rendering
   one word per line. Same trap as .close__inner. Whenever the child count
   and the column count disagree, place them by hand. */
.reason__num   { grid-column: 1; grid-row: 1 / span 2; padding-top: 0.4em; }
.reason__title { grid-column: 2; grid-row: 1; font-size: var(--t-h3); font-weight: 500; letter-spacing: -0.014em; }
.reason__text  { grid-column: 2; grid-row: 2; }
.reason__text {
  margin-top: var(--s-3);
  font-size: var(--t-small);
  line-height: 1.7;
  color: var(--text-dim);
  max-width: 58ch;
}

/* ══ RECORD (resume) ══════════════════════════════════════════════ */

.record { list-style: none; margin-top: var(--s-5); margin-inline: auto; padding: 0; width: min(100%, var(--maxw)); }
.record__role {
  padding-block: var(--s-6);
  border-bottom: var(--hair) solid var(--line-quiet);
}
.record__role:first-child { border-top: var(--hair) solid var(--text); }
.record__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-5);
  flex-wrap: wrap;
}
.record__title { font-size: var(--t-h3); font-weight: 600; letter-spacing: -0.016em; }
.record__org { margin-top: var(--s-2); font-size: var(--t-small); color: var(--text-dim); }
.record__when { white-space: nowrap; }
.record__lede {
  margin-top: var(--s-4);
  font-size: 1.0625rem;
  line-height: 1.7;
  max-width: 62ch;
}
.record__points { list-style: none; margin: var(--s-4) 0 0; padding: 0; }
.record__points li {
  position: relative;
  padding-left: var(--s-5);
  padding-block: var(--s-2);
  font-size: var(--t-small);
  line-height: 1.7;
  color: var(--text-dim);
  max-width: 68ch;
}
.record__points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.05em;
  width: 12px;
  height: var(--hair);
  background: var(--line);
}

/* ══ PORTRAIT ═════════════════════════════════════════════════════ */

.portrait {
  display: grid;
  grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: start;
  max-width: var(--maxw);
}
.portrait__figure {
  margin: 0;
  border: var(--hair) solid var(--line-quiet);
  border-radius: var(--radius);
  padding: var(--s-3);
  background: var(--field-deep);
}
.portrait__figure img {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  filter: saturate(0.85) contrast(1.02);
}
.portrait__line {
  margin-top: var(--s-3);
  font-size: 1.125rem;
  font-weight: 500;
  letter-spacing: -0.014em;
  max-width: none;
}
.portrait__note {
  margin-top: var(--s-2);
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--text-dim);
  max-width: 52ch;
}
.portrait__spacer { margin-top: var(--s-5); }

/* ══ ERROR PAGES ══════════════════════════════════════════════════ */

.error-page {
  min-height: calc(100svh - var(--rail-h) - 12rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s-5);
}
.error-page .display { max-width: 12ch; }
.error-page__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-top: var(--s-2);
}

/* ══ responsive ═══════════════════════════════════════════════════ */

@media (max-width: 1099px) {
  .register__head { grid-template-columns: minmax(0, 1fr) auto; }
  .register__note { grid-column: 1 / -1; order: 3; }
}

@media (max-width: 760px) {
  .entry__link { grid-template-columns: 3.25rem minmax(0, 1fr); }
  .entry__meta {
    grid-column: 2;
    flex-direction: row;
    align-items: baseline;
    gap: var(--s-3);
    margin-top: var(--s-3);
  }
  /* One column here, so the desktop placements must be released or the
     items get pushed into an implicit second column. */
  .reason { grid-template-columns: minmax(0, 1fr); gap: var(--s-3); }
  .reason__num,
  .reason__title,
  .reason__text { grid-column: 1; grid-row: auto; }
  .reason__num { padding-top: 0; }
  .sheetnav { grid-template-columns: minmax(0, 1fr); }
  .sheetnav__link--next { text-align: left; }
  .detail-drawing__frame { padding: var(--s-4) var(--s-3); }
  .portrait { grid-template-columns: minmax(0, 1fr); gap: var(--s-5); }
  .portrait__figure { max-width: 16rem; }
}

/* ══════════════════════════════════════════════════════════════════
   Principal overhaul components.

   Density note: the audit that prompted these measured 8,159px of scroll
   for 524 words on the home page, with the right half of a 1440px screen
   empty. Every component below is built to put information into that
   space rather than to fill it with air.
   ══════════════════════════════════════════════════════════════════ */

/* ── section-index ────────────────────────────────────────────────
   Quiet 01/04 counters. Gives long pages a spine to scan without adding
   another heading level. */

.section-index {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  margin-bottom: var(--s-4);
}
.section-index__num {
  font: 500 var(--t-label)/1 var(--mono);
  letter-spacing: var(--track-label);
  color: var(--line);
}
.section-index__num::after {
  content: "";
  display: inline-block;
  width: 1.75rem;
  height: var(--hair);
  margin-left: var(--s-3);
  vertical-align: middle;
  background: var(--line-quiet);
}

/* ── meta-chip ────────────────────────────────────────────────────
   Domain · org · year · status as one scan row, replacing four stacked
   definition rows that each cost a line of vertical space. */

.meta-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.meta-chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: var(--hair) solid var(--line-quiet);
  border-radius: var(--radius);
  background: var(--field-deep);
  font-size: var(--t-small);
  color: var(--text);
  white-space: nowrap;
}
.meta-chip__key {
  font: 500 var(--t-label)/1 var(--mono);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-faint);
}
.meta-chip--live { border-color: var(--line); }
.meta-chip--live .meta-chip__key { color: var(--line); }

/* ── drawing-caption ──────────────────────────────────────────────
   A diagram without a caption asks the reader to guess what it proves.
   One sentence removes the guess. */

.drawing-caption {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s-3);
  align-items: start;
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: var(--hair) solid var(--line-quiet);
}
.drawing-caption__key {
  font: 500 var(--t-label)/1.5 var(--mono);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--line);
  white-space: nowrap;
}
.drawing-caption__text {
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--text-dim);
  max-width: 68ch;
}

/* ── case-anatomy ─────────────────────────────────────────────────
   Constraint / Decision / Ownership as a three-column band on desktop.
   The previous stacked version cost three full rows for the same words;
   side by side they can also be compared, which is the point. */

.case-anatomy { margin-top: var(--s-6); }

.case-anatomy__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--hair);
  margin: var(--s-4) 0 0;
  background: var(--line-quiet);
  border: var(--hair) solid var(--line-quiet);
}
.case-anatomy__cell {
  background: var(--field);
  padding: var(--s-5) var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.case-anatomy__num {
  font: 500 var(--t-label)/1 var(--mono);
  letter-spacing: var(--track-label);
  color: var(--line);
}
.case-anatomy__key {
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: -0.014em;
  margin: 0;
}
.case-anatomy__val {
  margin: 0;
  font-size: var(--t-small);
  line-height: 1.65;
  color: var(--text-dim);
}
/* The middle cell is the decision, which is the whole reason the section
   exists, so it carries a hairline the others do not. */
.case-anatomy__cell--decision { background: var(--field-deep); }
.case-anatomy__cell--decision .case-anatomy__val { color: var(--text); }

/* ── flagship-rail ────────────────────────────────────────────────
   Jump chips above the register so a reader who only has ninety seconds
   can reach the six cases that matter without scrolling past ten. */

.flagship-rail {
  display: flex;
  gap: var(--s-2);
  flex-wrap: wrap;
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: var(--hair) solid var(--line-quiet);
}
.flagship-rail__label {
  font: 500 var(--t-label)/2.2 var(--mono);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-faint);
  margin-right: var(--s-2);
}
.flagship-rail__link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border: var(--hair) solid var(--line-quiet);
  border-radius: var(--radius);
  font-size: var(--t-small);
  color: var(--text);
  transition: border-color var(--d-fast) var(--ease), background-color var(--d-fast) var(--ease);
}
.flagship-rail__link:hover { border-color: var(--line); background: var(--field-deep); }
.flagship-rail__year {
  font: 500 var(--t-label)/1 var(--mono);
  color: var(--text-faint);
}

/* ── independent-fold ─────────────────────────────────────────────
   Range, not argument. A native <details> so it works with no JS and is
   keyboard-operable for free. */

.fold {
  margin-top: var(--s-6);
  border-top: var(--hair) solid var(--line-quiet);
}
.fold__summary {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  padding: var(--s-4) 0;
  cursor: pointer;
  list-style: none;
}
.fold__summary::-webkit-details-marker { display: none; }
.fold__summary::after {
  content: "+";
  margin-left: auto;
  font-family: var(--mono);
  color: var(--line);
}
.fold[open] .fold__summary::after { content: "\2212"; }
.fold__title { font-size: var(--t-h3); font-weight: 500; letter-spacing: -0.014em; }
.fold__note { font-size: var(--t-small); color: var(--text-dim); }
.fold__count {
  font: 500 var(--t-label)/1 var(--mono);
  letter-spacing: var(--track-label);
  color: var(--text-faint);
}

/* Two columns of compact rows: ten independent projects in the space the
   register was using for four. */
.fold__list {
  list-style: none;
  margin: 0 0 var(--s-5);
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--hair) var(--s-5);
}
.fold__row { border-top: var(--hair) solid var(--line-quiet); }
.fold__link {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr);
  gap: var(--s-3);
  padding: var(--s-3) 0;
  align-items: baseline;
}
.fold__link:hover .fold__name { color: var(--line); }
.fold__year { font: 500 var(--t-label)/1.6 var(--mono); color: var(--text-faint); }
.fold__name { font-size: var(--t-small); }
.fold__stack {
  display: block;
  margin-top: 2px;
  font: 500 var(--t-label)/1.5 var(--mono);
  color: var(--text-faint);
}

/* ── operating-list ───────────────────────────────────────────────
   Numbered principles. Two columns on desktop, because four stacked
   full-width rows is exactly the pattern that made this site feel thin. */

.operating {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-5) var(--s-6);
  margin-top: var(--s-6);
  margin-inline: auto;
  padding: 0;
  list-style: none;
  max-width: var(--maxw);
}
.operating__item {
  display: grid;
  grid-template-columns: 2.25rem minmax(0, 1fr);
  gap: var(--s-3);
  padding-top: var(--s-4);
  border-top: var(--hair) solid var(--line-quiet);
}
.operating__num {
  font: 500 var(--t-label)/1.7 var(--mono);
  letter-spacing: var(--track-label);
  color: var(--line);
}
.operating__title {
  font-size: 1.0625rem;
  font-weight: 500;
  letter-spacing: -0.014em;
  line-height: 1.35;
  margin: 0;
}
.operating__body {
  margin: var(--s-3) 0 0;
  font-size: var(--t-small);
  line-height: 1.65;
  color: var(--text-dim);
  max-width: 52ch;
}
.operating__proof {
  display: inline-block;
  margin-top: var(--s-3);
  padding: 2px var(--s-2);
  border: var(--hair) solid var(--line-quiet);
  border-radius: var(--radius);
  font: 500 var(--t-label)/1.6 var(--mono);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--line);
}

/* ── responsive ───────────────────────────────────────────────────── */

@media (max-width: 1000px) {
  .case-anatomy__grid { grid-template-columns: minmax(0, 1fr); }
  .operating { grid-template-columns: minmax(0, 1fr); gap: var(--s-5); }
  .fold__list { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 640px) {
  .meta-chip { font-size: var(--t-label); }
  .drawing-caption { grid-template-columns: minmax(0, 1fr); gap: var(--s-2); }
}

/* ══ SCOPE PANEL ══════════════════════════════════════════════════
   Organisational scope as one artifact. Every fact here was already on
   the site, buried in a resume bullet or half a sentence of prose; a CIO
   scanning for principal-level evidence should not have to assemble it. */

.scope {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--hair);
  background: var(--line-quiet);
  border: var(--hair) solid var(--line-quiet);
  margin-top: var(--s-5);
  margin-inline: auto;
  max-width: var(--maxw);
}
.scope__cell {
  background: var(--field);
  padding: var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.scope__key {
  font: 500 var(--t-label)/1 var(--mono);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0;
}
.scope__val {
  margin: 0;
  font-size: 1.0625rem;
  line-height: 1.25;
  letter-spacing: -0.012em;
  color: var(--text);
}
.scope__note {
  margin: 0;
  font-size: var(--t-label);
  line-height: 1.4;
  color: var(--line);
}

/* ══ REJECTED ═════════════════════════════════════════════════════
   The option that was killed, and why. The strongest single artifact on
   a case study: anyone can describe what they built, and describing what
   you turned down is what shows the decision was actually made. */

.rejected {
  margin-top: var(--s-6);
  border: var(--hair) solid var(--line-quiet);
  border-left: none;
  background: var(--field-deep);
  padding: var(--s-5);
  max-width: 58rem;
  position: relative;
}
/* A struck-through rule, not a coloured stripe: the mark means "this was
   crossed out", which is exactly what the section is about. */
.rejected::before {
  content: "";
  position: absolute;
  left: 0; top: var(--s-5); bottom: var(--s-5);
  width: var(--hair);
  background: var(--text-faint);
}
.rejected__label {
  font: 500 var(--t-label)/1 var(--mono);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-faint);
}
.rejected__option {
  margin: var(--s-3) 0 0;
  font-size: 1.125rem;
  line-height: 1.4;
  letter-spacing: -0.014em;
  color: var(--text);
  text-decoration: line-through;
  text-decoration-color: var(--text-faint);
  text-decoration-thickness: var(--hair);
  max-width: 52ch;
}
.rejected__why {
  margin: var(--s-4) 0 0;
  font-size: var(--t-small);
  line-height: 1.7;
  color: var(--text-dim);
  max-width: 66ch;
}

@media (max-width: 1000px) { .scope { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .scope { grid-template-columns: minmax(0, 1fr); } }

/* ══ RECOGNITION ══════════════════════════════════════════════════
   Awards, designations, and the organisations the work was delivered
   for. External validation is the one proof a portfolio cannot
   manufacture, so it is presented plainly: no badges, no ribbons, no
   trophy iconography beyond a single hairline mark. */

.recognition__list {
  list-style: none;
  margin: var(--s-5) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--hair);
  background: var(--line-quiet);
  border: var(--hair) solid var(--line-quiet);
}
.recognition__item {
  background: var(--field);
  padding: var(--s-5) var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.recognition__figure {
  margin: 0;
  border: var(--hair) solid var(--line-quiet);
  background: var(--field-deep);
  padding: var(--s-3);
}
.recognition__figure img {
  width: 100%;
  /* Certificate scans are portrait and very tall; unconstrained they made
     the image cards three times the height of the text-only one and left
     the row badly unbalanced. `contain` keeps the whole document visible,
     which matters when the image IS the evidence. */
  height: 15rem;
  object-fit: contain;
  object-position: top center;
  /* Photographs of certificates carry whatever white balance the camera
     chose. A touch of desaturation stops them fighting the one accent. */
  filter: saturate(0.85) contrast(1.02);
}

.recognition__mark { color: var(--line); }
.recognition__mark svg { width: 34px; height: 34px; }

.recognition__kind { text-transform: capitalize; }
.recognition__title {
  margin-top: var(--s-3);
  font-size: 1.0625rem;
  font-weight: 500;
  letter-spacing: -0.014em;
  line-height: 1.3;
}
.recognition__meta { margin-top: var(--s-2); font-size: var(--t-small); color: var(--text-dim); }
.recognition__note {
  margin-top: var(--s-3);
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--text-dim);
  max-width: 46ch;
}

.recognition__clients {
  margin-top: var(--s-6);
  padding-top: var(--s-4);
  border-top: var(--hair) solid var(--line-quiet);
}
.recognition__logos {
  list-style: none;
  margin: var(--s-4) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-6);
  align-items: center;
}
.recognition__logos li { display: flex; align-items: center; gap: var(--s-3); }
.recognition__logos img {
  height: 34px;
  width: auto;
  /* Client logos arrive in their own brand colours, which would be the
     loudest thing on a page built around one accent. Greyed by default,
     restored on hover for anyone who wants to check them. */
  filter: grayscale(1);
  opacity: 0.66;
  transition: filter var(--d-base) var(--ease), opacity var(--d-base) var(--ease);
}
.recognition__logos li:hover img { filter: grayscale(0); opacity: 1; }
.recognition__logoText { font-size: 1rem; }
.recognition__logoYear { color: var(--text-faint); }

@media (max-width: 560px) {
  .recognition__logos { gap: var(--s-4); }
  .recognition__logos img { height: 26px; }
}
