@charset "UTF-8";
/* Generated from scss/. Do not edit — run `npm run build:css`. */
/* Design tokens and webfaces. Everything the site draws with is declared here.
   Dark mode is a value swap on [data-theme="dark"] — no component knows about it. */
/* ---------------------------------------------------------------- webfaces */
@font-face {
  font-family: "PP Neue Montreal";
  font-weight: 300;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPNeueMontreal-Light.otf") format("opentype");
}
@font-face {
  font-family: "PP Neue Montreal";
  font-weight: 400;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPNeueMontreal-Regular.otf") format("opentype");
}
@font-face {
  font-family: "PP Neue Montreal";
  font-weight: 400;
  font-style: italic;
  font-display: block;
  src: url("../fonts/PPNeueMontreal-Italic.otf") format("opentype");
}
@font-face {
  font-family: "PP Neue Montreal";
  font-weight: 600;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPNeueMontreal-Semibold.otf") format("opentype");
}
@font-face {
  font-family: "PP Neue Montreal";
  font-weight: 700;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPNeueMontreal-Extrabold.otf") format("opentype");
}
@font-face {
  font-family: "PP Neue Montreal";
  font-weight: 800;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPNeueMontreal-Extrabold.otf") format("opentype");
}
@font-face {
  font-family: "PP Museum";
  font-weight: 300;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPMuseum-Light.otf") format("opentype");
}
@font-face {
  font-family: "PP Museum";
  font-weight: 400;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPMuseum-Regular.otf") format("opentype");
}
@font-face {
  font-family: "PP Museum";
  font-weight: 800;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPMuseum-Ultrabold.otf") format("opentype");
}
@font-face {
  font-family: "PP NeueBit";
  font-weight: 700;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPNeueBit-Bold.otf") format("opentype");
}
@font-face {
  font-family: "PP Editorial Old";
  font-weight: 200;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPEditorialOld-Ultralight.otf") format("opentype");
}
@font-face {
  font-family: "PP Editorial Old";
  font-weight: 200;
  font-style: italic;
  font-display: block;
  src: url("../fonts/PPEditorialOld-UltralightItalic.otf") format("opentype");
}
@font-face {
  font-family: "PP Editorial Old";
  font-weight: 400;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPEditorialOld-Regular.otf") format("opentype");
}
@font-face {
  font-family: "PP Editorial Old";
  font-weight: 400;
  font-style: italic;
  font-display: block;
  src: url("../fonts/PPEditorialOld-Italic.otf") format("opentype");
}
@font-face {
  font-family: "PP Editorial Old";
  font-weight: 800;
  font-style: normal;
  font-display: block;
  src: url("../fonts/PPEditorialOld-Ultrabold.otf") format("opentype");
}
@font-face {
  font-family: "PP Editorial Old";
  font-weight: 800;
  font-style: italic;
  font-display: block;
  src: url("../fonts/PPEditorialOld-UltraboldItalic.otf") format("opentype");
}
@font-face {
  font-family: "Monaspace Neon";
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
  src: url("../fonts/MonaspaceNeonVar.woff2") format("woff2-variations");
}
@font-face {
  font-family: "Monaspace Neon";
  font-weight: 400;
  font-style: italic;
  font-display: swap;
  src: url("../fonts/MonaspaceNeon-Italic.woff2") format("woff2");
}
/* ------------------------------------------------------------------ tokens */
:root {
  /* Surfaces and ink. Every one of these flips in dark mode. */
  --paper: #f4f4f4;
  --bg2: #eaeae9;
  --bg4: #a1a19e; /* the echo, the part of a title that runs past its row */
  --bg-inv: #2e2d2c;
  --ink: #171716;
  --ink2: #454442;
  --mut: #5c5b59;
  --faint: #a1a19e;
  --hair: #e2e2e1;
  --red: #e75800;
  --duo-shadow: #7f2600;
  --duo-highlight: #e75800;
  /* The ink that sits on the duotone wash. The wash is identical in both themes,
     so this does not flip with them. White is the only value that holds across
     the whole map: against the shadow end it reads 9.6:1 and against the
     highlight 3.6:1, where a dark ink manages 1.7:1 over the shadows and
     disappears into exactly the part of a photograph there is most of. */
  --duo-ink: #ffffff;
  /* The plate that holds a mark rather than a photograph. A mark was drawn for a
     light ground, so its plate keeps one in both themes: one value, no dark
     override. Nothing is drawn on it — the label sits under the plate, on the
     page ground, where --mut is legible in both themes. */
  --plate-ground: #f4f4f4;
  /* Type scale. Fluid between a 320px and a 1560px viewport. */
  --size-xs: clamp(0.6914rem, 0.6899rem + 0.0065vw, 0.6944rem);
  --size-sm: clamp(0.7778rem, 0.7522rem + 0.1163vw, 0.8333rem);
  --size-base: clamp(0.875rem, 0.8174rem + 0.2618vw, 1rem);
  --size-lg: clamp(0.9844rem, 0.885rem + 0.4516vw, 1.2rem);
  --size-xl: clamp(1.1074rem, 0.9542rem + 0.6965vw, 1.44rem);
  --size-4xl: clamp(1.5768rem, 1.1568rem + 1.909vw, 2.4883rem);
  --size-5xl: clamp(1.7739rem, 1.2154rem + 2.5384vw, 2.986rem);
  --leading-tight: 1.2;
  --leading-normal: 1.5;
  --leading-responsive: calc(1em + 0.5rem);
  --leading-display: calc(1em + 0.1rem);
  --track-tight: -0.025em;
  /* Space scale, same fluid ramp. */
  --space-4xs: clamp(0.125rem, 0.125rem + 0vw, 0.125rem);
  --space-3xs: clamp(0.25rem, 0.25rem + 0vw, 0.25rem);
  --space-2xs: clamp(0.4375rem, 0.4087rem + 0.1309vw, 0.5rem);
  --space-xs: clamp(0.6875rem, 0.6587rem + 0.1309vw, 0.75rem);
  --space-s: clamp(0.875rem, 0.8174rem + 0.2618vw, 1rem);
  --space-m: clamp(1.3125rem, 1.2261rem + 0.3927vw, 1.5rem);
  --space-l: clamp(1.75rem, 1.6348rem + 0.5236vw, 2rem);
  --space-xl: clamp(2.625rem, 2.4522rem + 0.7853vw, 3rem);
  --space-2xl: clamp(3.5rem, 3.2696rem + 1.0471vw, 4rem);
  --space-3xl: clamp(5.25rem, 4.9045rem + 1.5707vw, 6rem);
  --space-xl-2xl: clamp(2.625rem, 1.9915rem + 2.8796vw, 4rem);
  /* The measure. Content stops at 1560px and the surplus becomes the gutter. */
  --measure: 1560px;
  --gutter: max(var(--space-m), calc((100% - var(--measure)) / 2));
  /* Breakpoints, emitted from the map in scss/_breakpoints.scss so that
     js/site.js reads exactly the widths the media queries were compiled with.
     See window.SITE.media(). */
  --bp-narrow: 860px;
  --bp-stack: 560px;
  /* The narrowest a colophon column may get before the grid drops a track. */
  --col-min: 22rem;
  /* Face stacks. The display faces are picked per entry by js/fit.js. */
  --font-sans: "PP Neue Montreal", Helvetica, Arial, sans-serif;
  --font-serif: "PP Editorial Old", Georgia, serif;
  --font-mono: "Monaspace Neon", ui-monospace, SFMono-Regular, Menlo, monospace;
  /* The five-track grid the index rows and the colophon rows both sit on.
     The floor is wrapped in min(…, 100%) so it cannot exceed the container: a bare
     fixed floor keeps applying when the container is narrower than it, and the
     track overflows instead of shrinking. auto-fit hides this at a default root
     size by dropping tracks, but 10rem is 10rem — at a large user font-size on a
     narrow viewport a single track is already wider than the page. */
  --track: minmax(min(max(10rem, calc((100% - 4 * var(--space-m)) / 5)), 100%), 1fr);
}

/* Tell the UA which way the page is set. The theme is a data-theme swap, so the
   browser has no other way to know: without this the scrollbar, the overscroll
   canvas behind body and any UA control stay in light chrome against a #20201f
   page — a white gutter flashing on every rubber-band scroll. */
:root {
  color-scheme: light;
}

[data-theme=dark] {
  color-scheme: dark;
}

[data-theme=dark] {
  --paper: #20201f;
  --bg2: #282726;
  --bg4: #5b5a57;
  --bg-inv: #e2e1e0;
  --ink: #ffffff;
  --ink2: #bfbebd;
  --mut: #9f9f9c;
  --faint: #5b5a57;
  --hair: #2e2d2c;
  /* Light ink on a dark ground bleeds optically, so the accent steps down rather
     than the weight — a static family has no grade axis, and this costs no reflow. */
  --red: #e15400;
}

/* Paper is paper. Browsers do not print background colours by default, so a page
   printed while the dark theme is on sent white ink to a sheet that never received
   its dark ground: the titles landed at about 2.3:1 on white and the page came out
   all but blank. Print always uses the light values, whatever the screen is set to. */
@media print {
  :root,
  [data-theme=dark] {
    color-scheme: light;
    --paper: #ffffff;
    --bg2: #eaeae9;
    --bg4: #a1a19e;
    --bg-inv: #2e2d2c;
    --ink: #171716;
    --ink2: #454442;
    --mut: #5c5b59;
    --faint: #a1a19e;
    --hair: #e2e2e1;
    --red: #e75800;
  }
}
/* Reset, document defaults, and the rules that apply to every page. */
*, *::before, *::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--size-base);
  -webkit-font-smoothing: antialiased;
}

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

::selection {
  background: var(--red);
  color: var(--paper);
}

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

img {
  max-width: 100%;
  display: block;
}

/* Present to the accessibility tree, absent from the page. For the one heading a
   page needs but does not draw — the index names itself in the masthead and in the
   nav, and setting that word a third time in the page body would be noise. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Skipped past by everything except the keyboard. */
.skip {
  position: absolute;
  left: var(--space-m);
  top: var(--space-m);
  z-index: 100;
  padding: var(--space-2xs) var(--space-s);
  background: var(--paper);
  border: 1px solid var(--red);
  color: var(--red);
  transform: translateY(-200%);
}

.skip:focus {
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
/* The apparatus: every label, caption, number and control on the site. It is the
   machinery around the work — never the work itself, which is why it is all one
   mono face at one size, and why it is the only thing that ever sets in accent. */
.mono {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: var(--size-xs);
  letter-spacing: 0;
  font-feature-settings: "calt" 1, "liga" 1, "dlig" 1, "ss01" 1, "ss02" 1, "ss03" 1, "ss04" 1, "ss05" 1, "ss06" 1, "ss07" 1, "ss08" 1;
  font-variant-ligatures: common-ligatures discretionary-ligatures contextual;
}

.caps {
  text-transform: uppercase;
}

.btn {
  appearance: none;
  border: 1px solid var(--hair);
  background: transparent;
  color: var(--mut);
  font: inherit;
  letter-spacing: 0;
  text-transform: uppercase;
  padding: var(--space-3xs) var(--space-2xs);
  cursor: pointer;
}

.btn:hover {
  border-color: var(--red);
  color: var(--red);
}

.link:hover {
  color: var(--red);
}

/* The theme switch names the theme it would take you to — "Dark" while light,
   "Light" while dark. Both labels are in the markup and CSS picks one, so the
   right word is up at first paint with no flash and no work for JS. */
[data-theme=dark] [data-if-light] {
  display: none;
}

html:not([data-theme=dark]) [data-if-dark] {
  display: none;
}

/* The persistent chrome on every page: the sticky header and the footer. */
.masthead {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  background: var(--paper);
}

/* Name left, links right, and they only break to a second line when they would
   genuinely collide — flex decides that from the actual text, so there is no
   breakpoint to keep in sync with the length of the nav. */
.masthead__inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--space-2xs) var(--space-l);
  padding: var(--space-m) var(--gutter) var(--space-2xs);
}

.masthead__name {
  font-weight: 700;
  font-size: var(--size-xl);
  line-height: var(--leading-responsive);
  letter-spacing: var(--track-tight);
}

.masthead__nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-s);
  color: var(--mut);
}

.masthead__nav [aria-current=page] {
  color: var(--red);
}

.masthead__rule {
  height: 1px;
  background: var(--hair);
}

@media print {
  .masthead {
    display: none;
  }
}
/* --------------------------------------------------------------------- footer */
.footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-s) var(--space-l);
  padding: var(--space-l) var(--gutter) var(--space-2xl);
  border-top: 1px solid var(--hair);
  color: var(--mut);
}

/* Opt this document into transitioning on same-origin navigation. Both the page
   being left and the page being entered have to carry this, which is why it sits
   in a shared partial rather than a page one. */
@view-transition {
  navigation: auto;
}
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 500ms;
  animation-timing-function: ease-in-out;
}

/* The masthead and footer are deliberately NOT given a view-transition-name.
 *
 * Naming them holds them still through the fade, which looks right in Chrome —
 * but in WebKit a named `position: sticky` element paints as a blank gap for the
 * whole transition, so the header simply vanishes and comes back. Verified in
 * WebKit 26.5: with the masthead named it is empty mid-transition; with the name
 * removed it renders correctly and the crossfade still runs.
 *
 * Nothing is lost by dropping it. The chrome is identical on both pages, so
 * cross-fading it against itself is invisible — the only part that differs is
 * which nav item is marked current, and fading that is the nicer result anyway.
 */
@media (prefers-reduced-motion: reduce) {
  /* Swap outright rather than cross-fade. The navigation still works; it simply
     does not animate. */
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}
/* The index: one row per entry, each one a title set to fill its column. */
.row {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 15rem;
  gap: var(--space-xl);
  border-bottom: 1px solid var(--hair);
  padding: var(--space-m) var(--gutter);
  transition: padding 160ms ease;
  cursor: pointer;
}

/* --------------------------------------------------------- the apparatus line */
.row__meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, var(--track));
  column-gap: var(--space-m);
  row-gap: var(--space-2xs);
  position: relative;
  z-index: 3;
  align-items: start;
  color: var(--mut);
}

/* Every entry is misregistered by a fixed amount derived from its own number.
   It never changes and it never animates — see entry No. 001. The transform is
   written into the markup at generate time. */
.row__num {
  color: var(--red);
  font-weight: 400;
}

.row__excerpt {
  grid-column: 2/-2;
  /* Fill each column to the full 6em before starting the next, rather than
     balancing — the excerpt should read as a column of text that ran out of room,
     not as three short stacks. */
  column-fill: auto;
  column-gap: var(--space-m);
  height: 6em;
  text-align: left;
  text-transform: none;
  color: var(--red);
  pointer-events: none;
}

/* An entry with no note behind it yet says so, quietly — the slot stays the same
   height so the apparatus line does not shift from row to row. */
.row__excerpt--empty {
  color: var(--faint);
  text-transform: uppercase;
}

.row__client {
  grid-column: -2/-1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3xs);
  color: var(--red);
}

.row__client svg {
  display: block;
  flex: none;
}

/* The page footer draws the rule that closes the sequence. */
.row:last-child {
  border-bottom: none;
}

/* ------------------------------------------------------------------ the title */
.row__body {
  position: relative;
  z-index: 2;
  flex: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: end;
  gap: var(--space-l);
}

/* js/fit.js solves a size for the title so its longest line sits flush to both
   edges of this box, then trims the line box to cap height and baseline so every
   face lands on the same optical grid. The values below are only what is drawn
   before the solver runs. */
.fit {
  position: relative;
  overflow: visible;
  text-align: left;
}

.fit__text,
.fit__echo {
  display: inline-block;
  white-space: pre;
  font-feature-settings: "liga" 1, "dlig" 1, "ss01" 1, "case" 1;
  font-variant-ligatures: common-ligatures discretionary-ligatures;
}

.fit__text {
  /* An h2, so its UA margins have to go: the solver overwrites margin-top and
     margin-bottom with the optical trim, but only once it has run, and the
     unsolved first paint would otherwise carry 0.83em above and below. */
  margin: 0;
  font-size: 68px;
  line-height: 1;
  letter-spacing: -0.02em;
  font-weight: 400;
}

/* The second ink. A copy of the title clipped to only what falls past the row's
   bottom rule, so descenders print into the next entry. */
.fit__echo {
  position: absolute;
  left: 0;
  top: 0;
  color: var(--bg4);
  pointer-events: none;
}

/* ---------------------------------------------------------------- the imagery */
/* background-image is written inline rather than passed as a custom property:
   Chrome resolves a relative url() inside var() against the stylesheet that uses
   it, not the one that declares it, which would make every path relative to css/. */
.row__wash {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  transition: opacity 160ms linear;
  pointer-events: none;
  background: var(--duo-shadow);
  overflow: hidden;
}

.row__wash > div {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  filter: url(#duo);
}

/* Hovering brings the photograph up under the whole row, so the type switches to
   the duotone ink and the rule beneath the row gets out of the way. That ink is
   the same in both themes because the wash is. */
.row:hover .row__wash {
  opacity: 1;
}

.row:hover {
  color: var(--duo-ink);
  border-bottom-color: transparent;
}

.row:hover .row__meta,
.row:hover .row__num,
.row:hover .row__excerpt,
.row:hover .row__client {
  color: var(--duo-ink);
}

.row:hover .fit__echo {
  color: var(--red);
}

/* ----------------------------------------------------------------- narrow view */
@media (max-width: 860px) {
  .row {
    min-height: 0;
    gap: var(--space-l);
  }
  /* Stacked, the apparatus line stops being an apparatus line: the excerpt and the
     place mark can no longer sit beside the number, so they pile up above the title
     and push it down the screen. The row keeps its number and its title, which is
     what the index is for, and the entry page carries the rest. */
  .row__excerpt,
  .row__client {
    display: none;
  }
}
/* The wash is a hover affordance, and on a touch pointer it can never be shown:
   :hover does not fire and a tap navigates. `opacity: 0` keeps the element in the
   render tree, so the photograph is still fetched — 1.2 MB of it, 38% of the index,
   downloaded by a phone to paint nothing. `display: none` is what stops the fetch. */
@media (hover: none) {
  .row__wash {
    display: none;
  }
}
@media print {
  .row__wash {
    display: none;
  }
  .row {
    break-inside: avoid;
  }
}
