@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/InterVariable-latin-v1.woff2") format("woff2");
}

/* Newsreader, one static cut: the display optical size at weight 320, subset to the
   same ranges as Inter. It sets the name and the project title and nothing else. */
@font-face {
  font-family: "Newsreader Display";
  font-style: normal;
  font-weight: 320;
  font-display: swap;
  src: url("/assets/fonts/Newsreader-Display-320-latin-v1.woff2") format("woff2");
}

* { box-sizing: border-box; }

:root {
  color-scheme: dark;
  --background: #02070d;
  --foreground: rgba(244, 249, 255, 0.94);
  --text: rgba(244, 249, 255, 0.68);
  /* muted carries the eyebrows at 11px, the figure labels at 12px and the notes
     at 13px. At 0.42 it composited to 3.87:1 against the background, under the
     4.5:1 that small text needs; 0.50 is 5.08:1. */
  --muted: rgba(244, 249, 255, 0.5);
  --line: rgba(244, 249, 255, 0.12);
  --accent: #75aadf;
  --font: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --display: "Newsreader Display", ui-serif, Georgia, "Times New Roman", serif;
}

html {
  background: var(--background);
  scroll-behavior: smooth;
}

body {
  min-width: 320px;
  min-height: 100vh;
  margin: 0;
  overflow-x: hidden;
  color: var(--foreground);
  background: var(--background);
  font-family: var(--font);
  font-feature-settings: "cv01", "ss03";
  font-synthesis: none;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a {
  color: inherit;
  text-decoration-color: transparent;
  text-underline-offset: 0.22em;
  transition: color 160ms ease, text-decoration-color 160ms ease;
}

a:hover {
  color: var(--foreground);
  text-decoration-color: currentColor;
}

a:focus-visible {
  border-radius: 2px;
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

/* The photograph and the canvas that turns it into its contour lines share one
   box, and landscape.js reads the photo's object-position so the two stay
   registered at any window shape. The box is sized to the large viewport so it
   does not resize, and re-crop, when a mobile address bar comes and goes. */
.landscape,
.wash {
  position: fixed;
  inset: 0 0 auto 0;
  height: 100lvh;
  pointer-events: none;
}

.landscape {
  z-index: 0;
  overflow: hidden;
  contain: strict;
  background: var(--background);
}

.landscape-photo,
.landscape-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* A veil over the sky where the hero's type sits, measured rather than guessed:
   without it the affiliations and the links read at 2.6 to 3.0:1 against the
   lightest sky under their glyphs at 1440 x 900. It is fixed to the window, not
   to the photograph, so it holds when a wide window crops the sky away, and it
   lies over the canvas as well as the photograph: under it, it vanished as the
   canvas took the sky over at dusk, just as the Belt of Venus rose behind the
   links. */
.landscape::after {
  position: absolute;
  inset: 0;
  z-index: 3;
  background:
    linear-gradient(180deg, rgba(2, 7, 13, 0.5) 0%, rgba(2, 7, 13, 0.42) 30%, rgba(2, 7, 13, 0.18) 48%, rgba(2, 7, 13, 0) 60%),
    linear-gradient(90deg, rgba(2, 7, 13, 0.3) 0%, rgba(2, 7, 13, 0.16) 40%, rgba(2, 7, 13, 0) 62%);
  content: "";
}

.landscape-canvas {
  z-index: 2;
}

.landscape-photo {
  object-fit: cover;
  /* the summit sits at 56.8% of the frame's width; the bottom edge is kept, so a
     wide window loses sky rather than the mountain */
  object-position: 56.8% 100%;
}

/* the portrait crop has the summit at 49% of its own width */
@media (max-aspect-ratio: 3/4) {
  .landscape-photo {
    object-position: 50% 100%;
  }
}

/* a flag on the summit: a hairline standing on the point landscape.js places,
   and the name to its right, so it stays clear of the reading column. It comes
   up only when the reader opens the picture's caption, and sits on a dark
   backing, since over the day sky, just above the horizon, no bare ink held
   4.5:1 at every size. Stacked layouts fade it as the copy arrives; landscape.js
   sets how far in --entered. */
.summit {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 4;
  margin: 0;
  padding: 4px 8px;
  border-radius: 4px;
  background: rgba(2, 7, 13, 0.66);
  opacity: 0;
  visibility: hidden;
  color: rgba(244, 249, 255, 0.92);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  line-height: 13px;
  text-transform: uppercase;
  white-space: nowrap;
  transform: translate(9px, -38px);
  transition: opacity 0.35s ease;
}

.summit span {
  color: rgba(244, 249, 255, 0.72);
  font-variant-numeric: tabular-nums;
}

.summit::after {
  position: absolute;
  left: -9px;
  top: 10px;
  width: 1px;
  height: 24px;
  background: rgba(244, 249, 255, 0.55);
  content: "";
}

.summit.placed {
  visibility: visible;
}

body:has(.sources[open]) .summit.placed {
  opacity: calc(1 - var(--entered, 0));
}

/* Guards the reading column once the sections arrive over the finished contour
   drawing. It rises as the first section's top edge crosses the window, from
   80svh to 105svh of scroll, where scroll timelines exist, and is simply on where
   they do not, which costs the photograph a little of its left. Rising over
   45svh, it left the first label entering a phone at 4.1:1 over the flow. */
.wash {
  z-index: 1;
  background: linear-gradient(90deg, rgba(2, 7, 13, 0.86) 0%, rgba(2, 7, 13, 0.7) 30%, rgba(2, 7, 13, 0) 58%);
}

@supports (animation-timeline: scroll()) {
  .wash {
    animation: wash-in linear both;
    animation-timeline: scroll(root);
    animation-range: 80svh 105svh;
  }

  @keyframes wash-in {
    from { opacity: 0; }
    to { opacity: 1; }
  }
}

/* Without the landscape, with no WebGL or before its first frame, nothing turns
   the photograph dark as the copy crosses it, so the wash does it alone: over the
   whole window, from the first scroll. Measured without it, the phone's contact
   row read at 3.2:1 over the snow and the project paragraph at 3.5:1. It is
   invisible at the top of the page, so the canvas taking over is not a flash. */
.landscape:not(.landscape-ready) ~ .wash {
  background: rgba(2, 7, 13, 0.8);
}

@supports (animation-timeline: scroll()) {
  .landscape:not(.landscape-ready) ~ .wash {
    animation-range: 5vh 45vh;
  }
}

.altitude {
  position: fixed;
  right: calc(clamp(24px, 6vw, 80px) + 40px);
  bottom: 35px;
  z-index: 3;
  margin: 0;
  color: var(--muted);
  font-size: 11px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.17em;
  opacity: 0;
  text-transform: uppercase;
}

.page {
  position: relative;
  z-index: 2;
  width: min(100%, 1120px);
  margin: 0 auto;
  padding: 0 clamp(24px, 6vw, 80px);
}

.hero {
  position: relative;
  min-height: 100svh;
  padding: clamp(88px, 15vh, 168px) 0 96px;
}

h1 {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(56px, 7.6vw, 112px);
  font-weight: 320;
  letter-spacing: -0.018em;
  line-height: 0.95;
}

.role {
  max-width: 640px;
  margin: 26px 0 0;
  color: rgba(244, 249, 255, 0.84);
  font-size: clamp(20px, 2vw, 27px);
  font-weight: 380;
  letter-spacing: -0.024em;
  line-height: 1.3;
  text-wrap: balance;
}

.affiliations {
  margin: 18px 0 0;
  color: rgba(244, 249, 255, 0.82);
  font-size: clamp(14px, 1.2vw, 16px);
  letter-spacing: -0.005em;
}

.affiliations span {
  padding: 0 0.5em;
  color: rgba(244, 249, 255, 0.62);
}

.affiliations a {
  text-decoration: underline;
  text-decoration-color: rgba(244, 249, 255, 0.3);
  white-space: nowrap;
}

.primary-links {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 26px;
  margin-top: 30px;
}

.primary-links a {
  color: rgba(244, 249, 255, 0.9);
  font-size: 15px;
  font-weight: 520;
  text-decoration: underline;
  text-decoration-color: rgba(244, 249, 255, 0.32);
}

.section {
  max-width: 560px;
  padding: clamp(56px, 9vh, 96px) 0;
}

/* The climb gets an open stretch of its own between the hero and the first
   section, so no text crosses the photograph while it turns: landscape.js ends
   the sweep as this section's top edge enters the window. Run under the copy,
   the paragraph read at 3.2:1 where it passed over the unturned snow. */
.hero + .section {
  margin-top: 80svh;
}

/* Where the column runs the width of the window, over the mountain, the wash
   runs down the window rather than across it. The horizontal one ended at 58% of
   the width while a portrait tablet's column reached 79%, and the column's last
   lines read at 4.4:1 over the drawing there. */
@media (max-width: 719px), (orientation: portrait) {
  .wash {
    background: linear-gradient(180deg, rgba(2, 7, 13, 0.62), rgba(2, 7, 13, 0.86) 55%, rgba(2, 7, 13, 0.92));
  }
}

/* an h2 that looks like a label: the sections need a real heading so the page
   has an outline for screen readers and reader mode, and the type is unchanged */
.eyebrow {
  margin: 0 0 32px;
  color: var(--muted);
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.17em;
  text-transform: uppercase;
}

.project-title {
  display: inline-flex;
  align-items: flex-start;
  gap: 16px;
  text-decoration: none;
}

.project-title h3 {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(32px, 3.4vw, 44px);
  font-weight: 320;
  letter-spacing: -0.012em;
  line-height: 1.08;
}

.project-title span {
  margin-top: 0.2em;
  color: var(--muted);
  font-size: 18px;
}

.project > p {
  margin: 20px 0 0;
  color: var(--text);
  font-size: clamp(16px, 1.4vw, 18px);
  line-height: 1.6;
}

/* the last section: room after it for the night the reader has scrolled into */
.interests {
  padding-bottom: clamp(120px, 34vh, 360px);
}

.interests > p:last-child {
  margin: 0;
  color: rgba(244, 249, 255, 0.8);
  font-size: clamp(20px, 1.9vw, 25px);
  font-weight: 360;
  letter-spacing: -0.024em;
  line-height: 1.42;
}

/* The picture's caption and credits: a small i at the window's corner, as a
   map's attribution is, that opens a card above itself. A <details>, so it opens
   and closes with no script, from the keyboard, and without WebGL. */
.sources {
  position: fixed;
  right: clamp(24px, 6vw, 80px);
  bottom: 28px;
  z-index: 5;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.4s ease, visibility 0s linear 0.4s;
}

/* it waits while the photograph is on its own, and comes up once the reader
   scrolls on and the picture starts to turn; open, it stays */
.sources.shown {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.4s ease, visibility 0s;
}

.sources summary {
  display: grid;
  width: 26px;
  height: 26px;
  place-items: center;
  border: 1px solid rgba(244, 249, 255, 0.34);
  border-radius: 50%;
  background: rgba(2, 7, 13, 0.5);
  color: rgba(244, 249, 255, 0.84);
  cursor: pointer;
  font-family: var(--display);
  font-size: 15px;
  font-style: italic;
  line-height: 1;
  list-style: none;
  transition: border-color 0.2s ease, color 0.2s ease;
}

.sources summary::-webkit-details-marker {
  display: none;
}

.sources summary:hover,
.sources[open] summary {
  border-color: rgba(244, 249, 255, 0.7);
  color: #fff;
}

.sources summary:focus-visible {
  outline: 2px solid rgba(117, 170, 223, 0.9);
  outline-offset: 3px;
}

.sources-panel {
  position: absolute;
  right: 0;
  bottom: 38px;
  width: min(380px, calc(100vw - 2 * clamp(24px, 6vw, 80px)));
  padding: 16px 18px;
  border: 1px solid rgba(244, 249, 255, 0.12);
  border-radius: 8px;
  /* nearly opaque, and blurred behind: on a phone it opens over the contact rows,
     which showed through a lighter card as a second line of text */
  background: rgba(2, 7, 13, 0.94);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: rgba(244, 249, 255, 0.74);
  font-size: 12px;
  line-height: 1.55;
}

.sources-panel p {
  margin: 0;
}

.sources-panel p + p {
  margin-top: 8px;
}

/* /tribe. Same column, same tokens, no landscape: the canvas belongs to the
   homepage, and a research page should not pay for WebGL to look related. */

/* the column itself is .page, unchanged, so this page's left edge lands exactly
   where the homepage's does on any viewport. Only the measures inside differ. */
.paper {
  padding-bottom: clamp(72px, 10vw, 112px);
}

/* the homepage's sections are narrower and unruled; these keep the measures
   /tribe had under site-v3.css */
.paper .section {
  max-width: none;
  padding: clamp(56px, 8vw, 88px) 0;
  border-top: 1px solid var(--line);
}

.paper-head {
  padding: clamp(56px, 11vh, 104px) 0 clamp(40px, 6vw, 60px);
}

.back {
  display: inline-block;
  margin-bottom: clamp(40px, 7vh, 72px);
  color: var(--muted);
  font-size: 13px;
  font-weight: 520;
  letter-spacing: 0.01em;
  text-decoration: underline;
  text-decoration-color: rgba(244, 249, 255, 0.18);
}

.paper h1 {
  font-family: var(--font);
  font-size: clamp(38px, 5.4vw, 62px);
  font-weight: 470;
  letter-spacing: -0.058em;
  line-height: 1.02;
}

/* "brain-response" is the one compound here, and left alone the line broke at
   its hyphen, which reads as two words */
.nowrap {
  white-space: nowrap;
}

.paper-sub {
  max-width: 760px;
  margin: 22px 0 0;
  color: var(--text);
  font-size: clamp(20px, 2.4vw, 27px);
  font-weight: 350;
  letter-spacing: -0.032em;
  line-height: 1.22;
  text-wrap: balance;
}

.paper-byline {
  margin: 30px 0 0;
  color: var(--muted);
  font-size: clamp(13px, 1.3vw, 15px);
  line-height: 1.6;
}

.paper .section p {
  max-width: 680px;
  margin: 0 0 1.15em;
  color: var(--text);
  font-size: clamp(16px, 1.6vw, 18px);
  line-height: 1.62;
}

.paper .section p:last-child {
  margin-bottom: 0;
}

.paper .section p.paper-lede {
  max-width: 700px;
  color: rgba(244, 249, 255, 0.86);
  font-size: clamp(19px, 2.1vw, 23px);
  font-weight: 360;
  letter-spacing: -0.026em;
  line-height: 1.42;
}

.paper .section p.paper-note {
  color: var(--muted);
  font-size: clamp(13px, 1.35vw, 15px);
  line-height: 1.58;
}

/* inline links in running text need the affordance the nav links get: the
   contact address had none and read as plain grey type */
.paper .section p a {
  color: rgba(244, 249, 255, 0.86);
  text-decoration: underline;
  text-decoration-color: rgba(244, 249, 255, 0.24);
}

.figures {
  display: flex;
  flex-wrap: wrap;
  gap: 28px 56px;
  margin: 32px 0;
}

.figures dt {
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.figures dd {
  margin: 8px 0 0;
  font-size: clamp(30px, 3.6vw, 40px);
  font-weight: 430;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.045em;
  line-height: 1;
}

.figures .tribe { color: var(--accent); }
.figures .input { color: rgba(244, 249, 255, 0.5); }
.figures .delta { color: var(--foreground); }

.paper-link {
  font-size: clamp(17px, 1.8vw, 20px);
  font-weight: 480;
  letter-spacing: -0.02em;
  text-decoration: underline;
  text-decoration-color: rgba(117, 170, 223, 0.5);
}

@media (max-width: 720px) {
  .page {
    padding-inline: 22px;
  }

  /* the homepage h1 rule scales with the viewport for a two-word name; this
     title is five words, so it needs its own ceiling on small screens */
  .paper h1 {
    font-size: clamp(31px, 8.6vw, 40px);
  }

  .paper-sub {
    font-size: clamp(19px, 5.4vw, 23px);
  }

  .paper-head {
    padding-top: 56px;
  }

  .figures {
    gap: 24px 40px;
  }

  /* the name in the sky and the contact row on the dark valley floor, with the
     mountain between: stacked at the top, the links ran down over the summit on
     a short phone and read at 2.4:1 at 320 x 568 */
  .hero {
    display: flex;
    flex-direction: column;
    padding: 72px 0 36px;
  }

  .hero .affiliations {
    margin-top: auto;
  }

  /* A scrim that travels with the contact rows. Scrolling up, they cross the
     massif's snow just as the alpenglow holds it brightest, and read at 1.8:1
     there without it; fixed to the rows rather than the window, it darkens only
     what is behind the words. It fades out below them as well: ending at full
     strength, its lower edge drew a hard line across the mountain as it rose. */
  .hero::before {
    position: absolute;
    left: -22px;
    right: -22px;
    bottom: -160px;
    z-index: -1;
    height: 440px;
    background: linear-gradient(180deg, rgba(2, 7, 13, 0) 0%, rgba(2, 7, 13, 0.52) 22.9%, rgba(2, 7, 13, 0.66) 63.6%,
      rgba(2, 7, 13, 0.56) 72%, rgba(2, 7, 13, 0.33) 82%, rgba(2, 7, 13, 0.12) 92%, rgba(2, 7, 13, 0) 100%);
    content: "";
    pointer-events: none;
  }

  h1 {
    font-size: clamp(46px, 14vw, 64px);
  }

  .role {
    margin-top: 20px;
    font-size: clamp(19px, 5.4vw, 23px);
  }

  .affiliations {
    max-width: 310px;
    line-height: 1.55;
  }

  /* the two affiliations stack: wrapped, the separator hung at a line's end */
  .affiliations span[aria-hidden="true"] {
    display: none;
  }

  .affiliations a {
    display: block;
    width: fit-content;
  }

  .primary-links {
    margin-top: 26px;
  }

  .section,
  .paper .section {
    padding: 54px 0 58px;
  }

  .eyebrow {
    margin-bottom: 26px;
  }

  .project-title {
    gap: 12px;
  }

  .project-title span {
    font-size: 16px;
  }

  .altitude {
    right: 62px;
    bottom: 27px;
  }

  .sources {
    right: 22px;
    bottom: 20px;
  }

  .sources-panel {
    width: calc(100vw - 44px);
  }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  a { transition: none; }
}

@media print {
  :root { color-scheme: light; }
  body { color: #111; background: #fff; }
  /* the credits are the landscape's, and the landscape is for the screen */
  .landscape, .wash, .altitude, .sources { display: none; }
  .role, .affiliations, .affiliations span, .eyebrow, .project > p, .project-title span,
  .summit, .summit span, .sources summary, .sources[open] summary, .sources-panel { color: #444; }
  .interests > p:last-child, .primary-links a { color: #111; }
  .interests { padding-bottom: 0; }
  .hero { min-height: auto; padding-top: 0; }
  .hero::before { display: none; }
  /* the climb's open stretch is for the screen; on paper it was most of a page */
  .hero + .section { margin-top: 0; }

  /* /tribe names a colour on every element it owns, and each of those rules
     outranks the body rule above, so none of them inherited the print colour:
     the page printed as its title over four blank sections. It is the page the
     poster's QR code points at, so it is the one people will print.
     The selectors below repeat the originals so they win on document order. */
  .paper-sub, .paper .section p { color: #222; }
  .paper .section p.paper-lede { color: #111; }
  .paper-byline, .paper .section p.paper-note, .figures dt { color: #444; }
  .back, .paper-link, .paper .section p a { color: #111; }
  .figures dd, .figures .tribe, .figures .delta { color: #111; }
  .figures .input { color: #555; }
  .paper-head { padding-top: 0; }
  .back { margin-bottom: 28px; }

  /* the rules that vanish on white: hairlines and underlines are all drawn in a
     near-white tint of the foreground */
  .paper .section { border-top-color: rgba(0, 0, 0, 0.2); }
  .primary-links a, .affiliations a, .back, .paper-link,
  .paper .section p a { text-decoration-color: rgba(0, 0, 0, 0.4); }
}
