/* ==========================================================================
   STYLE - layout and components.

   RULE: no raw values in this file. Colors, fonts, sizes, and spacing all
   come from tokens.css via var(). If you find yourself typing a hex code
   or a px font-size here, add a token instead.

   The spine of the layout is .row: a fixed left rail carrying the section
   number, next to a 12-column content field. Every section on every page
   is built from it, which is what makes the grid feel like a system rather
   than a decoration.
   ========================================================================== */

/* --- Reset ---------------------------------------------------------------- */

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

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchor targets clear the sticky nav instead of hiding behind it. */
  scroll-padding-top: calc(var(--nav-height) + var(--space-lg));
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--step-0);
  font-weight: var(--weight-body);
  line-height: var(--leading-normal);
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, picture, svg, iframe, video, audio { display: block; max-width: 100%; }

input, button, textarea, select { font: inherit; color: inherit; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

p { text-wrap: pretty; }

a { color: var(--accent); text-decoration-thickness: var(--hairline); text-underline-offset: 0.18em; }

ul, ol { padding: 0; list-style: none; }

/* Visible keyboard focus everywhere. Do not remove. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* Screen-reader-only text (skip link, icon labels). */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  top: var(--space-xs);
  left: var(--space-xs);
  z-index: 100;
  padding: var(--space-xs) var(--space-md);
  background: var(--accent);
  color: var(--accent-contrast);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  text-decoration: none;
  transform: translateY(-250%);
}
.skip-link:focus { transform: translateY(0); }

/* --- Layout primitives ----------------------------------------------------
   .shell   outer width cap and page gutters
   .row     rail + content field
   .field   12-column subgrid inside the row
   -------------------------------------------------------------------------- */

.shell {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.row {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  column-gap: var(--grid-gap);
  /* The rail number sits on the first baseline of whatever heading opens
     the field, at every heading size, instead of on a fixed nudge. */
  align-items: baseline;
}

/* The section number. Structural, not decorative: it is the row's address
   in the grid, which is why no section also carries a separate label. */
.row__rail {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  color: var(--accent);
  line-height: var(--leading-normal);
}

.field {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  row-gap: var(--space-lg);
}

/* Common spans. Prose stays inside --measure regardless of its span. */
.span-full  { grid-column: 1 / -1; }
.span-prose { grid-column: 1 / span 8; max-width: var(--measure); }
.span-half  { grid-column: span 6; }

/* Offsets. The rail never moves; variance happens inside the field, so a
   page can break its own left edge without breaking the grid. All of these
   collapse to the full field at the same width .span-prose does. */
.span-7  { grid-column: 1 / span 7; }
.start-7 { grid-column: 7 / -1; }
.start-9 { grid-column: 9 / -1; }

.prose { max-width: var(--measure); }

/* --- Type helpers --------------------------------------------------------- */

.mono {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  font-weight: var(--weight-medium);
}

.lede {
  font-size: var(--step-1);
  line-height: var(--leading-snug);
  color: var(--muted);
  max-width: var(--measure);
}

/* Italic emphasis inside display type. The extra leading and the reserve
   below keep descenders in y, g, j, p, q from being clipped. */
em, .em {
  font-style: italic;
  line-height: 1.1;
  padding-bottom: 0.06em;
}

/* Underline that grows from the left. The only motion on a text link. */
.link {
  display: inline-block;
  color: var(--fg);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  padding-bottom: var(--space-2xs);
  position: relative;
}
.link::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: var(--hairline);
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--transition-hover);
}
.link:hover,
.link:focus-visible { color: var(--accent); }
.link:hover::after,
.link:focus-visible::after { transform: scaleX(1); }

.link--accent { color: var(--accent); }
.link--accent::after { transform: scaleX(1); }
.link--accent:hover::after { transform: scaleX(0); }

/* --- Navigation -----------------------------------------------------------
   One line at desktop, 64px tall. Collapses to a mono MENU button at 767px.
   -------------------------------------------------------------------------- */

.site-nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
  border-bottom: var(--hairline) solid var(--rule);
}

.site-nav__inner {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  min-height: var(--nav-height);
}

.site-nav__brand {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--fg);
  text-decoration: none;
  margin-right: auto;
  white-space: nowrap;
}

.site-nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
}

.site-nav__link {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  padding-block: var(--space-2xs);
  border-bottom: var(--hairline) solid transparent;
}

.site-nav__link:hover,
.site-nav__link:focus-visible { color: var(--fg); }

.site-nav__link[aria-current="page"],
.site-nav__link.is-active {
  color: var(--fg);
  border-bottom-color: var(--accent);
}

/* Theme switch. Text, not an icon: it stays on-brand with the mono system
   and never becomes a mystery glyph. */
.theme-toggle {
  display: flex;
  align-items: stretch;
  background: none;
  border: var(--hairline) solid var(--rule);
  padding: 0;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.theme-toggle__opt {
  padding: var(--space-2xs) var(--space-xs);
  color: var(--muted);
}

:root:not([data-theme="dark"]) .theme-toggle__opt[data-mode="light"],
:root[data-theme="light"] .theme-toggle__opt[data-mode="light"] {
  color: var(--accent-contrast);
  background: var(--accent);
}

:root[data-theme="dark"] .theme-toggle__opt[data-mode="dark"] {
  color: var(--accent-contrast);
  background: var(--accent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle__opt[data-mode="light"] {
    color: var(--muted);
    background: none;
  }
  :root:not([data-theme="light"]) .theme-toggle__opt[data-mode="dark"] {
    color: var(--accent-contrast);
    background: var(--accent);
  }
}

.site-nav__toggle {
  display: none;
  background: none;
  border: var(--hairline) solid var(--rule);
  padding: var(--space-2xs) var(--space-xs);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--fg);
}

/* --- Reading progress -----------------------------------------------------
   Scroll-driven CSS animation, no scroll listener anywhere. Where the
   browser does not support scroll timelines it simply is not painted.
   -------------------------------------------------------------------------- */

.progress {
  position: fixed;
  top: var(--nav-height);
  left: 0;
  z-index: 49;
  width: 100%;
  height: var(--hairline);
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  display: none;
}

@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .progress {
      display: block;
      animation: progress-grow linear both;
      animation-timeline: scroll(root block);
    }
  }
}

@keyframes progress-grow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* --- Hero -----------------------------------------------------------------
   Asymmetric and bottom-anchored, like the title page of a bound document.
   Never centered. min-height uses dvh so the iOS address bar cannot make
   the layout jump.
   -------------------------------------------------------------------------- */

.hero {
  /* Deliberately short of a full viewport. The top of the contents table
     breaking the fold is what tells a reader in the first second that there
     are four bodies of work here, not one. A full-height hero would bury
     that and leave a void above the wordmark that reads as a layout bug. */
  min-height: min(72dvh, 40rem);
  display: grid;
  align-content: end;
  padding-top: var(--space-2xl);
  padding-bottom: var(--space-2xl);
  border-bottom: var(--hairline) solid var(--rule);
}

.hero__name {
  font-size: var(--step-5);
  line-height: var(--leading-tight);
  text-wrap: balance;
}

.hero__statement {
  font-size: var(--step-1);
  line-height: var(--leading-snug);
  color: var(--muted);
  max-width: 44ch;
  margin-bottom: var(--space-xl);
}

/* The wordmark spans the field; the statement sits on the opposite
   diagonal, under its right half. */
.hero__aside { align-self: end; }

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xl);
}

/* --- Section shell -------------------------------------------------------- */

.section { padding-block: var(--space-3xl); }
.section + .section { border-top: var(--hairline) solid var(--rule); }

.section__title {
  font-size: var(--step-3);
  margin-bottom: var(--space-sm);
}

.section__lede {
  color: var(--muted);
  max-width: var(--measure);
  margin-bottom: var(--space-2xl);
}

.section__more { margin-top: var(--space-xl); }

/* Page headers on the four section pages. */
.page-head { padding-block: var(--space-2xl) var(--space-xl); }
.page-head__title { font-size: var(--step-4); margin-bottom: var(--space-sm); }

/* A page header and the content under it are one unit, so the section that
   follows does not also open with a full section's worth of top padding. */
.page-head + .section { padding-top: var(--space-xl); }

/* --- 00 Index -------------------------------------------------------------
   The contents table. A real table is the right answer exactly once on this
   site, and this is it.
   -------------------------------------------------------------------------- */

.index { width: 100%; border-collapse: collapse; }

.index th {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  text-align: left;
  padding-bottom: var(--space-xs);
  border-bottom: var(--hairline) solid var(--rule-strong);
}

.index th:last-child, .index td:last-child { text-align: right; }

.index td {
  padding-block: var(--space-md);
  border-bottom: var(--hairline) solid var(--rule);
  vertical-align: baseline;
}

.index tbody tr:hover { background: var(--scrim); }

.index__num, .index__count, .index__years {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--muted);
  width: 1%;
  white-space: nowrap;
}

.index__num { color: var(--accent); padding-right: var(--space-lg); }
.index__count, .index__years { padding-left: var(--space-lg); }

.index__name a {
  font-family: var(--font-display);
  font-size: var(--step-1);
  color: var(--fg);
  text-decoration: none;
}
.index__name a:hover { color: var(--accent); }

/* --- 01 Music: hairline track rows ---------------------------------------- */

.track-list { display: flex; flex-direction: column; }

.track {
  padding-block: var(--space-xl);
  border-bottom: var(--hairline) solid var(--rule);
}
.track:first-child { padding-top: 0; }

.track__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
}

.track__title { font-size: var(--step-1); }

.track__meta {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}

.track__note { color: var(--muted); max-width: var(--measure); }

.track__embed { margin-top: var(--space-lg); }

/* SoundCloud iframes and the local player share one container treatment so
   both sources read as a single system. */
.track__embed iframe {
  width: 100%;
  border: var(--hairline) solid var(--rule);
}

/* --- Universal player -------------------------------------------------------
   One bar for the whole page, built once by main.js, not one widget per
   release. It docks to the bottom of the viewport the first time anything
   is asked to play, and stays there: the same transport drives a local
   <audio> element for shipped files and the SoundCloud Widget API for
   anything that still only lives there, so nothing on the page ever shows
   a bare <audio> control or a raw SoundCloud iframe. Same vocabulary as the
   rest of the site: mono numerals, a hairline scrub with an accent fill, no
   filled background track behind it.
   -------------------------------------------------------------------------- */

.player-bar {
  position: fixed;
  left: 0;
  bottom: 0;
  z-index: 60;
  width: 100%;
  background: var(--bg);
  border-top: var(--hairline) solid var(--rule-strong);
  transform: translateY(100%);
  transition: transform var(--transition-slow);
}

.player-bar.is-open { transform: translateY(0); }

.player-bar__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
  min-height: var(--player-bar-height);
  padding-block: var(--space-sm);
}

.player-bar__button {
  background: none;
  border: var(--hairline) solid var(--rule-strong);
  padding: var(--space-2xs) var(--space-sm);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--fg);
}
.player-bar__button:hover,
.player-bar__button:focus-visible { color: var(--accent); border-color: var(--accent); }
.player-bar__button:active { transform: translateY(1px); }

.player-bar__toggle { min-width: 5rem; }

.player-bar__meta {
  min-width: 0;
  flex: 1 1 10rem;
}

.player-bar__title {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.player-bar__release {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.player-bar__scrub {
  flex: 1 1 10rem;
  position: relative;
  height: var(--space-md);
  cursor: pointer;
  min-width: 4rem;
}

.player-bar__scrub::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 50%;
  height: var(--hairline);
  background: var(--rule-strong);
}

.player-bar__fill {
  position: absolute;
  inset-inline-start: 0;
  top: 50%;
  height: var(--hairline);
  width: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

.player-bar__time {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.player-bar__hide {
  background: none;
  border: 0;
  padding: var(--space-2xs);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}
.player-bar__hide:hover,
.player-bar__hide:focus-visible { color: var(--accent); }

/* Minimized: same slide-down as fully closed, but body.has-player is off
   and the bar is not actually torn down, so whatever is playing keeps
   playing behind it. Declared after .is-open so it wins the tie when both
   classes are present. */
.player-bar.is-minimized { transform: translateY(100%); }

/* The reopen tab. Exists in the DOM only once something has actually been
   cued (see js/main.js), and even then only shown while the bar itself is
   minimized. */
.player-tab {
  position: fixed;
  right: var(--space-lg);
  bottom: var(--space-lg);
  z-index: 60;
  max-width: 16rem;
  background: var(--bg);
  border: var(--hairline) solid var(--rule-strong);
  padding: var(--space-xs) var(--space-md);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  transform: translateY(0.5rem);
  transition: opacity var(--transition), transform var(--transition);
}
.player-tab.is-visible { opacity: 1; transform: none; }
.player-tab:hover,
.player-tab:focus-visible { color: var(--accent); border-color: var(--accent); }

/* The SoundCloud widget iframe that drives playback when a track has no
   local file yet. It never needs to be seen: the bar above is the only UI.
   Same hiding technique as .visually-hidden, so it stays in the layout
   (unlike display: none, which some browsers use to justify suspending an
   iframe's media) without ever being visible. */
.player-bar__sc-frame {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
}

body.has-player { padding-bottom: var(--player-bar-height); }

/* --- Lightbox ---------------------------------------------------------------
   Click any image in the page to see it full size. Built by main.js, so
   with JavaScript off an image is exactly what it always was: no dead
   zoom cursor, no dead control.
   -------------------------------------------------------------------------- */

.js main img { cursor: zoom-in; }

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-xl);
  background: var(--overlay-scrim);
  opacity: 0;
  transition: opacity var(--transition-slow);
}

.lightbox[hidden] { display: none; }

.lightbox.is-open { opacity: 1; }

.lightbox img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  border: var(--hairline) solid var(--rule-strong);
  cursor: default;
  transform: scale(0.96);
  transition: transform var(--transition-slow);
}

.lightbox.is-open img { transform: scale(1); }

.lightbox__close {
  position: absolute;
  top: var(--space-lg);
  right: var(--space-lg);
  background: none;
  border: var(--hairline) solid var(--overlay-fg);
  padding: var(--space-2xs) var(--space-md);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--overlay-fg);
  transition: opacity var(--transition);
}
.lightbox__close:hover,
.lightbox__close:focus-visible { opacity: 0.7; }

/* --- 01 Music: tapes --------------------------------------------------------
   A release is a cover, a metadata row, a tracklist, and a transport. The
   earlier tape uses the same block at reduced weight, so the two read as
   one system and the difference between them is left to the work itself.
   -------------------------------------------------------------------------- */

.tape + .tape {
  margin-top: var(--space-3xl);
  padding-top: var(--space-3xl);
  border-top: var(--hairline) solid var(--rule);
}

.tape__head {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  gap: var(--space-xl);
  align-items: start;
  margin-bottom: var(--space-xl);
}

.tape__cover {
  border: var(--hairline) solid var(--rule);
  overflow: hidden;
}

.tape__cover img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

.tape__title { font-size: var(--step-2); margin-bottom: var(--space-xs); }

.tape__meta {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--space-md);
}

/* His own words, quoted from the release. */
.tape__note {
  color: var(--muted);
  font-style: italic;
  line-height: 1.4;
  padding-bottom: 0.08em;
  max-width: 42ch;
  margin-bottom: var(--space-lg);
}

/* The earlier tape sits at reduced weight without changing shape. */
.tape--earlier .tape__head { grid-template-columns: minmax(0, 10rem) minmax(0, 1fr); }

/* A release with no cover gives its whole row to the text. */
.tape__head--nocover { grid-template-columns: minmax(0, 1fr); }
.tape--earlier .tape__head--nocover { grid-template-columns: minmax(0, 1fr); }
.tape--earlier .tape__title { font-size: var(--step-1); }

/* --- Tracklist --------------------------------------------------------------
   Numbers and runtimes are tabular so the right column stays a true column.
   The playing track fills its number square rather than adding a separate
   icon: it reports real state, which is the only thing such a marker earns.
   -------------------------------------------------------------------------- */

.tracklist { border-top: var(--hairline) solid var(--rule-strong); }

.tracklist__row {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--space-md);
  padding-block: var(--space-sm);
  border-bottom: var(--hairline) solid var(--rule);
}

.tracklist__row:hover { background: var(--scrim); }
.tracklist__row.is-current { background: var(--scrim); }

/* Only once JavaScript has actually wired the row up to play something:
   without it the row is not a control, only the plain SoundCloud link
   inside it is, and a pointer cursor over the whole row would lie about
   that. */
.js .tracklist__row { cursor: pointer; }

.tracklist__num,
.tracklist__time {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.tracklist__num {
  color: var(--accent);
  position: relative;
}

/* A filled square behind the number of the track that is actually playing. */
.tracklist__row.is-playing .tracklist__num {
  color: var(--accent-contrast);
}
.tracklist__row.is-playing .tracklist__num::before {
  content: "";
  position: absolute;
  inset: -0.2em -0.3em;
  z-index: -1;
  background: var(--accent);
}

.tracklist__title {
  font-family: var(--font-display);
  font-size: var(--step-0);
  color: var(--fg);
  text-decoration: none;
}

.tracklist__title:hover,
.tracklist__title:focus-visible { color: var(--accent); }

button.tracklist__title {
  background: none;
  border: 0;
  padding: 0;
  text-align: left;
  cursor: pointer;
}

.tracklist__total {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr) auto;
  gap: var(--space-md);
  padding-top: var(--space-sm);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* --- Click-to-load embed -----------------------------------------------------
   A SoundCloud iframe is a heavy third-party embed, so the page ships a
   button instead and mounts the iframe only when someone asks for it. Until
   then this site makes no third-party requests at all. Without JavaScript
   the button is never shown and the plain link beside it does the job.
   -------------------------------------------------------------------------- */

.embed { margin-top: var(--space-lg); }

.embed__button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  background: none;
  border: var(--hairline) solid var(--rule-strong);
  padding: var(--space-xs) var(--space-md);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--fg);
}

.embed__button:hover,
.embed__button:focus-visible { color: var(--accent); border-color: var(--accent); }
.embed__button:active { transform: translateY(1px); }

.embed__hint {
  display: block;
  margin-top: var(--space-xs);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}

.embed__frame { border: var(--hairline) solid var(--rule); }
.embed__frame iframe { width: 100%; display: block; border: 0; }

/* --- Unwritten ----------------------------------------------------------------
   A deliberately unfinished slot. It is styled to look unfinished so that
   nothing written in a borrowed voice can quietly ship in its place.
   -------------------------------------------------------------------------- */

.todo {
  border: var(--hairline) dashed var(--rule-strong);
  padding: var(--space-md);
  max-width: var(--measure);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}

/* --- 02 Writing: editorial feature ---------------------------------------- */

.feature { margin-bottom: 0; }

/* Excerpt on the left, pull quote in the right columns and dropped below
   the excerpt's first line, so the two blocks have staggered edges. */
.feature--split {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  align-items: start;
}
.feature--split > * { grid-column: 1 / span 7; }
.feature--split > .pullquote {
  grid-column: 8 / -1;
  grid-row: 3 / span 2;
  margin-block: var(--drop) 0;
}

.feature__title {
  font-size: var(--step-2);
  margin-bottom: var(--space-sm);
}

.feature__meta {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--space-xl);
}

.feature__excerpt {
  font-size: var(--step-0);
  line-height: var(--leading-read);
  max-width: var(--measure);
}
.feature__excerpt p + p { margin-top: var(--space-md); }

.pullquote {
  font-size: var(--step-2);
  font-style: italic;
  line-height: 1.3;
  padding-bottom: 0.08em;
  color: var(--fg);
  border-left: var(--hairline) solid var(--accent);
  padding-left: var(--space-lg);
  margin-block: var(--space-xl);
  max-width: 34ch;
}

/* Compact list of the remaining pieces. Sparse dividers, one per row, and
   the row itself is the link target. */
.piece-list { display: flex; flex-direction: column; }

.piece {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  align-items: baseline;
  gap: var(--space-xs) var(--grid-gap);
  padding-block: var(--space-lg);
  border-top: var(--hairline) solid var(--rule);
  text-decoration: none;
  color: inherit;
}
.piece:hover { background: var(--scrim); }
.piece:last-child { border-bottom: var(--hairline) solid var(--rule); }

.piece__title {
  grid-column: 1 / span 7;
  font-family: var(--font-display);
  font-size: var(--step-2);
  color: var(--fg);
}
.piece:hover .piece__title { color: var(--accent); }

.piece__meta {
  grid-column: 8 / -1;
  justify-self: end;
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}

.piece__excerpt {
  grid-column: 3 / span 7;
  color: var(--muted);
  max-width: var(--measure);
  margin-top: var(--space-2xs);
}

/* --- Verse ----------------------------------------------------------------
   For work where the line breaks are the author's, not the browser's. The
   measure is narrower than prose so lines are far less likely to wrap, and
   a wrapped line is indented so it reads as a continuation rather than as a
   new line of verse.
   -------------------------------------------------------------------------- */

.verse {
  max-width: 54ch;
  font-size: var(--step-0);
  line-height: 1.75;
}

.verse__stanza { margin-bottom: var(--space-lg); }
.verse__stanza:last-child { margin-bottom: 0; }

/* One block per line, so a line that is too long for the measure wraps with
   a hanging indent and reads as a continuation instead of as a new line. */
.verse__line {
  display: block;
  padding-left: 1.5em;
  text-indent: -1.5em;
  text-wrap: initial;
}

/* A numbered poem inside a selection. */
.poem + .poem {
  margin-top: var(--space-3xl);
  padding-top: var(--space-2xl);
  border-top: var(--hairline) solid var(--rule);
}

.poem__num {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  color: var(--accent);
  margin-bottom: var(--space-md);
}

/* Attribution under a title, for work written after someone else's. */
.essay__after {
  font-style: italic;
  color: var(--muted);
  padding-bottom: 0.08em;
  margin-top: calc(var(--space-sm) * -1);
  margin-bottom: var(--space-sm);
}

/* --- 03 Photography: justified contact sheet ------------------------------
   Each figure declares its own --ar (width / height). flex-basis and
   flex-grow are both proportional to --ar, so every image in a row ends up
   the same height and the row edges stay flush. The ::after spacer stops
   the final row from stretching to fill.
   -------------------------------------------------------------------------- */

.sheet {
  --sheet-row: 15rem;
  display: flex;
  flex-wrap: wrap;
  gap: var(--grid-gap);
}

/* A single roll. Every frame shares one ratio, so this drops the justified
   flex behaviour for a plain two-column grid: with a uniform set the ::after
   spacer joins the final row and leaves it short of the others. Two across,
   not four, because these are the only photographs on the site and a 250px
   thumbnail of a dark frame shows nothing. */
.sheet--roll {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.sheet--roll::after { content: none; }
.sheet--roll .sheet__item { flex: none; }

/* An editorial sequence. Every frame on the roll is about 1.46:1, so
   changing the span changes the scale without cropping anything. Repeats
   every five: one across the field, a 7 + 5 pair, a 5 + 7 pair, with the
   narrower frame of each pair dropped so the edges stagger. */
.sheet--editorial {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--space-xl) var(--grid-gap);
  align-items: start;
}
.sheet--editorial::after { content: none; }
.sheet--editorial .sheet__item { flex: none; grid-column: span 6; }
.sheet--editorial .sheet__item:nth-child(5n + 1) { grid-column: 1 / -1; }
.sheet--editorial .sheet__item:nth-child(5n + 2) { grid-column: 1 / span 7; }
.sheet--editorial .sheet__item:nth-child(5n + 3) { grid-column: 8 / -1; margin-top: var(--drop); }
.sheet--editorial .sheet__item:nth-child(5n + 4) { grid-column: 1 / span 5; margin-top: var(--drop); }
.sheet--editorial .sheet__item:nth-child(5n + 5) { grid-column: 6 / -1; }
/* A lone frame closing a short run is set in from both edges. */
.sheet--editorial .sheet__item:nth-child(5n + 4):last-child { grid-column: 3 / span 7; }

.sheet::after {
  content: "";
  flex-grow: 1000000;
  flex-basis: 0;
}

.sheet__item {
  flex-grow: calc(var(--ar) * 100);
  flex-basis: calc(var(--ar) * var(--sheet-row));
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.frame {
  overflow: hidden;
  background: var(--surface);
  border: var(--hairline) solid var(--rule);
}

.frame img,
.frame video {
  width: 100%;
  height: auto;
  aspect-ratio: var(--ar);
  object-fit: cover;
  transition: transform var(--transition-slow);
}

.sheet__item:hover .frame img,
.sheet__item:focus-within .frame img { transform: scale(1.02); }

.sheet__item figcaption {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}

/* --- 04 Misc: grouped chunks ----------------------------------------------
   Items are clustered into two or three groups. Only the group gets a
   divider, never every individual row.
   -------------------------------------------------------------------------- */

.chunks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: var(--space-2xl) var(--space-2xl);
}

.chunk { border-top: var(--hairline) solid var(--rule-strong); padding-top: var(--space-md); }

.chunk__label {
  font-size: var(--step-2);
  color: var(--fg);
  margin-bottom: var(--space-lg);
}

.chunk__list { display: flex; flex-direction: column; gap: var(--space-lg); }

.entry__title { font-size: var(--step-1); margin-bottom: var(--space-2xs); }

.entry__meta {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--muted);
  margin-bottom: var(--space-2xs);
}

.entry__body { color: var(--muted); }

/* --- 04 Coding: an expandable timeline --------------------------------------
   Native <details>, so the collapse and expand exist without JavaScript:
   the row reads as a single line, title and language and year, until asked
   to open. Screenshot and description are laid out only inside the open
   state, so a closed page is a fast, plain-text scan.
   -------------------------------------------------------------------------- */

.timeline {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
}

.entry--timeline { border-top: var(--hairline) solid var(--rule); }
.chunk__list .entry--timeline:last-child { border-bottom: var(--hairline) solid var(--rule); }

.entry__summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  padding-block: var(--space-sm);
  cursor: pointer;
  list-style: none;
}
.entry__summary::-webkit-details-marker { display: none; }

.entry__summary:hover,
.entry__summary:focus-visible { background: var(--scrim); }

.entry__summary-main {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  min-width: 0;
}

.entry__year {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--accent);
  white-space: nowrap;
}

.entry__summary .entry__title {
  font-size: var(--step-0);
  margin-bottom: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.entry__summary-aux {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  flex-shrink: 0;
}

.entry__lang {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.entry__marker {
  font-family: var(--font-mono);
  color: var(--muted);
  transition: transform var(--transition);
}
.entry--timeline[open] .entry__marker { transform: rotate(45deg); }

/* Screenshot across seven parts, caption in the remaining four, sitting on
   the image's bottom edge. */
.entry__detail {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
  align-items: end;
  gap: var(--space-md) var(--grid-gap);
  padding-bottom: var(--space-lg);
}

/* --- Essay reading page ---------------------------------------------------- */

.essay { padding-block: var(--space-2xl) var(--space-3xl); }

.essay__title {
  font-size: var(--step-4);
  margin-bottom: var(--space-sm);
  max-width: 22ch;
}

.essay__meta {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: var(--space-xl);
  border-bottom: var(--hairline) solid var(--rule);
  margin-bottom: var(--space-2xl);
}

.essay .essay__body { grid-column: 3 / -1; }

.essay__body {
  max-width: var(--measure);
  font-size: var(--step-0);
  line-height: var(--leading-read);
}

.essay__body p + p { margin-top: var(--space-md); }

.essay__body h2 {
  font-size: var(--step-1);
  margin-top: var(--space-2xl);
  margin-bottom: var(--space-sm);
}

.essay__body blockquote {
  border-left: var(--hairline) solid var(--accent);
  padding-left: var(--space-lg);
  margin-block: var(--space-xl);
  font-style: italic;
  line-height: 1.5;
  padding-bottom: 0.08em;
  color: var(--muted);
}

.essay__foot {
  margin-top: var(--space-3xl);
  padding-top: var(--space-lg);
  border-top: var(--hairline) solid var(--rule);
}

/* --- About page ------------------------------------------------------------ */

.portrait { border: var(--hairline) solid var(--rule); overflow: hidden; }
.portrait img { width: 100%; height: auto; aspect-ratio: 4 / 5; object-fit: cover; }

.about__body { max-width: var(--measure); line-height: var(--leading-read); }
.about__body p + p { margin-top: var(--space-md); }
.about__aside { grid-row: 1; }
.about__aside .contact { flex-direction: column; gap: var(--space-lg); }
.about__aside .link { overflow-wrap: anywhere; }

/* --- Contact --------------------------------------------------------------- */

.contact { display: flex; flex-wrap: wrap; gap: var(--space-xl); }

.contact__item { display: flex; flex-direction: column; gap: var(--space-2xs); }

.contact__label {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}

/* --- Footer ---------------------------------------------------------------- */

.site-footer {
  border-top: var(--hairline) solid var(--rule);
  padding-block: var(--space-2xl);
  color: var(--muted);
}

.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
  align-items: baseline;
  justify-content: space-between;
}

.site-footer__links { display: flex; flex-wrap: wrap; gap: var(--space-lg); }

.site-footer p {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* --- Motion (MOTION_INTENSITY 4) -------------------------------------------
   Reveal on scroll, driven by IntersectionObserver in main.js. The hidden
   start state is scoped to .js so that content is never invisible when
   JavaScript does not run.
   -------------------------------------------------------------------------- */

.js .reveal {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity var(--reveal-duration) var(--ease-out),
    transform var(--reveal-duration) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--reveal-stagger));
  will-change: opacity, transform;
}

.js .reveal.is-visible {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* --- Responsive ------------------------------------------------------------ */

@media (max-width: 900px) {
  .span-prose { grid-column: 1 / -1; }
  .span-half { grid-column: 1 / -1; }
  .span-7, .start-7, .start-9 { grid-column: 1 / -1; }
  .about__aside { grid-row: auto; }
  .essay .essay__body { grid-column: 1 / -1; }
  .feature--split > *,
  .feature--split > .pullquote { grid-column: 1 / -1; grid-row: auto; }
  .feature--split > .pullquote { margin-block: var(--space-xl); }
  .piece__title, .piece__meta, .piece__excerpt { grid-column: 1 / -1; }
  .piece__meta { justify-self: start; text-align: left; }

  /* Five uppercase mono links plus the wordmark and the theme switch stop
     fitting on one line here, so the list becomes a menu rather than
     wrapping to a second row.

     The order has to go on the <nav>, not on the list inside it: <nav> is
     the flex item. And the row-gap has to be zero, otherwise the closed
     menu's empty second row still pushes the bar taller. The open menu
     supplies its own padding instead. */
  .site-nav__inner { flex-wrap: wrap; row-gap: 0; }
  .theme-toggle { order: 2; }
  .site-nav__toggle { display: block; order: 3; }

  .site-nav__inner > nav { order: 4; width: 100%; }

  .site-nav__list {
    display: none;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-md);
    width: 100%;
    padding-block: var(--space-md);
    border-top: var(--hairline) solid var(--rule);
  }
  .site-nav__list[data-open="true"] { display: flex; }
}

@media (max-width: 767px) {
  /* The rail folds above its content instead of reserving a column. */
  .row { grid-template-columns: minmax(0, 1fr); row-gap: var(--space-sm); }

  .hero { min-height: min(60dvh, 30rem); }

  .tape__head,
  .tape--earlier .tape__head { grid-template-columns: minmax(0, 1fr); gap: var(--space-lg); }
  .tape__cover { max-width: 15rem; }

  .entry__detail { grid-template-columns: minmax(0, 1fr); }
  .entry__shot { max-width: 20rem; }
  .entry__summary { flex-wrap: wrap; row-gap: var(--space-2xs); }

  .tracklist__row,
  .tracklist__total { grid-template-columns: 2rem minmax(0, 1fr) auto; gap: var(--space-sm); }

  .player-bar__inner { gap: var(--space-sm); padding-block: var(--space-xs); }
  .player-bar__release { display: none; }
  .player-bar__scrub { flex-basis: 100%; order: 5; }
  .player-bar__time { order: 6; }

  .sheet { --sheet-row: 9rem; }
  .sheet--roll { grid-template-columns: minmax(0, 1fr); }
  /* Doubled :nth-child(n) only to outrank the lone-frame rule above. */
  .sheet--editorial .sheet__item:nth-child(n):nth-child(n) { grid-column: 1 / -1; margin-top: 0; }

  .index__years,
  .index th:last-child { display: none; }
  /* With Years gone, Works is the closing column and aligns like one. */
  .index th:nth-child(3),
  .index__count { text-align: right; }

  .piece { grid-template-columns: minmax(0, 1fr); }
  .piece__meta { white-space: normal; }

  .hero__actions { gap: var(--space-lg); }
}

/* --- Motion preferences ----------------------------------------------------
   Everything above collapses to static. Non-negotiable.
   -------------------------------------------------------------------------- */

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

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .js .reveal { opacity: 1; transform: none; }
  .progress { display: none; }
}

/* --- Print -----------------------------------------------------------------
   An essay printed from this site should read like a manuscript.
   -------------------------------------------------------------------------- */

@media print {
  .site-nav, .site-footer, .progress, .essay__foot, .player-bar, .player-tab { display: none; }
  body { background: var(--print-bg); color: var(--print-fg); font-size: var(--print-size); }
  .row { grid-template-columns: minmax(0, 1fr); }
  .essay__body { max-width: none; }
}
