/* ---------------------------------------------------------------
   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; }

/* A short is 9:16. Full-bleed that would be a tower half a screen tall for
   sixty seconds of video, so it sits in a column beside the text instead —
   the arrangement a project screenshot already uses. */
.card--vertical {
  display: grid;
  grid-template-columns: clamp(88px, 12vw, 116px) 1fr;
  gap: 0;
}

/* The frame keeps its own proportions rather than stretching to the row. A
   still forced to the height of the text beside it is cropped by whatever that
   text happens to say, which at some widths leaves a sliver of the frame and
   nothing else. The divider therefore rides on the text, not on the image, so
   it still runs the full height of the card. */
.card--vertical .card__frame {
  aspect-ratio: 9 / 16;
  align-self: center;
  border-bottom: 0;
}

.card--vertical .card__text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  border-left: 1px solid var(--line);
}

/* Side by side stops working once the card is phone-width: a 9:16 column beside
   the title leaves the text a few words a line. So the frame moves above the
   text and gives up the bleed. A short is a poster here, not a banner: full
   width at 9:16 would be a tower taller than the screen. */
@media (max-width: 460px) {
  .card--vertical {
    display: block;
    padding: 16px 18px 18px;
  }

  .card--vertical .card__frame {
    width: 140px;
    height: auto;
    margin: 0 auto 14px;
    border: 1px solid var(--line);
    border-radius: 7px;
  }

  .card--vertical .card__text { padding: 0; border-left: 0; }
}

/* 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; }
  /* Five items no longer fit on one line at 360px, and a nav that
     overflows sideways loses its last item entirely. */
  .site-nav { flex-wrap: wrap; gap: 10px 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);
}

/* The still is the play button, so it behaves like one. */
a.note__media--play { display: grid; cursor: pointer; }
a.note__media--play img { transition: opacity 0.2s ease; }
a.note__media--play:hover img { opacity: 0.88; }
a.note__media--play::after { transition: background-color 0.2s ease, transform 0.2s ease; }
a.note__media--play:hover::after {
  background-color: color-mix(in srgb, var(--accent) 88%, transparent);
  transform: translate(-50%, -50%) scale(1.06);
}

/* ---------------------------------------------------------------
   Guestbook
   --------------------------------------------------------------- */
#guestbook-sign { margin-top: 22px; }

.sign {
  text-decoration: none;
  color: var(--accent);
  border-color: var(--accent-line);
  background: var(--accent-wash);
}
.sign:hover { border-color: var(--accent); }

.gb__empty {
  margin: 0;
  padding: 30px 0;
  font-size: var(--size-body);
  font-style: italic;
  color: var(--muted);
}

.sig {
  padding: 22px 0;
  border-bottom: 1px solid var(--line);
}

.sig__who {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: inherit;
}

.sig__face {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-pill);
  object-fit: cover;
  background: var(--paper);
  flex: none;
}

/* An account with no avatar gets its initial rather than a grey circle, so the
   row keeps its shape and the eye still has something to land on. */
.sig__face--none {
  display: grid;
  place-items: center;
  font-family: var(--font-head);
  font-size: 15px;
  color: var(--muted);
  border: 1px solid var(--line);
}

.sig__name {
  font-family: var(--font-head);
  font-size: 17px;
  transition: color 0.18s ease;
}
.sig__who:hover .sig__name { color: var(--accent); }

/* The handle is the quiet half of the name and sits on the same line until the
   line runs out, at which point it is the part that goes. */
.sig__handle { text-transform: none; letter-spacing: 0.02em; }
@media (max-width: 460px) { .sig__handle { display: none; } }

.sig__text {
  margin: 12px 0 0 44px;
  font-size: var(--size-small);
  line-height: 1.6;
  /* Somebody wrote their line breaks on purpose. Keep them. */
  white-space: pre-wrap;
  max-width: var(--measure-tight);
  text-wrap: pretty;
}

.sig__date {
  display: inline-block;
  margin: 10px 0 0 44px;
  color: var(--muted);
  text-decoration: none;
  transition: color 0.18s ease;
}
.sig__date:hover { color: var(--accent); }

/* My own reply to a signature is part of that signature, not another one: it is
   indented under it and gives back the divider it would otherwise draw. */
.sig--reply {
  margin: 18px 0 0 44px;
  padding: 0 0 0 16px;
  border-bottom: 0;
  border-left: 2px solid var(--accent-line);
}
.sig--reply .sig__text,
.sig--reply .sig__date { margin-left: 0; }
.sig--reply .sig__face { width: 22px; height: 22px; }
.sig--reply .sig__name { font-size: 15px; }
