/* Project Detail Page — the hero, then the sheet */

/* ===== DESIGN TOKENS (unified across the page trio) ===== */
:root {
  --paper: #f1eee8;
  /* Leaf green. --accent is tuned for the warm paper ground; --accent-inv is
     the same hue lifted so it stays legible on dark sections. */
  --accent: #2e5c3c;
  --accent-inv: #a8c9a4;
  --accent-soft: rgba(46, 92, 60, .12);
  --line: rgba(10, 10, 10, .12);
  --line-inv: rgba(255, 255, 255, .16);
  --e-power: cubic-bezier(.22, 1, .36, 1);
}

/* ===== READING PROGRESS ===== */
.read-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 2px;
  z-index: 210;
  background: transparent;
  pointer-events: none;
}
.read-progress-bar {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--black);
  transform-origin: left;
  transition: background 0.3s;
}
/* Over dark sections the nav inverts; keep the bar visible there too */
#nav.nav-dark ~ .read-progress .read-progress-bar,
.nav-dark-active .read-progress-bar {
  background: rgba(255,255,255,0.9);
}



/* ===== HERO ===== */
.project-hero {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100svh;
  min-height: 560px;
  overflow: hidden;
  background: #0a0a0a;
}
.hero-cinema {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

/* Render layers — for slide transitions */
.render-layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  will-change: transform, opacity;
  overflow: hidden;
}
.render-layer.render-active { opacity: 1; }
.render-layer img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  will-change: transform, filter;
}

/* Condition tints */
.condition-tint {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.2s ease;
}
.condition-tint.active { opacity: 1; }
.autumn-tint {
  background: linear-gradient(180deg, rgba(180,120,50,0.12) 0%, rgba(140,80,30,0.18) 100%);
}
.night-tint {
  background: linear-gradient(180deg, rgba(10,15,40,0.5) 0%, rgba(5,10,30,0.35) 100%);
}
.hero-vignette {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.5) 100%);
}

/* ===== BOTTOM FURNITURE STACK ===== */
.hero-foot {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: clamp(12px, 2.2vh, 30px);
  padding: 0 clamp(20px, 4vw, 48px)
             max(clamp(16px, 2.6vh, 30px), env(safe-area-inset-bottom, 0px));
}

/* ===== CONDITION NAME (above slider) ===== */
.condition-display {
  pointer-events: none;
  text-align: center;
}
.condition-word {
  font-family: var(--serif);
  font-size: clamp(1.1rem, 2.5vw, 1.8rem);
  font-weight: 300;
  letter-spacing: 0.4em;
  color: #fff;
  text-transform: uppercase;
  opacity: 0.85;
  text-shadow: 0 1px 20px rgba(0,0,0,0.3);
  display: inline-block;
  white-space: nowrap;
  will-change: transform, opacity;
}

/* ===== RENDER COUNTER (top right) ===== */
.render-counter {
  position: absolute;
  top: calc(var(--nav-h, 80px) + 16px);
  right: 32px;
  z-index: 4;
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 400;
  color: rgba(255,255,255,0.5);
  letter-spacing: 0.12em;
  display: flex;
  align-items: center;
  gap: 6px;
}
.ri-dash { opacity: 0.4; }

/* ===== PROJECT INFO (in .hero-foot) ===== */
.hero-info {
  display: flex;
  flex-direction: column;
  gap: 8px;
  color: #fff;
  max-width: 380px;
}
.hero-cat {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  opacity: 0.6;
}
.hero-title {
  font-family: var(--serif);
  font-size: clamp(1.6rem, 3vw, 2.4rem);
  font-weight: 400;
  line-height: 1.15;
  margin: 0;
}
.hero-year {
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 300;
  opacity: 0.5;
  letter-spacing: 0.08em;
}

/* ===== RENDER NAV ARROWS ===== */
.render-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 5;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.2);
  background: rgba(0,0,0,0.2);
  backdrop-filter: blur(8px);
  color: rgba(255,255,255,0.7);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s ease;
}
.render-nav:hover {
  background: rgba(255,255,255,0.1);
  border-color: rgba(255,255,255,0.4);
  color: #fff;
  transform: translateY(-50%) scale(1.08);
}
.render-nav:active { transform: translateY(-50%) scale(0.95); }
.render-nav.prev { left: 24px; }
.render-nav.next { right: 24px; }
.render-nav svg { width: 20px; height: 20px; }

/* ===== NOTCH SLIDER (in .hero-foot) ===== */
.notch-slider {
  width: min(88%, 480px);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.notch-viewport {
  position: relative;
  height: clamp(30px, 5vh, 48px);
  width: 100%;
  cursor: grab;
  touch-action: none;
}
.notch-viewport:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.85);
  outline-offset: 6px;
  border-radius: 2px;
}
.notch-viewport:active { cursor: grabbing; }
.notch-strip {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  width: 100%;
  height: 100%;
  padding: 0 2px;
  transform: perspective(600px) rotateX(8deg);
  transform-origin: center bottom;
}
.notch {
  width: 1.5px;
  background: rgba(255,255,255,0.35);
  border-radius: 1px 1px 0 0;
  flex-shrink: 0;
  transform-origin: bottom center;
  height: 10px;
}
.notch.major {
  width: 2px;
  height: 14px;
}
.notch-thumb {
  position: absolute;
  bottom: 0;
  left: 0%;
  width: 2.5px;
  height: 40px;
  background: #fff;
  border-radius: 1.5px;
  z-index: 5;
  box-shadow: 0 0 12px rgba(255,255,255,0.5), 0 0 3px rgba(255,255,255,0.9);
  pointer-events: none;
  transform: translateX(-50%);
  transition: left 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.notch-thumb.dragging { transition: none; }
.notch-labels {
  display: flex;
  justify-content: space-between;
  width: 100%;
  user-select: none;
  padding: 0 2px;
}
.notch-labels span {
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  color: rgba(255,255,255,0.2);
  cursor: pointer;
  transition: color 0.4s;
  text-align: center;
  flex: 1;
}
.notch-labels span:hover { color: rgba(255,255,255,0.45); }
.notch-labels span.active { color: rgba(255,255,255,0.85); }
.drag-hint {
  font-family: var(--sans);
  font-size: 9px;
  font-weight: 400;
  letter-spacing: 0.2em;
  color: rgba(255,255,255,0.4);
  text-align: center;
  margin-top: 2px;
}


/* ============================================================
   BELOW THE HERO — THE PROJECT SHEET
   ------------------------------------------------------------
   Rebuilt to sit inside the same design language as projects,
   about and the index rather than beside it.

   What that means concretely, and why each rule is here:

     · ONE SHELL. `.pd-shell` is `max-width:1240px; margin:0 auto`
       inside a `clamp(20px, 5vw, 64px)` gutter — the same pair
       about.css repeats on every section. Two sections opt out
       deliberately (the reel and the closing plate) by pushing
       the gutter down onto their own children, which is exactly
       how about's team reel bleeds.

     · ONE HEADER. `.pd-head` is a baseline flex row over a 1px
       hairline: `.s-label` on the left, a serif accent numeral on
       the right. Every section on this page announces itself the
       same way, and it is the same idiom as `.phil-top`,
       `.fd-head` and `.pj-filter-head`.

     · SERIF FOR DISPLAY AND NUMERALS, at weight 400 and nothing
       else. Poppins 300 for prose, 500 for uppercase labels
       tracked .14em–.2em. Green is reserved for numerals and
       micro-labels — never a heading, never body copy.

     · svh FOR VERTICAL RHYTHM, so a short laptop compresses
       instead of clipping.

     · NO BREAKPOINT DECIDES WHO GETS THE GOOD VERSION. The
       approach chapter is a CSS sticky plate and the reel is a
       native scroller, so a phone gets the same two mechanisms a
       desktop does. The page it replaced pinned the intent
       section above 1025px and handed everything narrower a flat
       stack.

     · html.pd-ready GATES EVERY HIDDEN START STATE, so a blocked
       or failed script leaves a page that is verbose rather than
       broken.
   ============================================================ */

.project-page {
  background: var(--paper);
  /* shared.css sets --grey to #767676, which is 3.92:1 on this paper.
     #67625a is 5.22:1 and retro-fixes every label and caption in both
     sheets at once. */
  --grey: #67625a;
  --ink: #0a0a0a;
  --gut: clamp(20px, 5vw, 64px);
  --shell: 1240px;
}

/* The scrolled nav tint is white in shared.css. `:not(.nav-dark)` is
   load-bearing — without it this ties on specificity with
   `#nav.nav-scrolled.nav-dark` and, loading later, wins, flattening the
   bar to paper over the hero. */
.project-page #nav.nav-scrolled:not(.nav-dark) {
  background: rgba(241, 238, 232, .88);
  box-shadow: 0 1px 0 rgba(10, 10, 10, .07);
}

/* On the dark plates the shared focus ring (solid --black) is invisible. */
.project-page .pd-onward a:focus-visible,
.project-page .pd-voice a:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
}

/* ---- the shell, the header, the rail ------------------------ */
.pd-shell {
  max-width: var(--shell);
  margin: 0 auto;
  width: 100%;
}

.pd-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
}
.pd-head .s-label { margin-bottom: 0; }

/* The two things that ever sit on the right of a header: a count, or a
   numbered caption. Both are a serif accent numeral inside tracked
   uppercase sans — the `<b>`/`<i>` idiom the about page uses throughout. */
.pd-head-count,
.pd-head-meta {
  font-size: .62rem;
  font-weight: 400;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--grey);
  white-space: nowrap;
}
.pd-head-count b,
.pd-head-meta i {
  font-family: var(--serif);
  font-style: normal;
  font-weight: 400;
  font-size: 1.2rem;
  letter-spacing: 0;
  color: var(--accent);
  margin-right: 7px;
  font-variant-numeric: tabular-nums lining-nums;
}
.pd-head-count s { text-decoration: none; opacity: .45; }

/* One progress rail, used by the approach chapter and the reel. Driven by
   a direct style write with no transition, so it tracks scroll exactly. */
.pd-rail {
  position: relative;
  display: block;
  width: 100%;
  height: 2px;
  background: var(--line);
  overflow: hidden;
}
.pd-rail-fill {
  position: absolute;
  inset: 0;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
}

/* Section headings. One size for all four so the page reads as a set. */
.pd-ap-h,
.pd-fr-h,
.pd-mt-h {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.7rem, 3.4vw, 3rem);
  line-height: 1.1;
  max-width: 20ch;
  margin: clamp(20px, 3.4svh, 40px) 0 clamp(26px, 4.4svh, 52px);
}


/* ============================================================
   1. BRIEF — the masthead and the record
   ============================================================ */
.pd-brief {
  padding: clamp(48px, 7.5svh, 100px) var(--gut) clamp(48px, 7.5svh, 96px);
}
.pd-brief-body {
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(0, .88fr);
  gap: clamp(30px, 5vw, 88px);
  align-items: start;
  margin-top: clamp(26px, 4.4svh, 52px);
}

.pd-title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.3rem, 6vw, 5.2rem);
  line-height: 1;
  letter-spacing: -.018em;
  max-width: 14ch;
  margin: 0;
}
/* The masked word rise. project.js builds these spans only when it is
   actually going to animate them, so with no JS — or with reduced motion —
   the heading is plain text and these rules never apply to anything. The
   inner element rides `translate`, the standalone property, leaving
   `transform` free for GSAP's own work. */
.pd-title .word {
  display: inline-block;
  overflow: hidden;
  line-height: 1.08;
  vertical-align: top;
}
.pd-title .word > i {
  display: inline-block;
  font-style: normal;
}

.pd-lede {
  margin: clamp(20px, 3svh, 32px) 0 0;
  font-size: clamp(.95rem, 1.5vw, 1.22rem);
  font-weight: 300;
  line-height: 1.62;
  color: var(--ink);
  max-width: 40ch;
}

/* The record. A definition list, because that is what it is — and a
   grid rather than a nested flex so every value shares one baseline
   and the labels stay in a single column down the page. */
.pd-spec {
  margin: 0;
  border-top: 1px solid var(--line);
}
.pd-spec-row {
  display: grid;
  grid-template-columns: minmax(84px, 30%) minmax(0, 1fr);
  align-items: baseline;
  gap: 14px clamp(14px, 2.2vw, 30px);
  padding: clamp(12px, 1.9svh, 18px) 0;
  border-bottom: 1px solid var(--line);
}
.pd-spec dt {
  font-size: .62rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--grey);
}
.pd-spec dd {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1rem, 1.5vw, 1.32rem);
  line-height: 1.2;
  color: var(--ink);
  font-variant-numeric: tabular-nums lining-nums;
}


/* ============================================================
   2. APPROACH — a sticky plate and three notes
   ------------------------------------------------------------
   The plate holds still while the notes pass it, and the note in
   the middle of the screen owns the plate. Nearest-to-centre wins
   rather than a per-note enter trigger: with three bands that
   overlap, two can be active at once and whichever fired last
   wins, and the last note's band ends before the section does,
   which leaves a stale number on screen.
   ============================================================ */
.pd-approach {
  padding: clamp(48px, 7.5svh, 100px) var(--gut) clamp(52px, 8svh, 104px);
  border-top: 1px solid var(--line);
}
.pd-ap-shell {
  display: grid;
  grid-template-columns: minmax(0, .92fr) minmax(0, 1.08fr);
  gap: clamp(30px, 5vw, 88px);
  align-items: start;
}

.pd-ap-stage {
  position: sticky;
  top: calc(var(--nav-h, 64px) + clamp(20px, 5svh, 64px));
  display: grid;
  gap: 14px;
}
.pd-ap-frame {
  position: relative;
  margin: 0;
  aspect-ratio: 4 / 5;
  max-height: calc(100svh - var(--nav-h, 64px) - clamp(96px, 17svh, 190px));
  overflow: hidden;
  border-radius: 6px;
  background: #e7e2d8;
  will-change: clip-path;
}
.pd-ap-frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0;
  transform: scale(1.06);
  transition: opacity .7s var(--e-power), transform 1.2s var(--e-power);
}
.pd-ap-frame img.is-shown { opacity: 1; transform: scale(1); }

.pd-notes { list-style: none; margin: 0; padding: 0; }
.pd-note {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 56svh;
  padding: clamp(18px, 3svh, 34px) 0;
  transition: opacity .6s var(--e-power);
}
/* The dim is gated on the class project.js adds only when it is going to
   drive this chapter. No script, or reduced motion, and all three notes
   simply stay legible — the page degrades to a stack of readable text
   rather than two thirds of a chapter greyed out with nothing to lift it. */
html.pd-ready .pd-note { opacity: .34; }
html.pd-ready .pd-note.is-on { opacity: 1; }

.pd-note-num {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1rem, 1.5vw, 1.3rem);
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums lining-nums;
}
.pd-note-t {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.5rem, 3vw, 2.5rem);
  line-height: 1.12;
  margin: 10px 0 14px;
  transition: transform .6s var(--e-power);
}
.pd-note.is-on .pd-note-t { transform: translateX(6px); }
.pd-note-c {
  margin: 0;
  font-size: clamp(.85rem, 1.05vw, .95rem);
  font-weight: 300;
  line-height: 1.72;
  color: var(--grey);
  max-width: 46ch;
}
/* The features list is a shared component; only its top margin is ours. */
.pd-note .feature-list { margin-top: 6px; }



/* ============================================================
   3. VOICE — the pull quote
   ------------------------------------------------------------
   An inset plate, not a full-bleed black band. The page keeps its
   dark grounds for the hero and the closing plate; a black band
   mid-page is the one thing all three sibling pages refuse.
   ============================================================ */
.pd-voice {
  padding: 0 var(--gut) clamp(48px, 7.5svh, 96px);
}
.pd-voice-card {
  position: relative;
  max-width: var(--shell);
  margin: 0 auto;
  min-height: clamp(320px, 54svh, 540px);
  display: flex;
  align-items: center;
  overflow: hidden;
  border-radius: 6px;
  background: #1a1a18;
  isolation: isolate;
}
.pd-voice-bg {
  position: absolute;
  inset: -12% 0;
  background-size: cover;
  background-position: center;
  /* Desaturated by blending against flat grey rather than `filter:
     grayscale(1)`. A filter on a scrubbing parallax layer re-runs its
     shader on every composited frame — measured at 16 dropped frames per
     2.2s of scroll here, and it was the single largest cost on the page.
     The blend is baked in at raster time instead: pixel-identical output
     (mean channel spread 1.81 either way), no per-frame work. */
  background-color: #808080;
  background-blend-mode: luminosity;
  opacity: .38;
  will-change: transform;
}
.pd-voice-veil {
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 100% at 30% 40%, rgba(10, 10, 10, .34), rgba(10, 10, 10, .78));
}
.pd-voice-inner {
  position: relative;
  z-index: 2;
  padding: clamp(34px, 6svh, 76px) clamp(24px, 5vw, 82px);
  max-width: 940px;
}
.pd-voice-label {
  display: block;
  font-size: .58rem;
  font-weight: 500;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .62);
  margin-bottom: clamp(16px, 2.6svh, 28px);
}
.pd-voice-quote {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-style: italic;
  font-size: clamp(1.4rem, 3.2vw + .4svh, 3rem);
  line-height: 1.3;
  color: #fff;
}
.pd-voice-credit {
  display: block;
  margin-top: clamp(18px, 3svh, 32px);
  font-family: var(--sans);
  font-style: normal;
  font-size: .64rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--accent-inv);
}


/* ============================================================
   4. FRAMES — the gallery
   ------------------------------------------------------------
   No frame is ever cropped — every picture keeps its own
   proportion. That rule decides the layout: in a spanning grid,
   a tall portrait beside a wide landscape sets the row height to
   the portrait and leaves a hole under the landscape the depth of
   the difference, and with eight mixed frames the section reads as
   holes with pictures in them.

   So the frames are packed into columns instead. Each column
   fills to its own height and the variety comes from the pictures
   themselves — a portrait is tall, a landscape is wide and short,
   and neither is asked to pretend otherwise.
   ============================================================ */
.pd-frames {
  padding: clamp(48px, 7.5svh, 100px) var(--gut) clamp(52px, 8svh, 104px);
  border-top: 1px solid var(--line);
}
.pd-grid {
  columns: 2;
  column-gap: clamp(16px, 2.6vw, 42px);
}
.pd-fr-item {
  /* A column box, not a grid cell: `inline-block` + `width:100%` is what
     keeps a multi-column child from being split across the break in the
     browsers that still do that to a plain block. */
  display: inline-block;
  width: 100%;
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  margin: 0 0 clamp(20px, 3.4vw, 46px);
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  color: inherit;
  font: inherit;
}
/* No stagger rule here. A column box is not selectable in CSS, so there is
   no way to say "drop the second column" — and `nth-child(2)` does not mean
   that: the browser balances the columns by height, so on a 1440 the second
   child sits second in the FIRST column and the offset lands as an arbitrary
   extra gap that moves as the widths change. The columns start level and
   diverge on the next frame, because the pictures are different shapes. */

.pd-fr-media {
  position: relative;
  overflow: hidden;
  border-radius: 6px;
  background: #e7e2d8;
}
.pd-fr-media img {
  width: 100%;
  height: auto;
  display: block;
  transition: transform .8s var(--e-power);
}
.pd-fr-item:hover .pd-fr-media img { transform: scale(1.035); }
.pd-fr-media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(transparent 55%, rgba(10, 10, 10, .16));
  opacity: 0;
  transition: opacity .4s var(--e-power);
}
.pd-fr-item:hover .pd-fr-media::after { opacity: 1; }

.pd-fr-cap {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-top: 12px;
  font-size: .6rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--grey);
  font-variant-numeric: tabular-nums;
}
.pd-fr-cap b {
  font-family: var(--serif);
  font-weight: 400;
  font-size: .95rem;
  letter-spacing: 0;
  color: var(--accent);
}


/* ============================================================
   5. MATTER — the material reel
   ------------------------------------------------------------
   Full-bleed: the section carries no horizontal padding and hands
   the gutter to its three children, so the track can run off the
   edge while the head and foot stay on the shell. A native
   scroller at every width — no pin, so a phone swipes the same
   reel a desktop drags. Depth is `--near`, a number written per
   frame from scrollLeft and consumed by CSS calc(), never a class
   toggle with a transition behind it.
   ============================================================ */
.pd-matter {
  padding: clamp(48px, 7.5svh, 100px) 0 clamp(46px, 7svh, 92px);
  border-top: 1px solid var(--line);
  overflow: hidden;
}
.pd-matter-head,
.pd-matter-foot { padding: 0 var(--gut); }
.pd-matter-head .pd-mt-h { margin-bottom: clamp(20px, 3.4svh, 40px); }

.pd-reel {
  --cell: clamp(180px, 21vw, 290px);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* A swipe that runs off the end of the reel must not turn into a
     back-navigation gesture or rubber-band the whole page. */
  overscroll-behavior-x: contain;
  /* No `scroll-snap-type`. Proximity snapping fought every gesture — the
     browser kept re-targeting mid-glide, which is what made the swipe feel
     notchy. project.js eases to the nearest cell once the scroll has
     actually stopped instead, so the motion is never interrupted while a
     finger or a mouse is still driving it. */
}
.pd-reel::-webkit-scrollbar { display: none; }
.pd-reel.is-grabbing { cursor: grabbing; }

.pd-reel-track {
  list-style: none;
  margin: 0;
  /* Positioned so it is the cells' offsetParent. project.js caches each
     cell's offsetLeft as its position in scroll space, and that identity
     only holds if the offsets are measured against the track itself. */
  position: relative;
  /* Half a viewport of lead-in and run-out, less half a cell. Without it
     the first and last cells physically cannot reach the middle of the
     screen — there is nothing left to scroll — so cells 01, 02 and the
     final pair could never light up or be brought into focus. The gutter
     is the floor, for narrow windows where half a viewport is smaller. */
  padding: clamp(10px, 2svh, 22px) max(var(--gut), calc(50vw - var(--cell) / 2))
           clamp(20px, 3svh, 34px);
  display: flex;
  align-items: flex-start;
  gap: clamp(14px, 1.8vw, 30px);
  width: max-content;
  /* A drag across pictures otherwise starts a text selection or a native
     image drag, both of which abort the gesture halfway. */
  user-select: none;
  -webkit-user-select: none;
}
.pd-mt-cell {
  --near: 0;
  flex: 0 0 auto;
  width: var(--cell);
}
.pd-mt-plate {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  /* Deliberately NOT `overflow: hidden`. The shadow below is a child, and a
     clipping plate would cut it off entirely. The children round themselves
     with `border-radius: inherit` instead — the image fills the box exactly,
     so the result is identical. */
  border: 0;
  padding: 0;
  border-radius: 5px;
  background: #e7e2d8;
  font: inherit;
  color: inherit;
  cursor: zoom-in;
  scale: calc(.955 + var(--near) * .07);
  translate: 0 calc(var(--near) * -10px);
  /* Shadow is a static, already-rasterised layer underneath (::before) whose
     opacity is faded, not an animated box-shadow. Interpolating a shadow's
     blur radius and alpha makes the browser re-rasterise the shadow for all
     eight plates on every frame of a drag — paint work no compositor can
     skip. Fading a ready layer costs nothing by comparison, and eight of
     these move at once. */
  will-change: scale, translate;
}
/* The shadow: rasterised once at full strength, thereafter only faded. */
.pd-mt-plate::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 18px 44px rgba(20, 16, 10, .22);
  opacity: calc(.34 + var(--near) * .66);
  z-index: -1;
  pointer-events: none;
}
.pd-reel.is-grabbing .pd-mt-plate { cursor: grabbing; }
.pd-mt-plate:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.pd-mt-plate img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: inherit;
}
/* A veil that lifts as the cell reaches the middle — distance, without a
   filter and its repaint cost. */
.pd-mt-plate::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--paper);
  opacity: calc(.28 - var(--near) * .28);
  pointer-events: none;
}
.pd-mt-meta {
  display: block;
  margin-top: 10px;
  padding-top: 9px;
  border-top: 1px solid var(--line);
  font-size: .58rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--grey);
  font-variant-numeric: tabular-nums;
}
.pd-mt-meta b {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1rem;
  letter-spacing: 0;
  color: var(--ink);
  opacity: calc(.42 + var(--near) * .58);
  margin-right: 5px;
}

.pd-matter-foot {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 34px);
}
.pd-matter-foot .pd-rail { flex: 1 1 auto; height: 1px; }
.pd-reel-hint {
  flex: 0 0 auto;
  font-size: .58rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--grey);
}


/* ============================================================
   6. ONWARD — the pager
   ============================================================ */
.pd-onward {
  position: relative;
  background: #0a0a0a;
  color: #fff;
}

.pd-prev-line {
  display: flex;
  align-items: baseline;
  gap: clamp(14px, 2.4vw, 32px);
  padding: clamp(18px, 3svh, 30px) var(--gut);
  border-bottom: 1px solid var(--line-inv);
  color: rgba(255, 255, 255, .78);
  transition: color .4s var(--e-power);
}
.pd-prev-line:hover { color: #fff; }
.pd-prev-lbl {
  flex: 0 0 auto;
  font-size: .58rem;
  font-weight: 500;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--accent-inv);
}
.pd-prev-name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1rem, 1.8vw, 1.5rem);
  line-height: 1.1;
  min-width: 0;
}

.pd-next-link {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: clamp(340px, 62svh, 620px);
  overflow: hidden;
  color: #fff;
}
.pd-next-bg {
  position: absolute;
  inset: -10% 0;
  background-size: cover;
  background-position: center;
  /* Same blend-instead-of-filter trade as .pd-voice-bg above. The hover
     part-colours the plate by thinning the grey it blends against, so the
     transition stays on background-color and never re-enters the filter
     pipeline. */
  background-color: rgba(128, 128, 128, 1);
  background-blend-mode: luminosity;
  opacity: .42;
  transition: opacity .8s var(--e-power), background-color .8s var(--e-power);
  will-change: transform;
}
.pd-next-link:hover .pd-next-bg { opacity: .58; background-color: rgba(128, 128, 128, .35); }
.pd-next-veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(10, 10, 10, .55), rgba(10, 10, 10, .82));
}
.pd-next-inner {
  position: relative;
  z-index: 2;
  width: 100%;
  padding: clamp(34px, 6svh, 76px) var(--gut) clamp(30px, 5svh, 64px);
}
.pd-next-eyebrow {
  display: block;
  font-size: .58rem;
  font-weight: 500;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .66);
  margin-bottom: clamp(12px, 2svh, 20px);
}
.pd-next-name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.1rem, 6.4vw, 5.4rem);
  line-height: .98;
  letter-spacing: -.018em;
  margin: 0;
}
.pd-next-cat {
  display: block;
  margin-top: 14px;
  font-size: .62rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .6);
}
.pd-next-cta {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  margin-top: clamp(20px, 3.4svh, 38px);
  font-size: .68rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent-inv);
}
.pd-next-cta span { position: relative; padding-bottom: 5px; }
/* The underline wipes out to the right and back in from the left — the
   house hover, identical on the projects cards and the close. */
.pd-next-cta span::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: var(--accent-inv);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform .5s var(--e-power);
}
.pd-next-link:hover .pd-next-cta span::after {
  transform: scaleX(1);
  transform-origin: left;
}
.pd-next-cta svg { transition: transform .5s var(--e-power); }
.pd-next-link:hover .pd-next-cta svg { transform: translateX(8px); }

/* --- LIGHTBOX --- */
#lightbox {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: rgba(0,0,0,0.92);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s;
}
#lightbox.open {
  opacity: 1;
  pointer-events: auto;
}
.lightbox-close {
  position: absolute;
  top: 24px;
  right: 24px;
  background: none;
  border: none;
  color: #fff;
  font-size: 2rem;
  cursor: pointer;
  z-index: 10;
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.3s;
}
.lightbox-close:hover {
  transform: rotate(90deg);
}
.lightbox-img {
  max-width: 90vw;
  max-height: 85vh;
  object-fit: contain;
  border-radius: 4px;
}

/* ============================================================
   RESPONSIVE
   ------------------------------------------------------------
   Only two things actually change with width, and neither of them
   is a mechanism: the two-column bands become one column, and the
   sticky plate stands down in favour of a picture inside each note
   (the plate has nothing to hold still against once the notes are
   full-width). The approach chapter is still driven by scroll and
   the reel is still a native scroller at every size — no width
   decides who gets the better version of this page.
   ============================================================ */
@media (max-width: 1024px) {
  .pd-brief-body,
  .pd-ap-shell { grid-template-columns: minmax(0, 1fr); }

  /* THE APPROACH, NARROW. The chapter used to stand down here: plate
     hidden, a picture dropped into each note, nothing driven by scroll.
     That gave a phone a plain stack where a laptop got the chapter, so
     the two readers were not reading the same page.

     Side by side is genuinely impossible under ~1025px — a 4:5 plate
     next to 46ch of text leaves neither enough room. So the composition
     turns ninety degrees instead of switching off: the plate sticks
     across the top, the notes run underneath it, and the same single
     writer in project.js swaps the image, the numeral and the rail. No
     second mechanism, no second driver — only a different axis. */
  .pd-ap-shell { gap: 0; }

  .pd-ap-stage {
    display: grid;
    position: sticky;
    /* Under the nav, which hides on scroll-down but must not overlap the
       plate on the way back up. */
    top: calc(var(--nav-h, 64px) + 8px);
    z-index: 2;
    gap: 10px;
    /* The paper behind it: the notes scroll under the plate, and without
       a ground they would show through the gap beside the rail. Bled to
       the gutter edges so nothing slips past on either side. */
    padding: 10px var(--gut) 12px;
    margin: 0 calc(var(--gut) * -1);
    background: var(--paper);
  }

  /* Landscape rather than portrait: a portrait plate pinned to the top of
     a phone leaves almost no room for the note it is illustrating. 16:10
     holds the picture legible inside roughly a third of the screen. */
  .pd-ap-frame {
    aspect-ratio: 16 / 10;
    max-height: 34svh;
  }

  /* Each note takes a screen, so exactly one is ever the nearest to the
     centre — the same condition the plate swap depends on at full width.
     The floor keeps a long note from being clipped on a short window. */
  .pd-note {
    min-height: calc(62svh - var(--nav-h, 64px));
    padding: clamp(20px, 4svh, 34px) 0;
    justify-content: center;
  }
  /* The last note has no note after it to scroll against, so on its own
     it would never reach the centre of the screen and the chapter would
     end on note 02. The tail gives it the room to get there. */
  .pd-notes > .pd-note:last-child { padding-bottom: 34svh; }

  .pd-title { max-width: 18ch; }
  .pd-lede { max-width: 52ch; }
}

@media (max-width: 640px) {
  /* One column only once a second one would be narrower than a phone
     thumbnail. The projects index holds two cards across down to 768 and
     the frames here match it, so a tablet reads the same board on both
     pages rather than a long single file on one of them. */
  .pd-grid { columns: 1; }
  .pd-fr-item { margin-bottom: clamp(26px, 5svh, 48px); }

  .pd-spec-row { grid-template-columns: minmax(0, 1fr); gap: 6px; }
  .pd-voice-inner { padding: clamp(28px, 5svh, 44px) clamp(20px, 6vw, 34px); }
  .pd-matter-foot { flex-direction: column; align-items: stretch; gap: 14px; }
  .pd-reel-hint { text-align: right; }
  .pd-prev-line { flex-direction: column; gap: 8px; }
}

/* A short landscape window (a laptop at 700px, a phone on its side) has no
   room for a 56svh note or a 62svh plate. Both shrink rather than clip. */
@media (max-height: 620px) and (min-width: 1025px) {
  .pd-note { min-height: 76svh; }
  .pd-next-link { min-height: 82svh; }
}


/* ============================================================
   REDUCED MOTION
   ------------------------------------------------------------
   Everything that hides itself for an entrance is gated behind
   html.pd-ready, which project.js only adds when it is going to
   animate — so this block does not have to unhide anything. What
   it does is stop the things that move on their own: the two
   depth fields, the plate cross-fade and the hover scales.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .pd-note,
  .pd-note-t,
  .pd-ap-frame img,
  .pd-fr-media img,
  .pd-fr-media::after,
  .pd-next-bg,
  .pd-prev-line,
  .pd-next-cta span::after,
  .pd-next-cta svg { transition: none; }

  .pd-note { opacity: 1; }
  html.pd-ready .pd-note { opacity: 1; }
  .pd-ap-frame img { transform: none; }
  .pd-note.is-on .pd-note-t { transform: none; }
  .pd-fr-item:hover .pd-fr-media img { transform: none; }

  /* The reel's proximity field is a written number, so neutralise the
     number's effect rather than the (absent) transition. */
  .pd-mt-plate {
    scale: 1;
    translate: none;
  }
  .pd-mt-plate::before { opacity: .6; }
  .pd-mt-plate::after { opacity: 0; }
  .pd-mt-meta b { opacity: 1; }

  .pd-voice-bg,
  .pd-next-bg { inset: 0; }
}
