/* rezza-site-v2 · style.css
   one frame, hairline cells, dark only. every space is a step on the 8px grid,
   every line-height a whole number of grid units. no glows, no shadows, no
   rounded corners: the images carry the colour, the structure stays quiet.
   the edge is said three ways: a near-black ground with a fine grain, crosses
   on the frame's corners, and bright ticks on every plate's corners. */

/* ---- fonts (served from this site, no longer from Fontshare or Google) ----
   font-display: block, and every page head preloads the faces on its first
   screen, so text waits a moment for its own face instead of showing a
   stand-in and switching. the latin-ext files only download if a page ever
   uses those letters. rename a file if it ever changes: they are cached a year. */

@font-face {
  font-family: "Clash Grotesk";
  src: url("assets/fonts/clash-grotesk-500.woff2") format("woff2");
  font-weight: 500;
  font-display: block;
}
@font-face {
  font-family: "Clash Grotesk";
  src: url("assets/fonts/clash-grotesk-600.woff2") format("woff2");
  font-weight: 600;
  font-display: block;
}
@font-face {
  font-family: "Inter";
  src: url("assets/fonts/inter-v20-latin.woff2") format("woff2");
  font-weight: 400 600;
  font-display: block;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  src: url("assets/fonts/inter-v20-latin-ext.woff2") format("woff2");
  font-weight: 400 600;
  font-display: block;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Space Mono";
  src: url("assets/fonts/space-mono-v17-latin.woff2") format("woff2");
  font-weight: 400;
  font-display: block;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Space Mono";
  src: url("assets/fonts/space-mono-v17-latin-ext.woff2") format("woff2");
  font-weight: 400;
  font-display: block;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---- tokens (Radix grays, dark, plus the custom green kept for links) ---- */

:root {
  --ink-0: #090909;
  --ink-1: #0f0f0f;
  --ink-2: #161616;
  --ink-line: #262626;
  --ink-line-hi: #3a3a3a;
  --ink-mark: #8c8c8c;
  --ink-mark-hi: #e6e6e6;
  --ink-muted: #9d9d9d;
  --ink-faint: #858585;
  --ink-text: #eeeeee;
  --green-11: #98c6a0;

  --bg: var(--ink-0);
  --plate: var(--ink-1);
  --line: var(--ink-line);
  --text: var(--ink-text);
  --muted: var(--ink-muted);
  --link: var(--green-11);
  --mark: var(--ink-mark);

  /* the grain: white noise at a whisper, tiled, never over the work itself */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .6 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");

  --s1: 8px;
  --s2: 16px;
  --s3: 24px;
  --s4: 32px;
  --s6: 48px;
  --s8: 64px;
  --s12: 96px;

  --inset: var(--s3);
  --gutter: var(--s3);
  --frame: 1184px;
  --measure: 38rem;

  --head: "Clash Grotesk", system-ui, sans-serif;
  --body: "Inter", system-ui, sans-serif;
  --mono: "Space Mono", ui-monospace, "SF Mono", monospace;

  color-scheme: dark;
}

@media (max-width: 640px) {
  :root {
    --inset: var(--s2);
    --gutter: var(--s2);
  }
}

/* ---- base ---- */

* { box-sizing: border-box; }

html { background: var(--bg); }

/* the body stays transparent so the grain (z-index -1) sits on the canvas,
   above the black and under everything else */
body {
  margin: 0;
  padding: var(--gutter);
  background: transparent;
  color: var(--text);
  font: 400 16px/24px var(--body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { display: block; max-width: 100%; height: auto; }

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

p { margin: 0; text-wrap: pretty; }

h1, h2 { text-wrap: balance; }

:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }

::selection { background: var(--ink-text); color: var(--ink-0); }

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background-image: var(--grain);
  opacity: 0.07;
  pointer-events: none;
}

.label {
  font: 400 11px/16px var(--mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

/* section names are real headings (h2) that look like labels */
h2.label, h3.label { margin: 0; text-wrap: initial; }

/* ---- the frame: one box, split by hairlines ---- */

.frame {
  position: relative;
  max-width: var(--frame);
  margin: 0 auto;
  border: 1px solid var(--line);
}

/* registration crosses on the frame's four corners, centred on the corner point */
.frame::before {
  content: "";
  position: absolute;
  inset: -8px;
  pointer-events: none;
  background:
    linear-gradient(var(--mark), var(--mark)) 0 7px / 15px 1px no-repeat,
    linear-gradient(var(--mark), var(--mark)) 7px 0 / 1px 15px no-repeat,
    linear-gradient(var(--mark), var(--mark)) 100% 7px / 15px 1px no-repeat,
    linear-gradient(var(--mark), var(--mark)) calc(100% - 7px) 0 / 1px 15px no-repeat,
    linear-gradient(var(--mark), var(--mark)) 0 calc(100% - 7px) / 15px 1px no-repeat,
    linear-gradient(var(--mark), var(--mark)) 7px 100% / 1px 15px no-repeat,
    linear-gradient(var(--mark), var(--mark)) 100% calc(100% - 7px) / 15px 1px no-repeat,
    linear-gradient(var(--mark), var(--mark)) calc(100% - 7px) 100% / 1px 15px no-repeat;
}

/* every plate: a dim outline, sharpened by bright ticks on its four corners */
.plate { position: relative; }

.plate::after {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 2;
  pointer-events: none;
  --t: var(--mark);
  --l: 12px;
  background:
    linear-gradient(var(--t), var(--t)) 0 0 / var(--l) 1px no-repeat,
    linear-gradient(var(--t), var(--t)) 0 0 / 1px var(--l) no-repeat,
    linear-gradient(var(--t), var(--t)) 100% 0 / var(--l) 1px no-repeat,
    linear-gradient(var(--t), var(--t)) 100% 0 / 1px var(--l) no-repeat,
    linear-gradient(var(--t), var(--t)) 0 100% / var(--l) 1px no-repeat,
    linear-gradient(var(--t), var(--t)) 0 100% / 1px var(--l) no-repeat,
    linear-gradient(var(--t), var(--t)) 100% 100% / var(--l) 1px no-repeat,
    linear-gradient(var(--t), var(--t)) 100% 100% / 1px var(--l) no-repeat;
}

.frame > * + *,
.stack > * + * { border-top: 1px solid var(--line); }

/* ---- bar ---- */

.bar {
  display: grid;
  grid-template-columns: auto 1fr auto auto auto;
  align-items: stretch;
}

.bar > * {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) var(--inset);
  font: 500 14px/24px var(--body);
}

.bar > * + * { border-left: 1px solid var(--line); }

.bar .home svg { width: 24px; height: 24px; flex: none; }

.bar .where { color: var(--muted); font-weight: 400; }

.bar a:hover,
.bar a[aria-current="page"] { color: var(--link); }

/* below a laptop the role line leaves the bar (it is the page's own first line anyway) */
@media (max-width: 1000px) {
  .bar { grid-template-columns: 1fr auto auto auto; }
  .bar .where { display: none; }
}

/* on a phone the bar keeps the name and About; the email lives in the footer */
@media (max-width: 640px) {
  .bar { grid-template-columns: 1fr auto auto; }
  .bar .where,
  .bar .mail { display: none; }
}

/* the smallest phones keep the mark and drop the name beside it (the link keeps its label) */
@media (max-width: 369px) {
  .bar .home .name { display: none; }
}

/* ---- home: hero ----
   the headline generator. two full-width bars hold the word sets, one on each
   edge of the hero: "I build the" and its words on top, "that makes the" and
   its words at the bottom. between them the sentence on the left and, on the
   right, a plate with the picture of the sentence as a whole, with air around
   it. the slots in the sentence are fixed windows, so the sentence never moves;
   their corner ticks are the brackets. */

.gen-body {
  display: grid;
  grid-template-columns: 3fr 2fr;
}

.gen-block {
  padding: var(--s12) var(--inset) var(--s8);
}

.gen h1 {
  margin: 0;
  font: 600 56px/64px var(--head);
  letter-spacing: -0.025em;
  text-wrap: wrap;
}

/* the picture: a plate with a lot of space around it, inside its own cell.
   the bound is the most room a picture may take (16:9, 400px wide at most);
   the plate inside it is the picture's own shape, filling the bound's width or
   its height, whichever comes first, with the hairline and the ticks on the
   picture's own edge (gen.js sizes it from the picture's natural shape). */
.gen-plate {
  display: grid;
  place-items: center;
  padding: var(--s8) var(--s6);
  border-left: 1px solid var(--line);
}

.gen-plate .bound {
  display: grid;
  place-items: center;
  width: 100%;
  max-width: 400px;
  aspect-ratio: 16 / 9;
}

.gen-plate .plate {
  width: 100%;
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: 16 / 9;
  background: var(--plate);
  outline: 1px solid var(--line);
  transition: width 320ms ease, height 320ms ease;
}

/* two pictures are stacked: a pick crossfades from one to the other (the plate
   eases to the new shape with it); the spin cuts, the plate flickers with the reel */
.gen-plate .plate img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 320ms ease;
}

.gen-plate .plate img.back { opacity: 0; }

.gen-plate .plate.cut,
.gen-plate .plate.cut img { transition: none; }

@media (max-width: 1100px) {
  .gen-body { grid-template-columns: 1fr; }
  .gen-block { padding: var(--s8) var(--inset) var(--s6); }
  .gen-plate { padding: var(--s6) var(--inset); border-left: 0; border-top: 1px solid var(--line); }
}

@media (max-width: 640px) {
  .gen-block { padding: var(--s6) var(--inset); }
  .gen h1 { font-size: 40px; line-height: 48px; }
  .gen-plate { padding: var(--s4) var(--inset); }
}

.slot {
  position: relative;
  display: inline-block;
  box-sizing: content-box;
  width: var(--w);
  height: 1lh;
  padding: 0 0.12em;
  vertical-align: top;
  overflow: hidden;
  cursor: pointer;
}

.reel { display: block; }

.reel > span { display: block; height: 1lh; white-space: nowrap; }

.slot::after {
  content: "";
  position: absolute;
  inset: 0.1em 0 0.06em;
  pointer-events: none;
  --t: var(--mark);
  --l: 0.2em;
  background:
    linear-gradient(var(--t), var(--t)) 0 0 / var(--l) 1px no-repeat,
    linear-gradient(var(--t), var(--t)) 0 0 / 1px var(--l) no-repeat,
    linear-gradient(var(--t), var(--t)) 100% 0 / var(--l) 1px no-repeat,
    linear-gradient(var(--t), var(--t)) 100% 0 / 1px var(--l) no-repeat,
    linear-gradient(var(--t), var(--t)) 0 100% / var(--l) 1px no-repeat,
    linear-gradient(var(--t), var(--t)) 0 100% / 1px var(--l) no-repeat,
    linear-gradient(var(--t), var(--t)) 100% 100% / var(--l) 1px no-repeat,
    linear-gradient(var(--t), var(--t)) 100% 100% / 1px var(--l) no-repeat;
}

.slot:hover::after,
.slot.is-turning::after { --t: var(--ink-mark-hi); }

/* the word bars: one cell per word, the current word's cell lit. the cells
   share the row equally when there is room; when there is not (a tablet, a
   phone) each keeps its own width and the row scrolls sideways, no bar shown,
   and gen.js keeps the lit cell in view. */

.words {
  display: grid;
  grid-template-columns: repeat(var(--n, 8), minmax(max-content, 1fr));
  overflow-x: auto;
  scrollbar-width: none;
}

.words::-webkit-scrollbar { display: none; }

.words-a { border-bottom: 1px solid var(--line); }

.words-b { border-top: 1px solid var(--line); }

.words button {
  padding: var(--s1) var(--s2);
  background: none;
  border: 0;
  border-left: 1px solid var(--line);
  color: var(--ink-faint);
  font: 400 12px/24px var(--mono);
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}

.words button:first-child { border-left: 0; }

.words button:hover { color: var(--text); }

.words button[aria-pressed="true"] { background: var(--ink-2); color: var(--text); }

html:not(.js) .words { display: none; }

/* ---- a row with a label on the left and a note on the right ---- */

.rowlabel {
  display: flex;
  justify-content: space-between;
  gap: var(--s2);
  padding: var(--s1) var(--inset);
}

/* ---- home: the projects, two to a row, as many rows as there are projects ---- */

.work {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

/* the builder marks each card's place: right column, and every row after the first */
.work > .right { border-left: 1px solid var(--line); }

.work > .top { border-top: 1px solid var(--line); }

/* image first: the picture runs edge to edge in its cell, the words sit under
   it. on hover the picture comes alive (motion.js plays a short loop over it). */
.project {
  position: relative;
  display: flex;
  flex-direction: column;
}

.project a.cover::after { content: ""; position: absolute; inset: 0; z-index: 3; }

.project a.cover:focus-visible { outline: none; }

.project:has(a.cover:focus-visible) { outline: 2px solid var(--link); outline-offset: -2px; }

.project .media {
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: var(--plate);
}

.project .media > img,
.project .media > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.project .media > video { opacity: 0; transition: opacity 500ms ease; }

.project.is-live .media > video { opacity: 1; }

.project .media::after { inset: 0; }

.project .meta {
  flex: 1;
  padding: var(--s3) var(--inset) var(--s4);
  border-top: 1px solid var(--line);
}

.project h3 {
  margin: var(--s1) 0;
  font: 600 32px/40px var(--head);
  letter-spacing: -0.01em;
}

.project p { max-width: 30em; color: var(--muted); }

.project:hover h3 { color: var(--link); }

/* the archive closes the wall: one cell across both columns, a strip of slices
   that drifts sideways while the card is live (the strip holds its sequence
   twice, so moving half its width loops without a seam) */
.project.wide { grid-column: 1 / -1; }

.project.wide .media { aspect-ratio: 3 / 1; }

.project .media > img.strip {
  right: auto;
  width: auto;
  max-width: none;
  height: 100%;
  object-fit: initial;
}

@keyframes drift { to { transform: translateX(-50%); } }

@media (prefers-reduced-motion: no-preference) {
  .project .media > img.strip { animation: drift 48s linear infinite; animation-play-state: paused; }
  .project.is-live .media > img.strip { animation-play-state: running; }
}

@media (max-width: 860px) {
  .project.wide .media { aspect-ratio: 3 / 2; }
}

.project:hover .media::after { --t: var(--ink-mark-hi); --l: 20px; }

@media (max-width: 860px) {
  .work { grid-template-columns: 1fr; }
  .work > .right { border-left: 0; }
  .work > * + * { border-top: 1px solid var(--line); }
}

/* ---- case: title block ---- */

.title {
  padding: var(--s12) var(--inset) var(--s8);
}

.title h1 {
  margin: var(--s2) 0 var(--s3);
  font: 600 72px/80px var(--head);
  letter-spacing: -0.025em;
}

.title .lede {
  max-width: 26em;
  text-wrap: balance;
  font: 500 24px/32px var(--head);
}

@media (max-width: 640px) {
  .title { padding: var(--s8) var(--inset) var(--s6); }
  .title h1 { font-size: 48px; line-height: 56px; }
  .title .lede { font-size: 20px; line-height: 32px; }
}

/* ---- case: facts ---- */

.facts {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  margin: 0;
}

.facts > div { padding: var(--s2) var(--inset) var(--s3); }

.facts > div + div { border-left: 1px solid var(--line); }

.facts dt { margin-bottom: var(--s1); }

.facts dd { margin: 0; font: 400 14px/24px var(--body); }

.facts a { color: var(--link); }

.facts a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* a link that leaves the site carries a small arrow, the same on every project */
.ext { display: inline-flex; align-items: center; gap: var(--s1); }

.ext-icon { flex: none; width: 10px; height: 10px; }

.facts.one { grid-template-columns: 1fr; }

/* two cells under the four: the second lines up with the fourth column above */
.facts.pair { grid-template-columns: 3fr 1fr; }

@media (max-width: 860px) {
  .facts { grid-template-columns: 1fr 1fr; }
  .facts.pair { grid-template-columns: 1fr 1fr; }
  .facts > div:nth-child(3) { border-left: 0; }
  .facts > div:nth-child(n + 3) { border-top: 1px solid var(--line); }
}

/* ---- case: figures ---- */

/* an inline drawing fills its plate like a picture would */
.figure .plate > svg { display: block; width: 100%; height: auto; }


.figure { margin: 0; padding: var(--inset); }

.figure .plate {
  background: var(--plate);
  outline: 1px solid var(--line);
}

.figure > figcaption {
  display: grid;
  grid-template-columns: 12rem 1fr;
  gap: var(--s2);
  padding-top: var(--s2);
}

.figure > figcaption p { max-width: var(--measure); font: 400 14px/24px var(--body); color: var(--muted); }

@media (max-width: 640px) {
  .figure > figcaption { grid-template-columns: 1fr; gap: var(--s1); }
}

/* figure sizes, so a case page has a rhythm instead of one plate after another:
   bleed runs the picture to the frame's edges (the frame's hairlines bound it);
   offset starts where the text column starts and runs to the right edge;
   a duo puts two figures side by side, split by a hairline. */
.figure.bleed { padding: 0 0 var(--inset); }

.figure.bleed .plate { outline: 0; }

.figure.bleed > figcaption { padding: var(--s2) var(--inset) 0; }

.figure.offset { padding-left: calc(var(--inset) + 12rem + var(--s2)); }

.figure.offset > figcaption { grid-template-columns: 1fr; gap: var(--s1); }

.figure.offset > figcaption.has-ctl { grid-template-columns: 1fr auto; }

.figure.offset > figcaption.has-ctl p { grid-column: 1 / -1; grid-row: 2; }

.duo {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.duo > * + * { border-left: 1px solid var(--line); }

.duo > .figure > figcaption { grid-template-columns: 1fr; gap: var(--s1); }

@media (max-width: 860px) {
  .figure.offset { padding-left: var(--inset); }
  .duo { grid-template-columns: 1fr; }
  .duo > * + * { border-left: 0; border-top: 1px solid var(--line); }
}

/* one line, three ways: three phone views side by side */
.phones {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s1);
  padding: var(--s1);
}

.phones figure { margin: 0; }

.phones figure img { outline: 1px solid var(--line); }

.phones .label { padding-top: var(--s1); }

/* six song cards, on the site's own black */
.plate.black { background: #000; }

.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  padding: var(--s1);
}

@media (max-width: 860px) {
  .cards { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 640px) {
  .cards { grid-template-columns: 1fr; }
}

/* a slider or a video: one plate, one picture on at a time, and a quiet row
   of controls in the caption, never on top of the work */
.stage {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

/* the stage clips its pictures, so its corner ticks sit just inside the edge */
.stage::after { inset: 0; }

.stage > img,
.stage > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.stage > img { opacity: 0; transition: opacity 700ms ease; }

.stage > img.is-on { opacity: 1; }

.figure > figcaption.has-ctl { grid-template-columns: 12rem 1fr auto; }

.ctl {
  display: flex;
  align-items: center;
  gap: var(--s2);
}

.ctl-say { display: flex; align-items: center; gap: var(--s2); }

.ctl .name {
  max-width: 14rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
}

.ctl .count { white-space: nowrap; }

.ctl .buttons { display: flex; }

.ctl button {
  width: 32px;
  height: 32px;
  padding: 0;
  background: none;
  border: 1px solid var(--line);
  color: var(--text);
  font: 400 14px/1 var(--mono);
  cursor: pointer;
}

.ctl button + button { border-left: 0; }

.ctl button:hover { color: var(--link); }

.ctl [data-toggle],
.ctl [data-pause] {
  width: auto;
  padding: 0 var(--s2);
  font: 400 11px/16px var(--mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

html:not(.js) .ctl { display: none; }

@media (max-width: 860px) {
  .figure > figcaption.has-ctl { grid-template-columns: 1fr auto; }
  .figure > figcaption.has-ctl p { grid-column: 1 / -1; grid-row: 2; }
  .ctl-say { display: flex; align-items: center; gap: var(--s2); }

.ctl .name { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .stage > img { transition: none; }
}

/* the template: their slide beside the system's, two rows */
.pairs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s1);
  padding: var(--s1);
}

.pairs .label { padding: 0 0 var(--s1); }

.pairs img { outline: 1px solid var(--line); }

@media (max-width: 640px) {
  .pairs { gap: var(--s1) 6px; }
  .pairs .label { font-size: 10px; letter-spacing: 0.08em; }
}

/* readers, in their own words: a grid of hairline cells */
.quotes {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

.quotes .rowlabel { grid-column: 1 / -1; border-bottom: 1px solid var(--line); }

.quotes figure {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--s3);
  margin: 0;
  padding: var(--s4) var(--inset) var(--inset);
}

.quotes figure + figure { border-left: 1px solid var(--line); }

.quotes blockquote {
  margin: 0;
  font: 500 20px/32px var(--head);
}

@media (max-width: 860px) {
  .quotes { grid-template-columns: 1fr; }
  .quotes figure + figure { border-left: 0; border-top: 1px solid var(--line); }
}

/* ---- case: the text ---- */

.text {
  display: grid;
  grid-template-columns: 12rem 1fr;
  gap: var(--s2);
  padding: var(--s8) var(--inset) var(--s12);
}

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

.text .prose p { font: 400 20px/32px var(--body); }

.text .prose p + p,
.text .prose .quote { margin-top: var(--s3); }

.text .quote { margin-left: 0; margin-right: 0; }

.text .quote figcaption { margin-top: var(--s2); padding-left: var(--s3); color: var(--text); }

.text blockquote {
  margin: 0;
  padding-left: var(--s3);
  border-left: 1px solid var(--link);
  font: 500 24px/32px var(--head);
}

@media (max-width: 640px) {
  .text { grid-template-columns: 1fr; padding: var(--s6) var(--inset) var(--s8); }
  .text .prose p { font-size: 18px; line-height: 32px; }
  .text blockquote { font-size: 20px; }
}

/* ---- case: next ---- */

.next {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s2);
  padding: var(--s6) var(--inset);
}

.next .name { font: 600 32px/40px var(--head); letter-spacing: -0.01em; }

.next:hover .name { color: var(--link); }

/* ---- foot ---- */

.foot {
  display: grid;
  grid-template-columns: 1fr repeat(5, auto);
}

.foot > * { padding: var(--s2) var(--inset); }

.foot > * + * { border-left: 1px solid var(--line); }

.foot a:hover { color: var(--link); }

@media (max-width: 640px) {
  .foot { grid-template-columns: 1fr 1fr; }
  .foot > :first-child { grid-column: 1 / -1; }
  .foot > :nth-child(n + 2) { border-top: 1px solid var(--line); }
  .foot > :nth-child(2n) { border-left: 0; }
}

@media (prefers-reduced-motion: no-preference) {
  .project h3, .next .name, .bar a, .foot a, .cv-link { transition: color 120ms steps(4, end); }
}

/* ---- motion (motion.js adds the classes; nothing here runs without it) ----
   a hairline reveal: a bright line draws across the middle of the plate, then
   the picture opens out of it, up and down, and the line fades. pictures in a
   row (phones, pairs, cards) just open, one after another. */

@keyframes hairline {
  0% { transform: scaleX(0); opacity: 1; }
  30% { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}

@media (prefers-reduced-motion: no-preference) {
  .reveal > img,
  .reveal > video,
  img.reveal { clip-path: inset(50% 0 50% 0); }

  .reveal.is-in > img,
  .reveal.is-in > video,
  img.reveal.is-in {
    clip-path: inset(0 0 0 0);
    transition: clip-path 1000ms cubic-bezier(0.16, 1, 0.3, 1) calc(260ms + var(--rd, 0ms)), opacity 700ms ease;
  }

  .project .reveal.is-in > video { transition: clip-path 1000ms cubic-bezier(0.16, 1, 0.3, 1) 260ms, opacity 500ms ease; }

  .reveal::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    z-index: 2;
    height: 1px;
    background: var(--ink-mark-hi);
    transform: scaleX(0);
    transform-origin: left;
    pointer-events: none;
  }

  .reveal.is-in::before { animation: hairline 1100ms cubic-bezier(0.16, 1, 0.3, 1) both; }

  .no-reveal > img, .no-reveal > video { clip-path: none; transition: opacity 500ms ease; }
  .no-reveal::before { content: none; }
}

/* the morph between a home card and its case: the one named pair (motion.js
   names it for that navigation only) grows from one to the other; the rest of
   the page crossfades quickly, and the bar, the same on both, stays put. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }

  .bar { view-transition-name: bar; }

  ::view-transition-group(cover) { animation-duration: 600ms; animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1); }

  ::view-transition-old(cover),
  ::view-transition-new(cover) { height: 100%; object-fit: cover; overflow: clip; }

  ::view-transition-old(root),
  ::view-transition-new(root) { animation-duration: 280ms; }
}

/* ---- archive: pictures and motion, in rows of one height ----
   every piece keeps its own shape; the pieces of a row share one height and
   the row fills the width (each item grows with its aspect ratio, so widths
   are proportional and heights come out equal). the builder gathers the rows. */

.archive { padding: var(--s3) var(--inset) var(--s6); }

.archive .row {
  display: flex;
  gap: var(--s3);
  margin-bottom: var(--s3);
}

.archive .row.one, .archive .row.two { margin: var(--s3) 0 var(--s6); }

.archive .item {
  flex: calc(var(--ar, 1)) 1 0;   /* --ar is "w / h"; inside calc() it is a number, the item's share of the row */
  min-width: 0;
  margin: 0;
}

.archive .item .plate {
  aspect-ratio: var(--ar, 1);
  overflow: hidden;
  background: var(--plate);
  outline: 1px solid var(--line);
}

.archive .item .plate > img,
.archive .item .plate > video { display: block; width: 100%; height: 100%; object-fit: cover; }

.archive .item .plate::after { inset: 0; }

.archive .item figcaption {
  display: flex;
  justify-content: space-between;
  gap: var(--s2);
  padding: var(--s1) 0 var(--s2);
}

.archive .item figcaption .label:first-child { color: var(--text); }

/* a phone takes the rows two pieces at a time */
@media (max-width: 640px) {
  .archive .row { flex-wrap: wrap; gap: var(--s2); margin-bottom: var(--s2); }
  .archive .item { flex: 1 1 calc(50% - var(--s2)); }
  .archive .row.one .item, .archive .row.two .item { flex-basis: 100%; }
}

/* ---- home and cv: experience, tools, open to ---- */

.about {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.about > .cell { padding: var(--s4) var(--inset) var(--s6); }

.about > .cell + .cell { border-left: 1px solid var(--line); }

.about .cell > .label + * { margin-top: var(--s2); }

.about .cell > p + h2.label,
.about .cell > ol + h2.label { margin-top: var(--s4); }

.about p { max-width: 30em; color: var(--muted); }

.about p + p { margin-top: var(--s2); }

.about p a { color: var(--text); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--line); }

.about p a:hover { color: var(--link); }

.roles { margin: 0; padding: 0; list-style: none; }

.roles li {
  display: grid;
  grid-template-columns: 9rem 1fr auto;
  gap: var(--s2);
  padding: var(--s1) 0;
  border-top: 1px solid var(--line);
}

.roles li:last-child { border-bottom: 1px solid var(--line); }

.roles .org { color: var(--text); }

.roles .role { color: var(--muted); }

.roles .note { display: block; font-size: 14px; color: var(--muted); opacity: 0.8; }

/* a word on the organisation, under its name; the top three rows share one height */
.roles .info { display: block; font-size: 14px; line-height: 20px; color: var(--muted); opacity: 0.8; }

.roles li.top { min-height: 104px; }

.roles .when { align-self: start; padding-top: 4px; }

/* a place and its years, heading the roles that sit under it, with room above */
.roles li.group { grid-template-columns: 1fr auto; padding-top: var(--s6); }

.roles li.group .label { color: var(--text); }

.cv-link {
  display: inline-block;
  margin-top: var(--s4);
  padding: 0;
  background: none;
  border: 0;
  color: var(--text);
  cursor: pointer;
}

.cv-link:hover { color: var(--link); }

@media (max-width: 860px) {
  .about { grid-template-columns: 1fr; }
  .about > .cell + .cell { border-left: 0; border-top: 1px solid var(--line); }
}

@media (max-width: 640px) {
  .roles li { grid-template-columns: 1fr auto; }
  .roles .role { grid-column: 1 / -1; grid-row: 2; }
}

.print-only { display: none; }

/* ---- the cv prints as a clean page ---- */

@media print {
  :root { --bg: #fff; --plate: #fff; --line: #cfcfcf; --text: #111; --muted: #444; --link: #111; }
  body { padding: 0; }
  body::before, .frame::before, .plate::after { display: none; }
  .frame { border: 0; }
  .bar, .foot, .print { display: none; }
  .print-only { display: block; margin-top: 8px; color: var(--muted); }
  .title { padding: 0 0 24px; }
  .title h1 { font-size: 40px; line-height: 48px; }
  .about > .cell { padding: 24px 0; }
  a { text-decoration: none; }
}
