/* ---------------------------------------------------------------
   Tokens
   --------------------------------------------------------------- */
:root {
  /* Palette — light */
  --bg: #f8f4ee;
  --paper: #fffdf9;
  --ink: #2b2620;
  --muted: #8a8077;
  --line: #e8e0d6;
  --accent: #b85c38;
  --shadow: 40 30 20;

  /* Derived */
  --ink-soft: color-mix(in srgb, var(--ink) 86%, var(--bg));
  --accent-wash: color-mix(in srgb, var(--accent) 7%, transparent);
  --accent-line: color-mix(in srgb, var(--accent) 55%, var(--line));

  /* Type */
  --font-head: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
  --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --size-display: clamp(2.4rem, 6.2vw, 4rem);
  --size-page-title: clamp(2.1rem, 5.4vw, 3.1rem);
  --size-title: clamp(1.35rem, 3vw, 1.6rem);
  --size-body: 17px;
  --size-small: 15px;
  --size-label: 10.5px;
  --measure: 62ch;
  --measure-tight: 54ch;

  /* Space */
  --wrap: 840px;
  --wrap-read: 720px;
  --pad-x: clamp(22px, 5vw, 64px);
  --pad-y: clamp(22px, 4.5vw, 46px);
  --gap-section: clamp(52px, 9vw, 96px);
  --gap-block: clamp(26px, 4vw, 38px);

  --radius: 12px;
  --radius-lg: 18px;
  --radius-pill: 999px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #15120e;
    --paper: #1e1a15;
    --ink: #ece6dd;
    --muted: #988d80;
    --line: #2c2620;
    --accent: #db8a5d;
    --shadow: 0 0 0;
  }
}

/* ---------------------------------------------------------------
   Base
   --------------------------------------------------------------- */
* { box-sizing: border-box; }

html, body { margin: 0; padding: 0; }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: 1.65;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

img { max-width: 100%; }

::selection { background: color-mix(in srgb, var(--accent) 22%, transparent); }

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

.wrap {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: var(--pad-y) var(--pad-x) 64px;
}

.wrap--read { max-width: var(--wrap-read); }

/* Small uppercase labels — body font, tracked. Never mono. */
.label {
  font-weight: 500;
  font-size: var(--size-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
}

.label--accent { color: var(--accent); }

.section-label {
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.link-underline {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
  transition: text-decoration-color 0.18s ease;
}

.link-underline:hover { text-decoration-color: var(--accent); }

/* ---------------------------------------------------------------
   Header / footer
   --------------------------------------------------------------- */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding-bottom: clamp(40px, 8vw, 84px);
}

.brand {
  display: flex;
  align-items: center;
  gap: 11px;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: -0.01em;
}

.brand img {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--line);
}

.site-nav {
  display: flex;
  align-items: center;
  gap: clamp(14px, 3vw, 26px);
  font-size: 14.5px;
  color: var(--muted);
}

.site-nav a { transition: color 0.18s ease; }
.site-nav a:hover { color: var(--ink); }
.site-nav a[aria-current="page"] { color: var(--ink); }

.site-footer {
  margin-top: clamp(56px, 10vw, 104px);
  padding-top: 22px;
  border-top: 1px solid var(--line);
  font-size: 13px;
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 8px;
}

.site-footer .heart { color: var(--accent); }

.site-footer .coords {
  font-weight: 500;
  font-size: 11.5px;
  letter-spacing: 0.08em;
}

/* ---------------------------------------------------------------
   Hero
   --------------------------------------------------------------- */
.hero {
  display: flex;
  flex-wrap: wrap-reverse;
  align-items: flex-end;
  gap: clamp(28px, 5vw, 56px);
}

.hero__text { flex: 1 1 340px; min-width: min(300px, 100%); }

.hero__eyebrow {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 22px;
}

.hero h1 {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: var(--size-display);
  line-height: 1.04;
  letter-spacing: -0.018em;
  margin: 0 0 26px;
  text-wrap: balance;
}

.hero h1 .accent {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
}

.hero p {
  margin: 0 0 16px;
  color: var(--ink-soft);
  max-width: 50ch;
}

.hero p + p {
  margin-bottom: 0;
  color: var(--muted);
  font-size: 16px;
}

.hero__portrait {
  flex: 0 0 auto;
  width: clamp(132px, 30vw, 184px);
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--line);
  box-shadow: 0 18px 40px -22px rgb(var(--shadow) / 0.35);
}

.hero__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ---------------------------------------------------------------
   Page heads
   --------------------------------------------------------------- */
.page-head { padding-bottom: clamp(24px, 3.5vw, 34px); }

.page-head__top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
}

.page-head h1 {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: var(--size-page-title);
  line-height: 1.05;
  letter-spacing: -0.018em;
  margin: 0;
}

.page-head p {
  margin: 16px 0 0;
  font-size: var(--size-body);
  line-height: 1.6;
  color: var(--muted);
  max-width: 56ch;
  text-wrap: pretty;
}

/* ---------------------------------------------------------------
   Socials
   --------------------------------------------------------------- */
.icon-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.socials { margin-top: clamp(40px, 7vw, 68px); }

.icon-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: 13px;
  border: 1px solid var(--line);
  color: var(--muted);
  transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease, transform 0.18s ease;
}

.icon-link:hover,
.icon-link:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-wash);
  transform: translateY(-2px);
}

.icon-link__tip {
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  white-space: nowrap;
  font-size: 12px;
  font-weight: 500;
  color: var(--bg);
  background: var(--ink);
  padding: 4px 10px;
  border-radius: 7px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.16s ease, transform 0.16s ease;
}

/* The little caret pointing back down at the icon. */
.icon-link__tip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  width: 0;
  height: 0;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--ink);
}

.icon-link:hover .icon-link__tip,
.icon-link:focus-visible .icon-link__tip {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.elsewhere { margin-top: clamp(44px, 8vw, 72px); }
.elsewhere .section-label { display: block; margin-bottom: 18px; }

/* ---------------------------------------------------------------
   Filters
   --------------------------------------------------------------- */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-bottom: clamp(20px, 3.5vw, 34px);
}

.filter {
  font-family: inherit;
  font-weight: 500;
  font-size: var(--size-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  padding: 8px 15px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}

.filter:hover { color: var(--ink); }

.filter[aria-pressed="true"] {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  color: var(--accent);
}

/* ---------------------------------------------------------------
   Feed
   --------------------------------------------------------------- */
.feed { margin-top: var(--gap-section); }

.feed__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 9px;
  border-bottom: 1px solid var(--line);
}

.feed__month { padding-top: var(--gap-block); }
.feed__month > .label { display: block; padding-bottom: 20px; }

.entry {
  display: grid;
  grid-template-columns: clamp(58px, 13vw, 80px) 1fr;
}

.entry__meta { padding-top: 1px; }

.entry__day {
  font-weight: 500;
  font-size: 11.5px;
  letter-spacing: 0.02em;
  color: var(--ink);
  opacity: 0.7;
}

.entry__kind {
  font-weight: 500;
  font-size: 10px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 5px;
}

.entry__body {
  position: relative;
  border-left: 1px solid var(--line);
  padding: 0 0 clamp(30px, 4.5vw, 42px) clamp(20px, 3vw, 28px);
}

.entry__body::before {
  content: "";
  position: absolute;
  left: -3px;
  top: 7px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0.85;
}

.entry__title {
  margin: -4px 0 8px;
  font-family: var(--font-head);
  font-weight: 400;
  font-size: var(--size-title);
  line-height: 1.2;
  letter-spacing: -0.015em;
  text-wrap: balance;
  transition: color 0.18s ease;
}

.entry__title a { transition: color 0.18s ease; }
.entry__title a:hover { color: var(--accent); }

.entry__excerpt {
  margin: 0 0 14px;
  font-size: 16px;
  line-height: 1.6;
  color: var(--muted);
  max-width: var(--measure-tight);
  text-wrap: pretty;
}

.entry__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
}

.entry__more {
  font-weight: 500;
  font-size: var(--size-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}

.entry__note {
  white-space: nowrap;
  font-style: italic;
  text-transform: none;
  letter-spacing: 0.02em;
}

/* The recording — used in the timeline and at the top of an article */
.player {
  display: flex;
  align-items: center;
  gap: 13px;
  flex: 1 1 260px;
  max-width: 380px;
  padding: 6px 16px 6px 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--paper);
  transition: border-color 0.2s ease;
}

.player:hover { border-color: var(--accent-line); }

.play {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--accent);
  cursor: pointer;
  transition: border-color 0.18s ease, background 0.18s ease;
}

.player:hover .play,
.play[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-wash); }

.play svg { display: block; }

/* Hairline "waveform" that fills as the recording plays. */
.player__wave {
  position: relative;
  flex: 1 1 auto;
  min-width: 40px;
  height: 3px;
  border-radius: 2px;
  background: repeating-linear-gradient(90deg,
    color-mix(in srgb, var(--muted) 45%, transparent) 0 3px, transparent 3px 6px);
}

.player__wave i {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 0;
  border-radius: 2px;
  background: repeating-linear-gradient(90deg, var(--accent) 0 3px, transparent 3px 6px);
}

.player__time { flex: 0 0 auto; }

.article > .player { margin-top: 28px; max-width: 420px; }

/* Cards inside the feed */
.card {
  display: block;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px 18px;
  background: var(--paper);
  transition: border-color 0.2s ease, transform 0.2s ease;
}

a.card:hover {
  border-color: var(--accent-line);
  transform: translateY(-1px);
}

.card__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
}

.card__title {
  font-family: var(--font-head);
  font-size: 21px;
  letter-spacing: -0.01em;
}

.card__arrow { color: var(--accent); font-size: 15px; }

.card__body {
  margin: 6px 0 0;
  font-size: var(--size-small);
  line-height: 1.55;
  color: var(--muted);
  max-width: 50ch;
}

.card__meta { margin-top: 12px; }

/* A project on the timeline carries its screenshot, in the same 4:3 the
   /projects rows use so the two pages agree about what a project looks like.
   The card stays a plain block when there is no image, so nothing reserves an
   empty column. */
.card--shot {
  display: grid;
  grid-template-columns: clamp(84px, 11vw, 104px) 1fr;
  gap: 16px;
  align-items: start;
}

.card__shot {
  display: block;
  width: 100%;
  height: auto;
  /* Square. The 4:3 of the /projects rows is a shape those rows have room for;
     squeezed next to two lines of text in the feed it just read as a wonky
     letterbox. A square sits level with the block of text beside it. */
  aspect-ratio: 1 / 1;
  object-fit: cover;
  object-position: top center;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--paper);
  filter: saturate(0.9);
  transition: filter 0.22s ease, border-color 0.22s ease;
}

a.card--shot:hover .card__shot {
  filter: none;
  border-color: var(--accent-line);
}

/* Narrow screens keep the card as it was. The feed already sits in the entry
   column beside the date rail, so a thumbnail here would squeeze the title into
   two or three words a line. */
@media (max-width: 700px) {
  .card--shot { display: block; }
  .card__shot { display: none; }
}

/* A video leads with the frame rather than tucking it beside the title. A 16:9
   still is the thing being linked to, and cropping it to a square — the shape a
   project screenshot takes here — would throw away the framing that makes a
   thumbnail worth showing at all. */
.card--video { padding: 0; overflow: hidden; }

.card__frame {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  filter: saturate(0.92);
  transition: filter 0.22s ease;
}

a.card--video:hover .card__frame { filter: none; }

.card--video .card__text { padding: 14px 18px 16px; }
.card--video .card__meta { margin-top: 8px; }

/* Incremental loading — sits in the entry column, not the date column */
.feed__more {
  display: grid;
  grid-template-columns: clamp(58px, 13vw, 80px) 1fr;
  padding-bottom: var(--gap-block);
}

/* the timeline hairline carries on past the last entry */
.feed__more::before {
  content: "";
  grid-column: 2;
  grid-row: 1;
  justify-self: start;
  width: 1px;
  background: var(--line);
}

.feed__more > * {
  grid-column: 2;
  grid-row: 1;
  justify-self: start;
  margin: 0 0 0 clamp(20px, 3vw, 28px);
}

.feed__end {
  font-size: var(--size-small);
  color: var(--muted);
}

/* Pill — the feed's "show more" loader */
.pill {
  display: inline-block;
  font-family: inherit;
  font-weight: 500;
  font-size: var(--size-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 11px 20px;
  cursor: pointer;
  transition: border-color 0.18s ease, color 0.18s ease;
}

.pill:hover { border-color: var(--accent-line); color: var(--accent); }

/* ---------------------------------------------------------------
   Scrubber rail
   --------------------------------------------------------------- */
.rail { display: none; }

@media (min-width: 1320px) {
  .rail {
    position: fixed;
    left: 0;
    top: 0;
    height: 100vh;
    width: 190px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-left: 28px;
    z-index: 30;
    pointer-events: none;
  }

  .tick {
    pointer-events: auto;
    position: relative;
    display: flex;
    align-items: center;
    height: 12px;
    width: 120px;
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
  }

  .tick--year { margin-top: 12px; }
  .tick:first-child { margin-top: 0; }

  .tick i {
    display: block;
    height: 1px;
    width: 13px;
    background: var(--line);
    transition: width 0.26s cubic-bezier(0.2, 0.7, 0.3, 1), background 0.2s ease;
  }

  .tick--year i { width: 24px; background: var(--muted); }

  .tick:hover i,
  .tick.is-active i { width: 34px; background: var(--accent); }

  .tick b {
    position: absolute;
    left: 44px;
    top: 50%;
    transform: translateY(-50%) translateX(-7px);
    white-space: nowrap;
    font-weight: 500;
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease;
  }

  /* Hovering the rail reveals only the year marks — plus whichever tick is
     under the cursor, and the active one. Showing all twelve months at once
     is noise. */
  .tick:hover b,
  .tick.is-active b,
  .rail:hover .tick--year b {
    opacity: 1;
    transform: translateY(-50%) translateX(0);
  }

  .tick:hover b,
  .tick.is-active b { color: var(--accent); }
}

/* ---------------------------------------------------------------
   Year-grouped rows — the posts list and the projects list
   --------------------------------------------------------------- */
.year { padding-top: clamp(20px, 3vw, 30px); }

.year > .label {
  display: block;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}

.row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 6px 26px;
  padding: 19px 6px;
  border-bottom: 1px solid var(--line);
  transition: padding-left 0.22s ease, background 0.22s ease;
}

a.row:hover {
  padding-left: 15px;
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}

.row__title {
  display: block;
  font-family: var(--font-head);
  font-size: 22px;
  line-height: 1.25;
  letter-spacing: -0.012em;
}

.row__arrow {
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--accent);
  vertical-align: 2px;
}

.row__body {
  display: block;
  margin-top: 6px;
  font-size: var(--size-small);
  line-height: 1.55;
  color: var(--muted);
  max-width: var(--measure-tight);
  text-wrap: pretty;
}

.row__meta {
  white-space: nowrap;
  text-align: right;
  padding-top: 7px;
}

/* A project row carries a screenshot. Projects with none keep the column
   empty rather than closing it up, so every title starts at the same place. */
.row--project { grid-template-columns: clamp(74px, 13vw, 124px) 1fr auto; }

.row__shot {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: top center;
  align-self: start;
  margin-top: 5px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--paper);
  filter: saturate(0.9);
  transition: filter 0.22s ease, border-color 0.22s ease;
}

a.row--project:hover .row__shot {
  filter: none;
  border-color: var(--accent-line);
}

.row__shot--none { border-color: transparent; background: none; }

/* Narrow: the meta drops under the text rather than squeezing the title. */
@media (max-width: 560px) {
  .site-header { flex-wrap: wrap; row-gap: 16px; }
  .site-nav { gap: 18px; }

  .row { grid-template-columns: 1fr; }
  .row--project { grid-template-columns: 62px 1fr; gap: 4px 16px; }
  .row--project .row__meta { grid-column: 2; }

  .row__meta {
    text-align: left;
    padding-top: 2px;
  }
}

/* ---------------------------------------------------------------
   Photos — a column masonry so uploads keep their own shape,
   nothing cropped to a square.
   --------------------------------------------------------------- */
.photo-grid {
  columns: 2;
  column-gap: clamp(14px, 2.4vw, 22px);
  padding-top: clamp(16px, 2.4vw, 24px);
}

.photo {
  break-inside: avoid;
  margin: 0 0 clamp(16px, 2.6vw, 26px);
}

.photo img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  transition: border-color 0.22s ease;
}

.photo a:hover img { border-color: var(--accent-line); }

.photo figcaption {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  margin-top: 9px;
  font-size: var(--size-small);
  line-height: 1.45;
  color: var(--muted);
  text-wrap: pretty;
}

.photo figcaption .label { white-space: nowrap; padding-top: 2px; }

/* In the timeline a photo is one entry, already inside the date column. */
.entry .photo { margin: 0; }

@media (max-width: 560px) {
  .photo-grid { columns: 1; }
}

/* ---------------------------------------------------------------
   Article
   --------------------------------------------------------------- */
.back { display: inline-block; transition: color 0.18s ease; }
.back:hover { color: var(--accent); }

.article h1 {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: var(--size-page-title);
  line-height: 1.06;
  letter-spacing: -0.02em;
  margin: 24px 0 18px;
  text-wrap: balance;
}

.article__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  padding-bottom: 26px;
  border-bottom: 1px solid var(--line);
}

.prose > p {
  margin: 20px 0 0;
  font-size: 17.5px;
  line-height: 1.72;
  max-width: var(--measure);
  text-wrap: pretty;
}

.prose > p:first-child {
  margin-top: 30px;
  font-family: var(--font-head);
  font-size: clamp(1.2rem, 2.6vw, 1.4rem);
  line-height: 1.5;
  color: color-mix(in srgb, var(--ink) 92%, var(--bg));
  max-width: 52ch;
}

.prose h2 {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: clamp(1.5rem, 3.2vw, 1.85rem);
  line-height: 1.2;
  letter-spacing: -0.015em;
  margin: 42px 0 14px;
}

.prose blockquote {
  margin: 26px 0 34px;
  font-family: var(--font-head);
  font-style: italic;
  font-size: clamp(1.3rem, 2.9vw, 1.55rem);
  line-height: 1.42;
  color: color-mix(in srgb, var(--ink) 88%, var(--bg));
  max-width: 46ch;
}

.prose h3 {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: clamp(1.2rem, 2.4vw, 1.35rem);
  line-height: 1.28;
  letter-spacing: -0.008em;
  margin: 32px 0 10px;
}

.prose ul,
.prose ol {
  margin: 18px 0 0;
  padding-left: 1.2em;
  max-width: var(--measure);
}

.prose li {
  margin-top: 9px;
  font-size: 17.5px;
  line-height: 1.66;
  padding-left: 0.25em;
  text-wrap: pretty;
}

.prose li::marker { color: var(--accent); font-size: 0.9em; }

.prose ol li::marker { letter-spacing: 0.02em; }

.prose strong {
  font-weight: 600;
  color: color-mix(in srgb, var(--ink) 96%, var(--bg));
}

.prose blockquote p { margin: 0; }

.prose figure { margin: 34px 0 0; }

.prose figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--line);
}

.prose figcaption {
  margin-top: 10px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--muted);
  max-width: 52ch;
}

.post-nav {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
  margin-top: clamp(48px, 7vw, 72px);
  padding-top: 26px;
  border-top: 1px solid var(--line);
}

.post-nav a {
  display: block;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color 0.2s ease, transform 0.2s ease;
}

.post-nav a:hover { border-color: var(--accent-line); transform: translateY(-1px); }
.post-nav a:last-child:not(:first-child) { text-align: right; }

.post-nav strong {
  display: block;
  margin-top: 7px;
  font-family: var(--font-head);
  font-weight: 400;
  font-size: 19px;
  line-height: 1.25;
}

/* ---------------------------------------------------------------
   Utility
   --------------------------------------------------------------- */
.empty {
  padding: 30px 0;
  color: var(--muted);
  font-size: var(--size-small);
}

[hidden] { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.01ms !important; }
}

/* ---------------------------------------------------------------
   Photo sets
   A set is the unit: a few photos with a title and a few lines.
   Mobile is the primary target here; the sort row and map only
   appear once the viewport is wide enough to want them.
   --------------------------------------------------------------- */
.set {
  padding: clamp(26px, 5vw, 40px) 0;
  border-bottom: 1px solid var(--line);
}

.set:last-child { border-bottom: 0; }

/* On the timeline the entry already supplies the rule, the dot and the spacing
   below, so the card drops its own. The edge-to-edge photos of /photos are
   dropped too: here there is a timeline rule to the left to stay inside of. */
.entry .set { padding: 0; border-bottom: 0; }
.entry .set__photos { margin-inline: 0; border-radius: var(--radius); }
.entry .set__head h2 { margin-top: -4px; }

.set__head {
  display: block;
  text-decoration: none;
  color: inherit;
}

.set__head h2 {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: var(--size-title);
  line-height: 1.15;
  letter-spacing: -0.014em;
  margin: 0 0 6px;
  text-wrap: balance;
  transition: color 0.2s ease;
}

.set__head:hover h2 { color: var(--accent); }

.set__summary {
  margin: 12px 0 0;
  color: var(--muted);
  max-width: var(--measure-tight);
  text-wrap: pretty;
}

/* The four shapes. Anything past four photos shows a count instead of
   growing the card, so the feed keeps a predictable rhythm. */
.set__photos {
  display: grid;
  gap: 5px;
  margin-top: clamp(16px, 3vw, 22px);
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
}

/* min-height/min-width 0 is load-bearing. Grid items default to min-height
   auto, so a portrait photo's min-content height overrides the container's
   aspect-ratio and drags every cell in the row taller with it — the square
   mosaic silently stops being square as soon as one source is a portrait. */
.set__photos img {
  display: block;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  object-fit: cover;
  background: var(--paper);
  transition: opacity 0.2s ease;
}

.set__photos:hover img { opacity: 0.92; }

/* A set of one is the photograph itself, so it is never cropped — it keeps its
   own proportions whatever they are. Only mosaics crop, and they crop to
   squares, which is the one shape that does not favour landscape over portrait.
   The container ratios below are chosen so every cell lands on 1:1. */
.set__photos--one { grid-template-columns: 1fr; }
.set__photos--one img { height: auto; object-fit: contain; }

.set__photos--two { grid-template-columns: 1fr 1fr; aspect-ratio: 2 / 1; }

.set__photos--three {
  grid-template-columns: 2fr 1fr;
  grid-template-rows: 1fr 1fr;
  aspect-ratio: 3 / 2;
}
.set__photos--three img:first-child { grid-row: span 2; }

.set__photos--many {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  aspect-ratio: 1 / 1;
}

.set__more {
  position: absolute;
  right: 10px;
  bottom: 10px;
  font-size: var(--size-label);
  font-weight: 600;
  letter-spacing: 0.1em;
  padding: 5px 11px;
  border-radius: var(--radius-pill);
  color: var(--paper);
  background: color-mix(in srgb, var(--ink) 78%, transparent);
  backdrop-filter: blur(6px);
}

/* On a phone the set fills the screen edge to edge — the padding that suits
   text makes photographs feel like postage stamps. */
@media (max-width: 560px) {
  .set__photos {
    margin-inline: calc(var(--pad-x) * -1);
    border-radius: 0;
  }
}

/* No wide-screen override here on purpose. A four-across strip turns every
   thumbnail into a 1:2 sliver, which crops the photograph to something the
   photograph never was. The 2x2 square grid holds at every width. */

/* ------------------------------------------------------ one set, in full */
.back-link {
  display: inline-block;
  margin-bottom: 18px;
  font-size: var(--size-small);
  color: var(--muted);
  text-decoration: none;
}
.back-link:hover { color: var(--accent); }

.set-full {
  display: grid;
  gap: clamp(22px, 4vw, 40px);
}

.set-full .photo img {
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  background: var(--paper);
}

/* Negative margins rather than 100vw: vw includes the scrollbar gutter, which
   is exactly how a page ends up scrolling sideways by a few pixels. */
@media (max-width: 560px) {
  .set-full .photo img { border-radius: 0; }
  .set-full { margin-inline: calc(var(--pad-x) * -1); }
  .set-full figcaption { padding-inline: var(--pad-x); }
}

/* ------------------------------------------------------------ desktop tools */
.gallery-tools {
  display: grid;
  gap: 20px;
  padding-bottom: clamp(20px, 3vw, 30px);
  border-bottom: 1px solid var(--line);
}

.gallery-tools__row {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.gallery-map {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--paper);
  overflow: hidden;
}

.gallery-map svg { display: block; width: 100%; height: auto; }

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

.map__pins circle {
  fill: color-mix(in srgb, var(--accent) 74%, transparent);
  stroke: var(--paper);
  stroke-width: 2;
  transition: fill 0.2s ease;
}

.map__pins text {
  fill: var(--muted);
  font-family: var(--font-body);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-anchor: middle;
}

.map__pin:hover circle { fill: var(--accent); }
.map__pin:hover text { fill: var(--ink); }

/* ------------------------------------------------------------------ lightbox */

/* Black, full-bleed and outside the page's palette on purpose: a photograph is
   easiest to read against nothing at all, in either theme. */
.lb {
  border: 0;
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
  max-width: 100vw;
  max-height: 100vh;
  background: #000;
  color: #fff;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}
.lb::backdrop { background: rgba(0, 0, 0, 0.92); }

/* Three slides on one track. See the note in site.js for why a single <img>
   swapping its src cannot do this. */
.lb__track { position: absolute; inset: 0; touch-action: none; will-change: transform; }
.lb__track.animate { transition: transform 0.26s cubic-bezier(0.22, 0.61, 0.36, 1); }
.lb__track.fading { transition: transform 0.26s ease, opacity 0.26s ease; }

.lb__slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
}

/* Chrome lives in the letterbox and gets out of the way. A portrait photo fills
   the height, so a permanent bar would sit on the picture. */
.lb__bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(8px);
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.lb__bar--away { opacity: 0; transform: translateY(100%); pointer-events: none; }

.lb__bar .label { color: #fff; }
.lb__pos { opacity: 0.7; }
.lb__cap { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb__keys { opacity: 0.45; }

.lb__bar button {
  color: #fff;
  background: rgba(255, 255, 255, 0.16);
  border: 0;
  border-radius: var(--radius-pill);
  padding: 6px 13px;
  font: inherit;
  font-size: var(--size-label);
  letter-spacing: 0.08em;
  cursor: pointer;
}
.lb__bar button:hover { background: rgba(255, 255, 255, 0.28); }
.lb__nav { font-size: 17px; line-height: 1; padding: 4px 12px; }

/* Invisible halves for tap-to-move, only where there is a finger. Plain divs
   rather than buttons: nothing to focus, so nothing draws a focus ring, and they
   stay out of Safari's double-tap gesture — an accidental zoom in here is
   unrecoverable, because Safari keeps the zoom level across a reload. They stop
   short of the bar so the right-hand zone never covers Close. */
.lb__zone {
  position: fixed;
  top: 0;
  bottom: 92px;
  width: 32%;
  display: none;
  background: transparent;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.lb__zone--prev { left: 0; }
.lb__zone--next { right: 0; }

@media (pointer: coarse) {
  .lb__zone { display: block; }
  .lb__nav, .lb__keys { display: none; }
}

/* A thumbnail that opens the viewer should say so. */
.set__photos[data-set] img { cursor: zoom-in; }

/* --------------------------------------------------------------------- notes */

/* A Bluesky post is 300 characters at most — it is the writing itself, with no
   title to lead with, so it gets no card chrome. It reads as a paragraph in the
   timeline, the way an excerpt does, rather than a box pretending to be one. */
.note__text {
  margin: -3px 0 0;
  font-size: var(--size-small);
  line-height: 1.6;
  /* Line breaks are authored — a post is written with them, not reflowed. */
  white-space: pre-wrap;
  max-width: var(--measure-tight);
  text-wrap: pretty;
}

.note__media {
  display: grid;
  gap: 5px;
  margin-top: 14px;
  border-radius: var(--radius);
  overflow: hidden;
  position: relative;
  max-width: var(--measure-tight);
}

.note__media img {
  display: block;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  object-fit: cover;
  background: var(--paper);
}

/* One image keeps its own proportions; more than one crops to a strip, because
   a row of mismatched heights reads as a mistake rather than a set. */
.note__media--one { grid-template-columns: 1fr; }
.note__media--one img { height: auto; object-fit: contain; }
.note__media--two { grid-template-columns: 1fr 1fr; aspect-ratio: 2 / 1; }
.note__media--many { grid-template-columns: 1fr 1fr; aspect-ratio: 3 / 2; }

/* Video posts link out rather than embedding a player: the still says there is
   something to watch, and the ▸ says where. */
.note__media--play::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 46px;
  height: 46px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 62%, transparent) center / 13px no-repeat
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23fff'%3E%3Cpath d='M8 5v14l11-7z'/%3E%3C/svg%3E");
  backdrop-filter: blur(3px);
  pointer-events: none;
}

.note__card {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-top: 12px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-decoration: none;
  color: inherit;
  max-width: var(--measure-tight);
  transition: border-color 0.2s ease;
}
.note__card:hover { border-color: var(--accent-line); }
.note__card-title { font-size: var(--size-small); line-height: 1.4; }

.note__link {
  display: inline-block;
  margin-top: 12px;
  color: var(--muted);
  text-decoration: none;
  transition: color 0.18s ease;
}
.note__link:hover { color: var(--accent); }

/* A phone photo is 3:4 or taller, and a single one stretched to the full width
   of the entry column is a screen and a half of one picture — you scroll past
   the timeline rather than down it. Cap the height and let the width follow, so
   a portrait shot sits at its own size. Only in the feed: on /photos and /set a
   set of one is deliberately the photograph itself, uncropped and full width. */
.entry .note__media--one img,
.entry .set__photos--one img {
  width: 100%;
  height: auto;
  max-height: min(52vh, 480px);
  /* contain, not cover: a tall photo is centred in the box with its own
     background either side, rather than being cropped down the middle. */
  object-fit: contain;
  background: var(--paper);
}
