/* Sebastian Prias, version two.
   One size of type with one larger step, the photographs held on warm paper,
   and two curves for everything that moves: things that arrive ease out on a
   long soft landing (--out); the few things that cross the whole screen go
   slow in, slow out (--inout). Photographs pull into focus as they load. */

:root {
  --paper: oklch(97.2% 0.006 85);
  --ink: oklch(19% 0.008 60);
  --muted: oklch(60% 0.008 65);
  --line: oklch(86% 0.008 75);

  --sans: "Onest", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --pad: 12px;
  --safe-t: env(safe-area-inset-top, 0px);   /* 0 in the browser; the status bar's height when opened from the home screen */
  --head: calc(36px + var(--safe-t));   /* a slim band, as on phones: the screen left to the photographs */
  --big: 26px;
  /* Type: one family, two sizes. Small (the body size) in 500, so it stays
     crisp; everything set large (--big: titles, the About text, the menu,
     the next project) in 400 at one size, one leading, one tracking. Only
     the name over the opening photograph is set larger. */
  --pw: min(26vw, 420px);   /* the project preview's width; the links start just past it */
  --out: cubic-bezier(0.16, 1, 0.3, 1);
  --inout: cubic-bezier(0.77, 0, 0.175, 1);
  --t: 0.7s;
  --settle: cubic-bezier(0.65, 0, 0.35, 1);   /* closing: gathers, glides, settles */
}

*, *::before, *::after { box-sizing: border-box; }
/* The one small voice beside the two sizes: counts, places, labels, in
   spaced capitals with figures of even width. */
.micro { font-size: 10px; font-weight: 500; line-height: 1.3; letter-spacing: 0.09em; text-transform: uppercase; font-variant-numeric: tabular-nums; }
.op-t, .nx-t, .nx-p, .p-dek, .lead, .read, .handles {
  font-size: var(--big); font-weight: 400; line-height: 1.12; letter-spacing: -0.025em;
}
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
/* Home opens on a full-bleed photo. Safari paints the strip behind the clock
   from the body's background, so the body is ink there (it reads as the bezel)
   while the html canvas and main stay paper for overscroll and content.
   Safari fills the strip beneath its bottom bar from the same colour, so
   once the photograph is well behind (.deep, site.js) the body is paper. */
html.ink-top { background: var(--paper); }
body.home { background: var(--ink); }
.deep body.home { background: var(--paper); }
body {
  margin: 0; background: var(--paper); color: var(--ink);
  font: 500 13px/1.3 var(--sans); letter-spacing: 0.01em;
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
}
a { color: inherit; text-decoration: none; }
button { font: inherit; letter-spacing: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
img { display: block; max-width: 100%; }
h1, h2, p, figure, ol, ul { margin: 0; padding: 0; }
ol, ul { list-style: none; }
:focus-visible { outline: 1px solid currentColor; outline-offset: 3px; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.skip { position: absolute; left: var(--pad); top: -40px; z-index: 200; background: var(--ink); color: var(--paper); padding: 6px 10px; }
.skip:focus { top: 8px; }
.top a:hover, .top a[aria-current="page"] { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
/* With the list of projects open, Projects is where you are. */
.veil-open .top a[aria-current="page"]:not(:hover) { text-decoration: none; }
.veil-open .top a[data-nav="projects"] { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }

/* Photographs: a softened miniature of the photograph stands in until the
   file lands (built in, so it shows from the first paint); then the
   photograph fades in over it. A plain fade, nothing else: no blur to
   animate, nothing moving, so nothing to flicker. Photographs already here
   simply show. (site.js moves the stand-in onto the frame and adds .pending.) */
img[style*="--avg"] { background-color: var(--avg); background-size: cover; background-position: center; }

/* The reveal, the first time a photograph comes into view (site.js adds .in;
   --d staggers those arriving together in a diagonal wave, left to right and
   row by row). Quiet and slow, like a print laid down on the table:
   - the card lifts only a few pixels as it fades up;
   - the photograph settles back inside its frame from a touch closer, more
     slowly than the card, so it keeps moving a moment after the card is still;
   - the caption follows a beat later, a plain fade with no movement. */
.js [data-rv] {
  opacity: 0; translate: 0 12px;
  transition: opacity 1.1s var(--out) var(--d, 0s), translate 1.6s var(--out) var(--d, 0s);
}
.js [data-rv].in { opacity: 1; translate: none; }
.js [data-rv].now, .js [data-rv].now * { transition: none !important; }   /* on screen as the page arrives: no reveal */
.js [data-rv] :is(.o-img, .fr, .ph) img { scale: 1.07; }
.js [data-rv].in :is(.o-img, .fr, .ph) img {
  scale: 1;
  transition: scale 2s var(--out) var(--d, 0s);
}
.js [data-rv] :is(.o-cap, .cap) { opacity: 0; transition: opacity 0.9s var(--out) calc(var(--d, 0s) + 0.35s); }
.js [data-rv].in :is(.o-cap, .cap) { opacity: 1; }
/* Hovering leans in, slowly. */
.js [data-rv].in a:hover :is(.o-img, .fr) img, .js [data-rv].in .ph:hover img { scale: 1.025; }

/* ---------------------------------------------------------------- header
   The name and the four links, spread across one line. No ornament. */

.top {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  display: grid; grid-template-columns: calc(var(--pw) + 24px) 1fr; align-items: center;
  height: var(--head); padding: var(--safe-t) var(--pad) 0;
  background: var(--paper);
  view-transition-name: head;
}
.name { white-space: nowrap; }
/* The links spread across the rest of the line: Projects starts just past the
   width of the project preview (which opens beneath the name), Contact ends
   at the right edge. */
.nav { display: flex; justify-content: space-between; white-space: nowrap; }
.menu-btn { display: none; }
.menu { display: none; }

/* ---------------------------------------------------------------- all projects
   A veil over the page with every project as one line of type, starting under
   "Projects" (--list-x from site.js). Hovering a line sets it bold and shows
   its photograph at the left, under the name, crossfading from the last. */

.veil {
  position: fixed; inset: 0; z-index: 35;
  padding: calc(var(--head) + 6px) var(--pad) 40px;
  overflow-y: auto; overscroll-behavior: contain;
  opacity: 0; visibility: hidden;
  transition: opacity 0.45s var(--out), visibility 0s linear 0.45s, background-color 0s linear 0.45s;
}
.veil-open .veil { opacity: 1; visibility: visible; transition: opacity 0.45s var(--out); background: color-mix(in oklch, var(--paper) 97%, transparent); }
.veil.is-page { position: static; opacity: 1; visibility: visible; background: none; min-height: 100dvh; }
/* The index: every project's name a step above the text, the place beside it
   in the small voice; the line you are on in ink, the rest stepping back.
   Its photograph shows at the left, under the name. */
.veil-list { margin-left: var(--list-x, 20%); max-width: none; }
/* On a screen the index is set in the text size, like a magazine's contents
   page: the photograph beside it leads, the names stay quiet. (Phones keep
   the larger size: the list is turned with a thumb there.) */
.veil-list a {
  display: flex; align-items: flex-start; gap: 10px; width: fit-content; padding: 2px 0;
  font-size: 13px; font-weight: 500; line-height: 1.3; letter-spacing: 0.01em;
}
/* The place: in the small text size, as written (Millahue, Chile; AD Middle East), on the name's baseline. */
.veil-list a { align-items: baseline; }
.veil-list .pp { font-size: 13px; font-weight: 500; letter-spacing: 0.01em; }
.veil-list .pp { color: var(--muted); }
.veil-list .pt, .veil-list .pp { transition: color 0.6s var(--out); }
.veil-list:has(a.on, a:hover, a:focus-visible) a:not(.on, :hover, :focus-visible) .pt { color: var(--muted); }   /* stepping back, still easy to read */
.veil-list:has(a.on, a:hover, a:focus-visible) a:not(.on, :hover, :focus-visible) .pp { color: color-mix(in oklch, var(--muted) 78%, var(--paper)); }
/* The line you are on: name and place both in ink. */
.veil-list a:is(.on, :hover, :focus-visible) .pp { color: var(--ink); }
.veil-list a:is(.on, :hover, :focus-visible) .pt, .veil-list a:is(.on, :hover, :focus-visible) .pp { color: oklch(9% 0.006 60); }   /* deeper than the ink */
/* ...and on phones, where the type is light against a bright screen, a hair
   of weight drawn around the letters (no change of width, nothing moves). */
@media (max-width: 760px) { .veil-list a:is(.on, :hover, :focus-visible) .pt { -webkit-text-stroke: 0.35px currentColor; } }
.veil-preview {
  position: fixed; top: calc(var(--head) + 6px); left: var(--pad);
  width: var(--pw); pointer-events: none;
}
.veil-preview img {
  position: absolute; top: 0; left: 0; width: 100%; height: auto;
  opacity: 0; transition: opacity 0.5s var(--out);
}
.veil-preview img.on { opacity: 1; }

/* ---------------------------------------------------------------- home
   The opening: the diptych, edge to edge. As the page scrolls, it draws back
   into a framed photograph on the paper (site.js scales it from this framed
   size, which is where it comes to rest). Then the projects, spread across
   twelve columns with room around each. Scrolling is the browser's own. */

.home main { padding-top: 0; background: var(--paper); }
/* --bleed: on phones the opening is taller than the screen by this much, so
   the photograph runs on beneath Safari's floating toolbar; everything placed
   in it is measured from the visible part. */
.hero { --bleed: 0px; position: relative; overflow-x: clip;   /* the enlarged copy beneath Safari's bars must never widen the page */
   height: calc(100vh + 100vh); height: calc(100lvh + var(--bleed) + 100dvh); }
.hero-pin {
  position: sticky; top: 0; height: 100vh; height: calc(100lvh + var(--bleed)); overflow: hidden;
  display: grid; place-items: center; padding: var(--head) 6vw calc(28px + var(--bleed));
}
.sf { display: grid; justify-items: start; }
.dip { display: flex; transform-origin: 50% 50%; }   /* no will-change: it would freeze the raster at one scale and blur the full-screen state */
.dip picture { display: block; overflow: hidden; }
.dip img { width: auto; height: auto; max-height: calc(100dvh - var(--head) - 96px); max-width: 38vw; }
/* Inside the frame the photographs ease back a little further than the frame
   itself (--z, site.js), so drawing back reads as a camera pulling away, with
   depth, rather than a picture being resized. */
.js .dip img { scale: var(--z, 1); }

/* The arrival, once per visit: the photographs settle in from a touch closer,
   then the name and the links follow, one after another. */
.hero .sf { transition: opacity 1.4s var(--out), scale 2.4s var(--out); }
.intro .hero .sf { opacity: 0; scale: 1.05; }
.intro .top .name, .intro .top .nav a, .intro .top .menu-btn, .intro .hero .big { opacity: 0; }
.intro-out .top .name, .intro-out .top .nav a, .intro-out .top .menu-btn, .intro-out .hero .big { transition: opacity 1.2s var(--out) 0.55s; }
.intro-out .top .nav a:nth-child(2) { transition-delay: 0.63s; }
.intro-out .top .nav a:nth-child(3) { transition-delay: 0.71s; }
.intro-out .top .nav a:nth-child(4) { transition-delay: 0.79s; }

.sf figcaption { width: 0; min-width: 100%; margin-top: 10px; display: flex; gap: 0.5em; white-space: nowrap; }
.sf figcaption a { display: flex; gap: 0.5em; min-width: 0; }
.sf .pl, .cap .pl { color: var(--muted); }
.sf figcaption a:hover .t, .spread a:hover .t { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
.js .hero .sf figcaption { opacity: var(--cap, 0); }
.big { display: none; }
.hero-under, .light, .light-under { display: none; }
@property --edge { syntax: "<length>"; inherits: true; initial-value: 0px; }
.sf figcaption .for-phone { display: none; }

/* Over the full-screen photographs the header is set in white on nothing;
   once they draw back it returns to ink on paper. */
/* The header's paper switches on and off at once (only its type fades):
   Safari colours the strip behind the clock from a header pinned at the top,
   and if it catches the paper half-faded it keeps that colour. */
.top { transition: color 0.5s var(--out); }
/* Opened from the home screen, the page runs up behind the clock, whose
   figures stay white: over the opening photograph that is all there is;
   elsewhere a band of ink sits behind them so they still read. */
.standalone .top::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: var(--safe-t);
  background: var(--ink); transition: opacity 0.5s var(--out);
}
.standalone.on-hero:not(.veil-open) .top::before { opacity: 0; }
.on-hero:not(.veil-open) .top { background: transparent; color: oklch(99% 0.004 85); text-shadow: 0 0 18px oklch(0% 0 0 / 0.25); }
/* (With the projects list open, the paper veil covers the photograph, so the header stays in ink.) */

/* ---------------------------------------------------------------- home: the projects
   Scattered across twelve columns with room around each, large (build.py
   places them: rows of two, two, three, two and one). */
.spread {
  display: grid; grid-template-columns: repeat(12, 1fr); align-items: start;
  column-gap: var(--pad); row-gap: clamp(96px, 12vw, 220px);
  padding: clamp(40px, 6vw, 96px) var(--pad) clamp(120px, 14vw, 220px);
}
.spread li { grid-column: var(--c) / span var(--s); }
.spread li.low { margin-top: clamp(64px, 10vw, 200px); }
.spread a { display: block; }
.fr { display: block; overflow: hidden; }
.spread .fr img { width: 100%; height: auto; }
.spread .cap { display: flex; gap: 0.5em; margin-top: 8px; white-space: nowrap; overflow: hidden; }
.js .spread .fr img { transition: scale 1.2s var(--out); }
.spread a:hover .fr img { scale: 1.025; }

/* Where supported (html.sda, site.js): the drawing back runs as a
   scroll-driven animation, on the browser's own scrolling thread, over the
   stretch of scroll from --r0 to --r1. The script supplies where it starts
   (--tx0, --ty0, --s0, measured); the sine curve is the same as the
   script's, so both feel alike, but here nothing can lag the finger. */
.sda .dip {
  animation: hero-draw linear both;
  animation-timeline: scroll(root);
  animation-range: var(--r0, 0px) var(--r1, 600px);
}
@keyframes hero-draw {
  from { transform: translate3d(var(--tx0, 0px), var(--ty0, 0px), 0) scale(var(--s0, 1)); animation-timing-function: cubic-bezier(0.37, 0, 0.63, 1); }
  to { transform: none; }
}
.sda.js .dip img { animation: hero-z linear both; animation-timeline: scroll(root); animation-range: var(--r0, 0px) var(--r1, 600px); }
@keyframes hero-z { from { scale: 1.09; animation-timing-function: cubic-bezier(0.37, 0, 0.63, 1); } to { scale: 1; } }
.sda .hero .sf figcaption { animation: hero-cap linear both; animation-timeline: scroll(root); animation-range: var(--r0, 0px) var(--r1, 600px); }
@keyframes hero-cap { 0%, 80% { opacity: 0; } 100% { opacity: 1; } }
.sda .big { animation: hero-name linear both; animation-timeline: scroll(root); animation-range: var(--r0, 0px) var(--r1, 600px); }
@keyframes hero-name { 0% { opacity: 1; } 32%, 100% { opacity: 0; } }
.sda .hero-under { animation: hero-under linear both; animation-timeline: scroll(root); animation-range: var(--r0, 0px) var(--r1, 600px); }
@keyframes hero-under { 0% { opacity: 1; } 3%, 100% { opacity: 0; } }
/* (During the arrival these elements keep their own fades.) */
.intro.sda .big, .intro.sda .hero-under { animation: none; opacity: 0; }

/* ---------------------------------------------------------------- overview
   Every photograph, five to a row, sitting on a shared baseline. */

main { padding-top: var(--head); }
.ov {
  display: grid; grid-template-columns: repeat(5, 1fr); align-items: end;
  column-gap: var(--pad); row-gap: clamp(48px, 6vw, 96px);
  padding: 32px var(--pad) 120px;
}
/* Above the sheet: the count, and the one choice the page offers, in the
   same two quiet words as the viewer's Full frame / Contact sheet. */
.ov-bar { display: flex; justify-content: space-between; align-items: baseline; padding: 28px var(--pad) 0; }
.ov-n { color: var(--muted); font-variant-numeric: tabular-nums; }
/* What to show: All, or one kind of place; the choice in ink, the rest in
   grey, the count of what is shown beside them in the small voice. */
.ov-cats { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px; }
.ov-cats button { position: relative; color: var(--muted); transition: color 0.3s var(--out); }
.ov-cats button::after { content: ""; position: absolute; inset: -14px -7px; }
.ov-cats button[aria-pressed="true"] { color: var(--ink); }
@media (hover: hover) { .ov-cats button:hover { color: var(--ink); } }
.ov-c { position: relative; top: -0.62em; margin-left: 0.2em; font-size: 0.62em; letter-spacing: 0.02em; font-variant-numeric: tabular-nums; }
.ov-view { display: flex; gap: 14px; }
.ov-view button { position: relative; color: var(--muted); transition: color 0.3s var(--out); }
.ov-view button::after { content: ""; position: absolute; inset: -14px -7px; }   /* a finger's worth */
.ov-view button[aria-pressed="true"] { color: var(--ink); }
@media (hover: hover) { .ov-view button:hover { color: var(--ink); } }
/* Large: three to a row on a screen (an upright still fits whole), one on a
   phone, with more air between. */
/* Large, on a screen: the photographs as big as the page allows, every one
   whole, never cropped. They run in rows across the full measure, each row
   one height, close together (a hairline of paper between them, so each
   still breathes), as tall as most of the screen: a row of uprights, a
   landscape beside an upright, a single wide view. site.js measures the
   rows exactly; without it each photograph grows in proportion to its shape
   (--ar, build.py), which keeps a row level. */
@media (min-width: 761px) {
  .ov-large .ov { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 36px 8px; padding: 24px var(--pad) 140px; --rh: 62vh; }
  .ov-large .ov > li { flex: var(--ar) 1 calc(var(--ar) * var(--rh)); max-width: calc(var(--ar) * 86vh); min-width: 0; }
  .js.ov-large .ov > li { flex: none; max-width: none; }   /* the rows are measured exactly (site.js) */
  .ov-large .ov::after { content: ""; flex: 999 1 0; order: 99999; }   /* the last row keeps its height rather than stretching */
}
/* The change of size glides each photograph to its new place (site.js). */
html.ov-swap::view-transition-group(*) { animation-duration: 0.75s; animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1); }
html.ov-swap::view-transition-old(*), html.ov-swap::view-transition-new(*) { animation-duration: 0.75s; }
/* A photograph is the same picture at either size, so it simply travels and
   grows: no second, fading copy of itself along the way. */
html.ov-swap::view-transition-old(*.ov) { display: none; }
html.ov-swap::view-transition-new(*.ov) { animation: none; }
.ov a { display: block; }
.o-img { display: block; overflow: hidden; }
.o-img img { width: 100%; height: auto; }
.o-cap { display: grid; margin-top: 6px; }
.o-cap .t, .o-cap .pl { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.o-cap .pl { color: var(--muted); }
.ov a:hover .t { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }

/* ---------------------------------------------------------------- project
   A feature, paced like a magazine's. The opener: the lead photograph edge
   to edge with the name on it (a landscape), or the full height of half the
   page with the name on the facing half (an upright). Then the dek (the name
   in ink, the lines after it in grey) and the credits. Then the photographs,
   a screen at a time (build.py: frames_for): edge to edge; set on the page
   with air around; an upright the full height of half the spread with a
   smaller photograph floating on the facing page; two uprights side by side;
   one small upright alone. A count keeps the place. At the end the next
   project's opener is already there; scrolling on into it carries you into
   that project (site.js). Any photograph opens full screen. */

.proj { padding: 0; }
.ph { display: block; width: 100%; overflow: hidden; cursor: pointer; }
.ph img { width: 100%; height: auto; }
.js .ph img { transition: scale 1.4s var(--out); }
.ph:hover img { scale: 1.02; }

.op { position: relative; display: grid; height: calc(100vh - var(--head)); color: var(--ink); }
.op-img { position: relative; align-self: center; justify-self: center; width: fit-content; }
.op-img .ph { width: auto; }
/* The opening photograph settles as the project arrives: it starts a touch
   closer inside its frame and eases back to its full view over a few
   seconds, as slowly as a breath (site.js adds .settled once it has landed). */
.js .op .ph { display: block; overflow: hidden; }
.js .op .ph img { scale: 1.05; transition: opacity 0.6s ease, scale 4s cubic-bezier(0.25, 0.1, 0.25, 1); }
.js .op.settled .ph img { scale: 1; }
.op-img img { width: auto; max-width: 100vw; max-height: calc(100vh - var(--head)); }
.op-cap { position: absolute; left: var(--pad); bottom: calc(var(--pad) + 8px); display: grid; gap: 4px; }
.op-pl { opacity: 0.72; margin-top: 2px; }
/* A landscape runs to the right edge of the page at its full height, never
   cropped; its name stands apart on the paper to its left, in ink, on the
   line of the photograph's foot. */
.op-l { display: flex; align-items: flex-end; height: auto; }
.op-l .op-img { flex: none; margin-left: auto; }
.op-l .op-img img { max-width: calc(100vw - 200px); max-height: calc(100vh - var(--head) - 44px); }   /* a foot of paper for the count */
.op-l .op-cap { position: static; order: -1; flex: 1; min-width: 0; padding: 0 var(--pad); }
/* A panorama runs the full width of the page, edge to edge and uncropped,
   its name beneath it on the paper. */
.op-w { flex-direction: column; align-items: stretch; }
.op-w .op-img { margin-left: 0; }
/* On the page's margins, so its edges line up with the type above and below
   (the name, Contact, the count), its name beneath on the same left edge. */
.op-w .op-img { padding: 0 var(--pad); }
.op-w .op-img img { width: 100%; max-width: none; max-height: none; }
.op-w .op-cap { order: 0; padding: 14px var(--pad) 0; }
.op-u { grid-template-columns: 1fr 1fr; }
.op-u .op-img { position: static; justify-self: start; }
.op-u .op-img img { max-width: 50vw; max-height: calc(106vh - var(--head)); }   /* an upright opens a touch taller than the screen, running just past the fold, as a magazine's opening page does */
@media (max-height: 760px) { .op-u .op-img img { max-height: calc(100vh - var(--head)); } }   /* on a short screen it stays whole on the first view: cut by the screen's edge it would read as cropped */
.op-u { height: auto; min-height: calc(100vh - var(--head)); }
.op-u .op-cap { left: calc(50% + var(--pad)); bottom: auto; top: calc(100vh - var(--head) - var(--pad) - 8px); translate: 0 -100%; }   /* the name sits at the foot of the screen, though the photograph runs on */

.p-intro {
  display: grid; grid-template-columns: repeat(12, 1fr); column-gap: var(--pad); align-items: end;
  padding: clamp(96px, 16vh, 200px) var(--pad) clamp(120px, 20vh, 240px);
}
.p-dek { grid-column: 1 / 9; color: var(--muted); max-width: 30em; text-wrap: pretty; }
.p-dek-t { color: var(--ink); }
/* Credits, set like a magazine's: a muted label, the name beside it. */
.p-side { grid-column: 10 / 13; display: grid; gap: 22px; }
.p-lt { display: grid; gap: 3px; }
.p-lt .micro, .lt { color: var(--muted); }
.lt { display: block; margin-top: 8px; }
.p-cr { display: grid; grid-template-columns: max-content 1fr; gap: 1px 1.2em; margin: 0; }
.p-cr dt { color: var(--muted); }
.p-cr dd { margin: 0; }

.sp { position: relative; display: grid; height: 100vh; }
.sp + .sp { margin-top: 14vh; }
.sp .cell { position: relative; display: grid; min-width: 0; min-height: 0; scroll-margin-top: var(--head); }
.sp .ph { width: auto; }
.sp img { width: auto; max-width: 100%; }
/* As large as the page allows, never cropped: edge to edge until the
   photograph meets the height of the screen. */
.sp-full .cell { place-items: center; }
.sp-full img { max-height: 100vh; }
.sp-framed, .sp-solo { place-items: center; padding: calc(var(--head) + 4vh) 8vw 8vh; }
.sp-framed img { max-height: calc(100vh - var(--head) - 12vh); }
.sp-solo img { max-height: 86vh; }   /* the last photograph closes the project at full voice */
/* An upright on a page of its own, the full height, from one edge. */
.sp-page { grid-template-columns: 1fr 1fr; }
.sp-page .cell { justify-items: start; align-items: center; }
.sp-page.side-1 .cell { grid-column: 2; justify-items: end; }
.sp-page img { max-height: 100vh; }
.sp-spread { grid-template-columns: 1fr 1fr; }
.sp-spread .cell:first-child { justify-items: start; align-items: center; }
.sp-spread .cell:first-child img { max-height: 100vh; }
.sp-spread.side-1 .cell:first-child { order: 2; justify-items: end; }
.sp-spread .cell:last-child { place-items: center; padding: 10vh 7vw; }
.sp-spread .cell:last-child img { max-height: 60vh; }
.sp-pair { grid-template-columns: 1fr 1fr; gap: 6px; }
.sp-pair .cell:first-child { justify-items: end; align-items: center; }
.sp-pair .cell:last-child { justify-items: start; align-items: center; }
.sp-pair img { max-height: 100vh; }
.ph.vid video { display: block; width: auto; max-width: 100%; height: auto; }

.count {
  position: fixed; right: var(--pad); bottom: 14px; z-index: 30; margin: 0;
  display: flex; gap: 0; align-items: baseline;
  color: var(--muted); transition: opacity 0.6s var(--out);
}
.c-n.roll { vertical-align: baseline; }
/* Thumbs: every photograph of the project at once (the viewer's contact sheet). */
.c-th { position: relative; margin-left: 1.6em; color: var(--muted); letter-spacing: inherit; text-transform: inherit; font: inherit; transition: color 0.3s var(--out); }
.c-th::after { content: ""; position: absolute; inset: -14px -10px; }
.c-th:hover { color: var(--ink); }
.menu-open .count, .veil-open .count { opacity: 0; pointer-events: none; }

.next {
  display: grid; grid-template-columns: repeat(12, 1fr); column-gap: var(--pad); align-content: start;
  margin-top: clamp(140px, 16vw, 280px); padding: 0 var(--pad) clamp(64px, 8vw, 120px);
}
.nx-l { grid-column: 1 / 7; color: var(--muted); }
.nx-t, .nx-p { grid-column: 1 / 7; }
.nx-t { margin-top: 10px; }
.nx-p { color: var(--muted); }
/* Small, at the page's edge: a glimpse of what comes next, never mistaken for this project's own photographs. */
.nx-f { grid-column: 11 / span 2; grid-row: 1 / span 4; display: block; overflow: hidden; max-height: 34vh; width: fit-content; justify-self: end; }
.nx-f img { max-height: 34vh; width: auto !important; max-width: 100%; }
.nx-f img { width: 100%; height: auto; transition: scale 1.2s var(--out); }
.next:hover .nx-f img { scale: 1.025; }
.next:hover .nx-t { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; }

/* ---------------------------------------------------------------- viewer
   A photograph on its own, on the paper, as large as the screen allows. The
   type sits at the edges: the caption top left and Close top right;
   Full frame and Contact sheet bottom left; the count over the project's name at the
   foot, centred (bottom right on phones). In Full frame the corners fade after two still seconds.
   Contact sheet: the bar rises on a sheet of every photograph in the project,
   ten to a row (four on phones), each row on a shared baseline; the large photograph lifts out of the
   way and pales, so the sheet is what you look at. */

.lb { position: fixed; inset: 0; z-index: 100; color: var(--ink); --gh: 0px; }
.lb[hidden] { display: none; }
.lb:focus { outline: none; }
.lb::before {
  content: ""; position: absolute; inset: 0; background: var(--paper);
  opacity: 0; transition: opacity 0.5s var(--out);
}
.lb.open::before { opacity: 1; }
.lb-stage {
  position: absolute; inset: 0; overflow: hidden; touch-action: pan-y pinch-zoom;
  transition: transform 0.8s var(--inout), opacity 0.6s var(--out);
}
.lb.overview .lb-stage { transform: translateY(calc(var(--gh) * -0.5)); opacity: 0.14; }
.lb-img {
  position: absolute; left: 0; top: 0; max-width: none;
  transform-origin: 0 0; will-change: transform;
  user-select: none; -webkit-user-drag: none;
  background-size: cover;
}

.lb-top {
  position: absolute; left: 0; right: 0; top: 0; height: var(--head); padding-top: var(--safe-t);
  display: flex; align-items: center; gap: 24px; padding: 0 var(--pad);
  pointer-events: none;
}
.lb-top > * { pointer-events: auto; }
.lb-alt { color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 60ch; }
.lb-close { margin-left: auto; }

/* The sheet: the bar, and beneath it, below the screen until Contact sheet, the
   thumbnails. The whole sheet slides as one. */
.lb-sheet {
  position: absolute; left: 0; right: 0; bottom: 0;
  transform: translateY(var(--gh));
  transition: transform 0.8s var(--inout);
  pointer-events: none;
}
.lb.overview .lb-sheet { transform: none; }
.lb-bar {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: end; gap: 24px;
  padding: 0 var(--pad) 14px;
}
.lb-bar > * { pointer-events: auto; }
.lb-modes { display: flex; gap: 14px; }
.lb-modes button { color: var(--muted); transition: color 0.3s var(--out); }
.lb-modes button[aria-pressed="true"], .lb-modes button:hover { color: var(--ink); }
.lb-meta { display: grid; justify-items: center; gap: 1px; text-align: center; }
.lb-n { color: var(--muted); font-variant-numeric: tabular-nums; }
.lb-ttl { white-space: nowrap; }
/* Full frame: a strip of every photograph at the bar's end, the one shown
   in ink, the rest held back; it gives way to the sheet in Contact sheet. */
.lb-strip { justify-self: end; display: flex; align-items: flex-end; gap: 5px; max-width: 40vw; overflow-x: auto; scrollbar-width: none; transition: opacity 0.4s var(--out); }
.lb-strip button { flex: none; height: 30px; opacity: 0.38; transition: opacity 0.3s var(--out); }
.lb-strip button:hover, .lb-strip button[aria-current="true"] { opacity: 1; }
.lb-strip img { height: 100%; width: auto; display: block; }
.lb.overview .lb-strip { opacity: 0; pointer-events: none; }

.lb-grid {
  display: grid; grid-template-columns: repeat(10, 1fr); align-items: end; gap: 16px 16px;
  max-height: 52vh; overflow-y: auto; overscroll-behavior: contain;
  padding: 6px var(--pad) calc(20px + env(safe-area-inset-bottom, 0px));
  pointer-events: auto; visibility: hidden; transition: visibility 0s linear 0.8s;
}
.lb.overview .lb-grid { visibility: visible; transition: none; }
.lb-grid button { display: block; padding-bottom: 6px; border-bottom: 1px solid transparent; }
.lb-grid button[aria-current="true"] { border-bottom-color: var(--ink); }
.lb-grid button { width: 100%; }
.lb-grid img { width: 100%; height: auto; }
/* The thumbnails rise one after another as the sheet comes up. */
.lb-grid li { opacity: 0; translate: 0 10px; transition: opacity 0.3s var(--out), translate 0.3s var(--out); }
.lb.overview .lb-grid li { opacity: 1; translate: none; transition: opacity 0.7s var(--out) calc(0.25s + var(--k, 0) * 40ms), translate 0.9s var(--out) calc(0.25s + var(--k, 0) * 40ms); }

/* In Full frame, the corners fade after two still seconds. */
.lb-top, .lb-bar { opacity: 0; transition: opacity 0.45s var(--out); }
.lb.open .lb-top, .lb.open .lb-bar { opacity: 1; transition-delay: 0.25s; }
.lb.open.idle .lb-top, .lb.open.idle .lb-bar { opacity: 0; transition-delay: 0s; }

.lb-arrow { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.has-cursor .lb-stage, .has-cursor .lb-stage * { cursor: none; }
.has-cursor .lb.overview .lb-stage, .has-cursor .lb.overview .lb-stage * { cursor: pointer; }

/* A number that rolls: the old figure leaves upward as the new one rises in
   (downward when going back). */
.roll { display: inline-grid; overflow: hidden; vertical-align: bottom; }
.roll > span { grid-area: 1 / 1; }
.roll .r-in { animation: roll-in 0.6s var(--out) both; }
.roll .r-out { animation: roll-out 0.6s var(--out) both; }
@keyframes roll-in { from { translate: 0 calc(var(--dir, 1) * 100%); } }
@keyframes roll-out { to { translate: 0 calc(var(--dir, 1) * -100%); } }

/* ---------------------------------------------------------------- cursor
   Inside the viewer only: the word "Prev" or "Next", centred on the pointer,
   in the site's own type. Ink over the paper. Over the photograph it reads
   what lies beneath (site.js): white with a faint shadow on dark passages,
   ink with a faint halo on light ones (an x-ray blend would vanish over
   mid-tones). */

.cursor {
  position: fixed; left: 0; top: 0; z-index: 200; width: 0; height: 0;
  pointer-events: none; color: var(--ink);
  opacity: 0; transition: opacity 0.3s var(--out), color 0.35s var(--out);
}
.cursor[data-mode="prev"], .cursor[data-mode="next"] { opacity: 1; }
.cursor[data-tone="light"] { color: oklch(99% 0.004 85); }
.cursor-word {
  position: absolute; left: 0; top: 0; translate: -50% -50%; white-space: nowrap;
  transition: text-shadow 0.35s var(--out), scale 0.4s var(--out);
}
.cursor[data-tone="light"] .cursor-word { text-shadow: 0 0 12px oklch(0% 0 0 / 0.35); }
/* Over a light passage of the photograph (sky, a white wall): ink, with a
   faint pale halo to lift it off any texture. */
.cursor[data-tone="onlight"] .cursor-word { text-shadow: 0 0 10px oklch(99% 0.004 85 / 0.6); }
.cursor.down .cursor-word { scale: 0.92; transition-duration: 0.15s; }

/* ---------------------------------------------------------------- information */

/* About, on a screen: one spread that fits it. The words start level with
   the portrait, close under the header; the three columns rest at the foot
   of the screen. A longer list simply lets the page run on. */
.info {
  display: grid; grid-template-columns: repeat(12, 1fr); column-gap: var(--pad); align-items: start;
  min-height: calc(100svh - var(--head));
  padding: clamp(28px, 3.2vw, 56px) var(--pad) clamp(28px, 3vw, 48px);
}
.i-text { grid-column: 1 / 9; align-self: stretch; display: flex; flex-direction: column; }
.i-text .lead { margin-bottom: clamp(48px, 7vh, 88px); }
/* The portrait, a quarter of the page, and beneath it his words, in ink at the
   text size: a quote set as a caption, its opening mark hung in the margin. */
.i-por { grid-column: 10 / 13; position: sticky; top: calc(var(--head) + 6px); }
.i-ph { display: block; }
.i-por img { width: 100%; height: auto; }
.i-q { margin: 16px 0 0; max-width: 34em; line-height: 1.42; text-indent: -0.42em; text-wrap: pretty; }
/* About: a step below the titles, so it reads as a passage rather than a headline. */
.lead, .read, .handles { font-size: 21px; line-height: 1.24; letter-spacing: -0.018em; }
@media (max-width: 760px) { .lead, .read, .handles { font-size: 18px; line-height: 1.26; letter-spacing: -0.012em; } }
.lead { max-width: 30em; text-wrap: pretty; }
.lead.two { color: var(--muted); }
.lead.two .ink { color: var(--ink); }
/* Three quiet columns: contact, published in (and clients), before photography. */
.i-cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--pad); margin-top: auto; }
.i-cols h2 { color: var(--muted); margin-bottom: 10px; }
.i-cols .i-h2 { margin-top: 28px; }
.i-cols li { margin-bottom: 2px; }
.i-d { display: block; color: var(--muted); }
.i-cols p.i-d { margin-bottom: 6px; }
.i-cols a:hover { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
.nf-go { margin-top: 1.4em; }
.nf-go a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
.i-time time { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- page changes
   Between pages the old one fades away quickly and the new one comes up
   beneath it; the header stays where it is. A photograph chosen in the
   Overview travels to its place in the project. Browsers without
   cross-document transitions simply change page. */

@view-transition { navigation: auto; }
::view-transition-old(root) { animation: vt-out 0.3s var(--out) both; }
::view-transition-new(root) { animation: vt-in 0.7s var(--out) 0.08s both; }
::view-transition-group(photo) { animation-duration: 0.9s; animation-timing-function: var(--out); }
::view-transition-old(photo), ::view-transition-new(photo) { animation-duration: 0.9s; height: 100%; object-fit: cover; }
/* Shown only on a screen, or only on a phone (a project's opener, build.py). */
@media (min-width: 761px) { .phone-only { display: none !important; } }
@media (max-width: 760px) { .wide-only { display: none !important; } }
@keyframes head-in { from { opacity: 0; } }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; } }

/* ---------------------------------------------------------------- phones
   The name and "Menu". Menu pushes the whole page down by half the screen to
   show the links above it. */

@media (max-width: 760px) {
  :root { --pad: 15px; --head: calc(36px + var(--safe-t)); --big: 22px; }   /* a slim band: the name and Menu close to the top, the screen left to the photographs */
  body { font-size: 12px; }

  .top { display: flex; }
  .nav { display: none; }
  .menu-btn { display: block; margin-left: auto; position: relative; touch-action: manipulation; }
  .menu-btn::after { content: ""; position: absolute; inset: -16px -15px -16px -24px; }   /* the whole corner answers, not just the word */
  .menu-btn.roll { display: inline-grid; justify-items: end; }   /* the two words stack while one rolls over the other */
  .name { transition: opacity 0.5s var(--out); }
  .at-cover .top .name { opacity: 0; }
  /* Over the opening photograph the header sits on the page rather than
     pinned to the screen: Safari paints a flat band behind its clock for
     anything pinned at the top, but lets the photograph itself run up there
     otherwise. It leaves with the page as the photograph draws back, and
     comes back pinned, on paper, once it has (its words fading in). */
  .on-hero:not(.head-up) .top { position: absolute; top: var(--lead, 0px); }
  html:not(.on-hero) .home .top > * { animation: head-in 0.6s var(--out) both; }

  /* The menu lies beneath the page, like a drawer: Menu slides the whole page
     down a little under half the screen and uncovers it, the panel drifting
     into place as it is uncovered; the words stand still on it. Opening uses
     the curve iOS uses for its sheets (quick to answer, a long soft landing).
     Closing gathers, glides and settles (an even in-and-out), so the page
     comes back up like something set down, never snapped shut. A page chosen
     from the menu opens with the drawer still open and then closes it the
     same way (menu-arrive, site.js). When closed the panel is truly gone
     (hidden, untouchable): its links can never catch a tap meant for the
     page, and Safari never sees paper pinned at the top. */
  .menu {
    display: flex; flex-direction: column; position: fixed; top: 0; left: 0; right: 0; z-index: 0;
    height: 44dvh; padding: calc(14px + env(safe-area-inset-top, 0px)) var(--pad) 14px;
    font-size: 30px; font-weight: 400; line-height: 1.1; letter-spacing: -0.028em;
    background: var(--paper);
    visibility: hidden; pointer-events: none;
    transform: translateY(-24px);
    transition: transform 0.8s var(--settle), visibility 0s linear 0.8s;
  }
  .menu-open .menu {
    visibility: visible; pointer-events: auto; transform: none;
    transition: transform 0.9s cubic-bezier(0.32, 0.72, 0, 1);
  }
  main.push { position: relative; z-index: 1; background: var(--paper); }   /* the page covers the drawer */
  .menu-gone .menu { display: none; }   /* fully removed once closed: the only way Safari lets go of its paper */
  .menu-home { color: var(--muted); }
  .menu a { display: block; width: fit-content; }
  .push { transition: transform 0.8s var(--settle); }
  .menu-open .push { transform: translateY(44dvh); transition: transform 0.9s cubic-bezier(0.32, 0.72, 0, 1); }
  .menu-arrive .menu, .menu-arrive .push, .menu-arrive .veil { transition: none !important; }
  html.menu-open { overflow: hidden; scroll-snap-type: none !important; }   /* or the snap would chase the pushed page back up */

  .veil { padding-top: calc(var(--head) + 10px); }
  /* Over a page on a phone, as on a screen: paper with the faintest trace
     of the page beneath. */
  .veil-open .veil { background: color-mix(in oklch, var(--paper) 97%, transparent); }
  html.veil-open { overflow: hidden; }
  .veil-list { margin-left: 0; }
  .veil-list a { gap: 0.6em; padding: 0; font-size: 18px; font-weight: 400; line-height: 1.22; letter-spacing: -0.015em; }
  .veil-list .pp { font-size: 12px; }
  /* Touching a line shows its photograph at the foot of the screen; sliding
     the finger down the list runs through them, like a contact sheet. */
  .veil-preview { top: auto; left: auto; right: var(--pad); bottom: calc(24px + env(safe-area-inset-bottom, 0px)); width: 46vw; }
  .veil-preview img { top: auto; left: auto; bottom: 0; right: 0; }
  /* The photograph opens the project it shows, just as its line does. */
  .veil-preview.live { pointer-events: auto; cursor: pointer; transition: scale 0.5s var(--out); }
  .veil-preview.live:active { scale: 0.985; transition-duration: 0.15s; }


  /* Phones: the opening is one upright photograph with the name on it in
     white, set from the left edge across about three quarters of the line
     (site.js); it draws back the same way. */
  /* --lead: on phones the page opens scrolled down by this much, and the
     room above holds the top of the photograph. Safari only shows page
     content behind its clock once the page is scrolled, so this is what lets
     the photograph run up behind it. Padding, not a margin, so it can never
     collapse out of main. */
  .home { --lead: 72px; }
  .hero { --bleed: 160px; }
  .home main { padding-top: var(--lead); }
  /* Safari draws its own strip beneath its floating search bar, and shows
     only ordinary page content there (not pinned layers). So behind the
     pinned opening sits the same photograph in the page's normal flow, set
     exactly as the full-screen state, to fill that strip. It steps aside as
     soon as the photograph starts to draw back (site.js), so it is only ever
     seen beneath the bar. */
  .hero-under {
    display: block; position: absolute; left: 0; right: 0; top: calc(-1 * var(--lead));
    height: calc(100lvh + var(--bleed) + var(--lead));   /* also fills the room above */
    background-size: cover; background-position: center;
    scale: 1.09;                        /* the same counter-zoom the photograph wears at full size (--z) */
    transition: opacity 0.3s var(--out);
  }
  /* The first visit: the lights come up. The photograph emerges as the room
     looks at dusk, warm and low, the name bright on it; then the exposure
     rises to the photograph itself. The dusk is multiplied over the picture
     (so it keeps its depth and colour, never a grey veil), and it lifts on a
     feather far longer than the screen: the top of the frame brightens a
     breath before the floor, but no edge is ever seen, only the light
     arriving. One gradient drawn twice to the same measure: over the
     photograph (.light) and over its copy behind Safari's clock and beneath
     its bar (.light-under, offset by the room above, --o), so the whole
     screen lights as one. (site.js starts it.) Only here, once a visit. */
  .light, .light-under {
    display: block; position: absolute; left: 0; right: 0; pointer-events: none;
    mix-blend-mode: multiply;
    --edge: calc(100lvh + var(--bleed) + 170vh);      /* lit: the dusk has passed below */
    --dusk: oklch(54% 0.03 58);
    --y: calc(var(--edge) + var(--o, 0px));
    background: linear-gradient(to bottom,
      transparent calc(var(--y) - 150vh),
      color-mix(in oklch, var(--dusk) 6%, transparent) calc(var(--y) - 112vh),
      color-mix(in oklch, var(--dusk) 22%, transparent) calc(var(--y) - 82vh),
      color-mix(in oklch, var(--dusk) 45%, transparent) calc(var(--y) - 57vh),
      color-mix(in oklch, var(--dusk) 70%, transparent) calc(var(--y) - 33vh),
      color-mix(in oklch, var(--dusk) 88%, transparent) calc(var(--y) - 15vh),
      var(--dusk) var(--y));
    transition: --edge 2.8s cubic-bezier(0.32, 0, 0.18, 1);
  }
  .light { top: 0; height: 100%; }
  .light-under { top: calc(-1 * var(--lead)); height: calc(100lvh + var(--bleed) + var(--lead)); --o: var(--lead); }
  .lights .light, .lights .light-under { --edge: -6vh; }   /* the whole room at dusk */
  .lights-fast .light, .lights-fast .light-under { transition-duration: 0.8s; }
  /* The name: a touch brighter in the dark, settling as the light reaches it. */
  .big { transition: color 1.6s var(--settle) 0.6s, text-shadow 1.6s var(--settle) 0.6s; }
  .lights .big { color: oklch(100% 0 0); text-shadow: 0 0 24px oklch(100% 0 0 / 0.24), 0 0 1px oklch(100% 0 0 / 0.3); }
  .lights-fast .big { transition-delay: 0s; transition-duration: 0.6s; }
  .intro-out .hero .big { transition: opacity 1.2s var(--out) 0.35s, color 1.6s var(--settle) 0.6s, text-shadow 1.6s var(--settle) 0.6s; }
  .intro .hero-under { opacity: 0; }   /* it arrives with the photograph */
  .intro-out .hero-under { transition: opacity 1.4s var(--out); }
  .sf figcaption .for-wide { display: none; }
  .sf figcaption .for-phone { display: flex; }

  .hero-pin { padding: var(--head) var(--pad) calc(24px + var(--bleed)); }
  .dip picture:not(:first-child) { display: none; }
  .dip img { max-width: calc(100vw - 2 * var(--pad)); max-height: calc(100dvh - var(--head) - 70px); }
  .big {
    display: block; position: absolute; left: var(--pad); right: var(--pad); top: calc((100% - var(--bleed)) * 0.58);
    color: oklch(99% 0.004 85); font-weight: 400; line-height: 0.95; letter-spacing: -0.03em; white-space: nowrap;
    font-size: 11vw; text-shadow: 0 1px 24px oklch(0% 0 0 / 0.18);
    opacity: var(--name, 1); pointer-events: none;
  }


  .ov { grid-template-columns: repeat(2, 1fr); row-gap: 40px; padding-top: 30px; }
  .ov-bar { padding-top: 22px; }
  .ov-large .ov { grid-template-columns: 1fr; row-gap: 56px; }

  .spread { margin-top: -160px; }   /* take back the --bleed: the spread starts where the screen did */
  .spread { grid-template-columns: repeat(12, 1fr); column-gap: 10px; row-gap: 64px; padding-top: 32px; }
  .spread li:nth-child(odd) { grid-column: 1 / span 9; }   /* three quarters of the width, left and right in turn */
  .spread li:nth-child(even) { grid-column: 4 / span 9; }
  .spread li.low { margin-top: 0; }

  /* The project, in one column: the opener, then each photograph at its own
     size, edge to edge or set in from the margins, with room between. */
  .op, .op-u { display: block; height: auto; }
  .op-img, .op-u .op-img { position: relative; width: auto; }
  .op-img .ph { width: 100%; }
  .op-img img, .op-u .op-img img, .op-l .op-img img, .op-w .op-img img { width: 100%; max-width: none; max-height: none; }   /* phones: every opener edge to edge */
  .op-l .op-img { margin-left: 0; }
  .op-cap, .op-u .op-cap, .op-l .op-cap { position: static; top: auto; translate: none; padding: 14px var(--pad) 0; color: var(--ink); text-shadow: none; }
  .p-intro { display: block; padding: 40px var(--pad) 96px; }
  .p-dek-t { display: none; }   /* the name is just above, under the opener */
  .p-cr { margin-top: 22px; }
  .sp, .sp .cell { display: block; height: auto; }
  .sp + .sp { margin-top: 18vw; }
  .sp .ph { width: 100%; }
  .sp img { width: 100%; max-width: none; max-height: none; }
  .sp-framed, .sp-solo { padding: 0 var(--pad); }
  .sp-solo .ph { width: 84%; margin: 0 auto; }
  .sp-spread .cell:last-child { padding: 18vw var(--pad) 0; }
  .sp-spread .cell:last-child .ph { width: 62%; margin-left: auto; }
  .sp-spread.side-1 .cell:last-child .ph { margin-left: 0; }
  .sp-pair { display: grid; gap: 4px; }
  .sp-pair .cell { display: block; }
  .count { top: calc(var(--safe-t) + 13px); bottom: auto; right: auto; left: 50%; translate: -50% 0; z-index: 41; }
  .c-th { margin-left: 1.2em; }
  .p-side { margin-top: 24px; }
  /* Phones: Small and Large on a line of their own at the right, so the
     choices beneath have the full width. */
  .ov-bar { display: flex; flex-direction: column; }
  .ov-view { order: -1; align-self: flex-end; gap: 18px; margin-bottom: 14px; }
  .ov-cats { gap: 10px 18px; }
  .next { grid-template-columns: repeat(6, 1fr); column-gap: 10px; margin-top: 110px; }
  .nx-l, .nx-t, .nx-p { grid-column: 1 / 5; }
  .nx-f { grid-column: 5 / 7; }

  .lb-alt { display: none; }
  .lb-bar { padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px)); }
  .lb.overview .lb-bar { padding-bottom: 14px; }
  /* Phones: the contact sheet is four to a row, each row on one baseline. */
  .lb-bar { grid-template-columns: auto 1fr; }
  .lb-meta { justify-items: end; text-align: right; }
  .lb-strip { display: none; }
  .lb-grid { grid-template-columns: repeat(4, 1fr); gap: 14px 10px; max-height: 56vh; }

  .info { display: flex; flex-direction: column; min-height: 0; padding: 40px var(--pad) 96px; }
  .i-text { display: contents; }   /* phones: who he is, his portrait and words, then the columns */
  .i-por { order: 1; }
  .i-cols { order: 2; }
  .i-text .lead { margin-bottom: 0; }
  .i-por { position: static; margin-top: 48px; }
  .i-ph { width: 72%; }
  .i-q { margin-top: 14px; }
  .i-cols { grid-template-columns: 1fr; gap: 32px; margin-top: 56px; }
}

@media (prefers-reduced-motion: reduce) {
  .js .op .ph img { scale: 1; }
  *, *::before, *::after { transition-duration: 0s !important; transition-delay: 0s !important; animation-duration: 0s !important; animation-delay: 0s !important; }
  .js [data-rv] { opacity: 1; translate: none; }
}
@media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }

/* Last, so it wins: a photograph still on its way is transparent over its
   stand-in, and fades in once decoded. */
.js img.pending { opacity: 0; transition: opacity 0.6s ease, scale 1.2s var(--out); }
.js img.pending.is-in { opacity: 1; }
