@font-face {
  font-family: 'avantgarde';
  src: url('fonts/avantgarde.woff') format('woff');
  font-display: swap;
}

/* Body copy: TeX Gyre Adventor, a free (GUST Font License) regular-weight
   version of the same Avant Garde letterforms. */
@font-face {
  font-family: 'adventor';
  src: url('fonts/texgyreadventor-regular.otf') format('opentype');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'adventor';
  src: url('fonts/texgyreadventor-italic.otf') format('opentype');
  font-weight: normal;
  font-style: italic;
  font-display: swap;
}

/* ---------- Tokens ---------- */

:root {
  --bg: #4E4E4E;
  --bg-header: #393939;
  --fg: #F5E7DD;

  /* Theme colours - each meets WCAG AA: 3:1 on the page grey (large text and
     UI parts) and 4.5:1 on the header grey (the small phone menu text).
     Keep that in mind if you change them. */
  /* TEST: option B for red and purple (same hue, brighter + more saturated so
     they pass). Before the test: --raph: #F0655A; --donny: #B97AD1; */
  --raph: #FF7A6F;
  --leo: #48BCCF;
  --mikey: #FF965C;
  --donny: #CE86EA;

  --font: 'avantgarde', 'Century Gothic', 'Futura', sans-serif;

  /* Side margin scales smoothly from 16px on phones to 48px on desktop.
     The gallery gap uses the same value so gaps always match the margins. */
  --gutter: clamp(16px, 3.5vw, 48px);
  /* Space between gallery images: 32px on desktop, scaling down on phones. */
  --gap: clamp(12px, 2.4vw, 32px);
  --section-title-size: clamp(24px, 3vw, 36px);
  /* Subtle, diffuse shadow that lifts case study images off the background. */
  --image-shadow: 0 4px 16px rgba(0, 0, 0, 0.12), 0 24px 64px rgba(0, 0, 0, 0.22), 0 48px 120px rgba(0, 0, 0, 0.14);

  /* Gallery: tiles never go below --tile-min (then a column drops) and
     there are never more than --tile-cols per row. */
  --tile-min: 260px;
  --tile-max: 368px;
  --tile-cols: 4;

  /* Page (and gallery) stop growing once --tile-cols tiles reach --tile-max;
     everything centers in the viewport beyond that. */
  --page-max: calc(var(--tile-cols) * var(--tile-max) + (var(--tile-cols) - 1) * var(--gap));

  /* Header: the title + categories block, with --header-pad above and below
     (the padding centres the categories in the --bar-h strip the header
     shrinks to on scroll). The logo is exactly as tall as that text block.
     Section anchors offset by --bar-h so headings don't land underneath it. */
  --bar-h: 56px;
  --title-size: clamp(24px, 4vw, 48px);
  --title-gap: clamp(8px, 1.5vw, 16px);
  --nav-size: clamp(15px, 2vw, 24px);
  --logo-h: calc(var(--title-size) * 1.1 + var(--title-gap) + var(--nav-size));
  --header-pad: calc((var(--bar-h) - var(--nav-size)) / 2);
  --header-h: calc(var(--logo-h) + 2 * var(--header-pad));

}

/* ---------- Base ---------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font);
}

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

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

.raph  { color: var(--raph); }
.leo   { color: var(--leo); }
.mikey { color: var(--mikey); }
.donny { color: var(--donny); }

.wrap {
  width: 100%;
  max-width: calc(var(--page-max) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ---------- Header ---------- */

/* Sticky with a negative top: the header scrolls normally until only its
   bottom --bar-h strip (the categories) is left, then sticks there. */
.site-header {
  position: sticky;
  top: calc(var(--bar-h) - var(--header-h));
  z-index: 5;
  height: var(--header-h);
  background: var(--bg-header);
}

.site-header__inner {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--gutter);
  height: 100%;
}

/* Title + nav sit at the bottom so the nav lands centred in the strip. */
.site-header__text {
  padding-bottom: var(--header-pad);
  font-size: var(--nav-size);
}

.site-title {
  margin: 0 0 var(--title-gap);
  font-size: var(--title-size);
  font-weight: normal;
  line-height: 1.1; /* keep in sync with --logo-h */
}

/* The drawing, just left of "COWABUNGA.work", as tall as the title line. */
.site-title,
.site-title a {
  display: flex;
  align-items: center;
  gap: 0.25em;
}

.site-title__logo {
  flex-shrink: 0;
  width: auto;
  height: 1.1em;
}

.nav {
  display: flex;
  gap: clamp(10px, 1.5vw, 20px); /* one line, so it always fits the strip */
  margin: 0;
  padding: 0;
  list-style: none;
  line-height: 1;
}

/* Hovered links and the section you're on (aria-current, set by nav.js) get
   an underline: a rounded bar in the link's colour, with room below the text.
   It's absolutely positioned, so it doesn't change the menu's height. */
.nav a:not(.nav__home-link) {
  position: relative;
  display: inline-block;
}

.nav a:not(.nav__home-link)::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -8px;
  height: 3px;
  border-radius: 3px;
  background: currentColor;
  opacity: 0;
  transform: scaleX(0.6);
  transition: opacity 0.2s ease, transform 0.25s ease;
}

.nav a:not(.nav__home-link):hover::after,
.nav a:not(.nav__home-link):focus-visible::after,
.nav a[aria-current]::after {
  opacity: 1;
  transform: none;
}

/* The drawing at the start of the menu: back to the top (or home, on the
   other pages). Hidden while the full header shows, and slides in once the
   header has condensed to the strip (nav.js adds .is-condensed), taking the
   title's place. Negative vertical margins keep it from changing the menu's
   line height. */
.nav__home {
  display: flex;
  align-items: center;
  max-width: 120px;
  transition: max-width 0.35s ease, margin 0.35s ease, opacity 0.35s ease;
}

.nav__home-link {
  --h: calc(var(--bar-h) - 2 * 8px); /* 40px tall in the 56px strip */
  display: flex;
  margin-block: calc((var(--nav-size) - var(--h)) / 2);
}

.nav__home-link img {
  width: auto;
  height: var(--h);
}

:root:not(.is-condensed) .nav__home {
  max-width: 0;
  margin-right: calc(-1 * clamp(10px, 1.5vw, 20px)); /* cancel the menu gap */
  opacity: 0;
  pointer-events: none;
  overflow: hidden;
}

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

/* ---------- Music buttons ---------- */

/* Shared: a round play/pause. Paused shows a play triangle; playing shows four
   equalizer bars bouncing in the site's four colours. */
.music {
  --size: 36px;
  flex-shrink: 0;
  position: relative;
  display: grid;
  place-items: center;
  width: var(--size);
  height: var(--size);
  padding: 0;
  border: 1.5px solid rgb(245 231 221 / 0.35);
  border-radius: 50%;
  background: var(--bg);
  color: var(--fg);
  cursor: pointer;
  transition: transform 0.2s ease, border-color 0.2s ease;
}

.music:hover,
.music:focus-visible {
  transform: scale(1.08);
  border-color: var(--fg);
}

.music:focus-visible {
  outline: 2px solid var(--leo);
  outline-offset: 3px;
}

.music__play {
  width: 44%;
  height: 44%;
  fill: currentColor;
}

/* A thick round-joined stroke in the same colour rounds off the corners. */
.music__play path {
  stroke: currentColor;
  stroke-width: 3.6;
  stroke-linejoin: round;
}

.music__bars {
  display: none;
  align-items: flex-end;
  gap: 8%;
  width: 44%;
  height: 40%;
}

.music.is-playing .music__play { display: none; }
.music.is-playing .music__bars { display: flex; }

.music__bars i {
  flex: 1;
  height: 100%;
  border-radius: 2px;
  transform-origin: bottom;
  animation: music-bar 0.9s ease-in-out infinite alternate;
}

.music__bars i:nth-child(1) { background: var(--leo);   animation-delay: -0.2s; }
.music__bars i:nth-child(2) { background: var(--raph);  animation-delay: -0.6s; }
.music__bars i:nth-child(3) { background: var(--mikey); animation-delay: -0.4s; }
.music__bars i:nth-child(4) { background: var(--donny); animation-delay: -0.8s; }

@keyframes music-bar {
  from { transform: scaleY(0.25); }
  to   { transform: scaleY(1); }
}

/* Corner button, at the far right of the header, lined up with the menu.
   - Main page: only shown once the header has collapsed (.is-condensed, from
     nav.js) and the big button is off screen (.vibe-passed, from music.js) -
     the big one covers it until then.
   - Other pages (design): always shown, so music can always be paused. */
.site-header__inner {
  position: relative;
}

.music--corner {
  --size: 40px;
  position: absolute;
  right: var(--gutter);
  bottom: calc((var(--bar-h) - var(--size)) / 2);
  transition: opacity 0.3s ease, transform 0.2s ease;
}

.has-vibe:not(.is-condensed) .music--corner,
.has-vibe:not(.vibe-passed) .music--corner {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0s 0.3s;
}

/* Ringed (the big button beside "get the whole vibe:", and the corner one in
   the collapsed header): no fill - just a thick ring of four round-ended arcs,
   one per theme colour, drawn in an SVG inside the button. The ring slowly
   turns while the music plays; the play triangle is scaled up to fill more of
   the circle. */
.music--ringed {
  border: 0;
  background: none;
}

.music--big {
  --size: var(--vibe-btn);
}

.music__ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg); /* start the first arc at the top */
  animation: music-ring 8s linear infinite;
  animation-play-state: paused;
}

.music__ring circle {
  fill: none;
  stroke: currentColor;
  stroke-width: 9.2;
  stroke-linecap: round;
}

.music--ringed.is-playing .music__ring {
  animation-play-state: running;
}

.music--ringed .music__play {
  width: 70%;
  height: 70%;
}

.music--ringed .music__bars {
  width: 42%;
  height: 38%;
}

.music--ringed:hover,
.music--ringed:focus-visible {
  transform: scale(1.06);
}

@keyframes music-ring {
  from { transform: rotate(-90deg); }
  to   { transform: rotate(270deg); }
}

@media (prefers-reduced-motion: reduce) {
  .music__bars i { animation: none; }
  .music__bars i:nth-child(odd) { transform: scaleY(0.6); }
  .music__ring { animation: none; }
  .music--corner { transition: none; }
}

/* ---------- "feel the whole vibe" intro ---------- */

.vibe {
  display: flex;
  flex-direction: column; /* phones: button under the words */
  align-items: center;
  justify-content: center;
  gap: var(--vibe-gap);
  padding-block: clamp(24px, 5vw, 64px) calc(var(--gap) * 3);
  text-align: center;
}

/* The full logo (drawing + COWABUNGA lettering), left of "get the whole
   vibe:" and as tall as it: the three
   lines' font sizes x 0.8 line height add up to 0.8026 x the lockup width. */
.vibe__logo {
  flex-shrink: 0;
  width: auto;
  height: calc(min(var(--vibe-w), 100cqi) * 0.8026);
}

@media (max-width: 599px) {
  .vibe__logo { width: min(100%, 300px); height: auto; }
}

/* Wider screens: the button sits to the right of the words, centred on them. */
@media (min-width: 600px) {
  .vibe { flex-direction: row; }
}

/* get the / whole / vibe:, every line exactly --vibe-w wide (or the page, if
   narrower): each line's font size is that width divided by its own width at
   1em (--em, set in index.html). Tight line height - the lines may just touch. */
.vibe {
  --vibe-w: 270px;
  --vibe-gap: clamp(28px, 4vw, 64px); /* space between the words and the button */
  --vibe-btn: clamp(120px, 12vw, 180px);
  container-type: inline-size;
}

.vibe__title {
  margin: 0;
  line-height: 0.8;
}

.vibe__line {
  display: block;
  font-size: calc(min(var(--vibe-w), 100cqi) / var(--em));
  white-space: nowrap;
}

/* Closer word gap in "get the" (its --em is measured with this spacing). */
.vibe__line--tight {
  word-spacing: -0.12em;
}

/* Scroll-linked: the title (with its drawing) fades out and a shadow fades in as the
   header shrinks to the categories strip (Chrome/Edge; elsewhere the header
   just scrolls up to the strip). Runs over the distance the header travels
   before sticking. */
@supports (animation-timeline: scroll()) {
  .site-header,
  .site-title {
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: scroll(root);
    animation-range: 0 calc(var(--header-h) - var(--bar-h));
  }
  .site-header { animation-name: header-shadow; }
  .site-title  { animation-name: fade-out; }
}

@keyframes header-shadow {
  to { box-shadow: 0 2px 12px rgb(0 0 0 / 0.25); }
}

@keyframes fade-out {
  to { opacity: 0; }
}


/* ---------- Body copy ---------- */

/* Paragraphs and lists in the lighter Adventor; titles, menus and the quote
   stay in the heavy Avant Garde. */
.case-row__text p,
.process ul,
.skills,
.about p {
  font-family: 'adventor', var(--font);
}

/* ---------- Sections ---------- */

.category {
  scroll-margin-top: calc(var(--bar-h) + var(--gap)); /* land just below the menu strip */
  padding-bottom: calc(var(--gap) * 3); /* sections sit 3 gaps apart */
}

/* Enough room below the last section that every section - even a short or
   empty one at the end - can scroll up to sit right under the menu strip. */
.category:last-of-type {
  min-height: calc(100vh - var(--bar-h) - var(--gap));
}

/* Section titles at 75% of their original size (48px -> 36px on desktop),
   centred. */
.category h2 {
  margin: 0 0 clamp(12px, 1.5vw, 18px);
  text-align: center;
  font-size: var(--section-title-size);
  font-weight: normal;
  line-height: 1;
}

/* Home page (body#top): section titles left-aligned, 25% bigger, with more
   room before the section's content. */
#top .category h2 {
  margin-bottom: clamp(20px, 2.5vw, 36px);
  font-size: calc(var(--section-title-size) * 1.25);
  text-align: left;
}

/* Space above the first section matches where a menu click lands a section:
   one gap below the menu strip. */
main {
  padding-top: var(--gap);
}

/* ---------- Gallery ---------- */

/* Without JS: a plain grid, up to --tile-cols per row, wrapping to 3, 2, 1
   once a column would drop below --tile-min. */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(
    min(100%, max(var(--tile-min), (100% - (var(--tile-cols) - 1) * var(--gap)) / var(--tile-cols))),
    1fr));
  gap: var(--gap);
}

/* With masonry.js: tiles are absolutely positioned; the script sets each
   one's size and position and the gallery's height. */
.gallery.is-masonry {
  display: block;
  position: relative;
}

.gallery.is-masonry .tile {
  position: absolute;
  top: 0;
  left: 0;
}

.gallery.is-masonry .tile img {
  height: 100%;
  object-fit: cover; /* keeps both images undistorted while the tile resizes */
}

/* When a swap changes a tile's size, the tile resizes and the ones below glide
   to their new spots - same duration and easing as the cross-fade, so it all
   reads as one motion. masonry.js turns this off during window resizes. */
.gallery.is-animated,
.gallery.is-animated .tile {
  transition: transform 1.5s ease-in-out, height 1.5s ease-in-out;
}

/* Animation gallery: full width, 4 across and 3 tall - the featured
   animation in the middle two columns of the top two rows with one small tile
   either side of it on each row, then a row of 4 below (8 small + featured). On narrow screens: featured
   full width on top, the rest two across. */
.anim-gallery {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-flow: dense;
  gap: var(--gap);
}

.anim-tile--focus {
  grid-column: 2 / span 2;
  grid-row: 1 / span 2;
}

@media (max-width: 700px) {
  .anim-gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: none; }
  .anim-tile--focus { grid-column: 1 / -1; grid-row: 1; }
}

.anim-tile {
  display: block;
  padding: 0;
  border: 0;
  border-radius: 16px;
  overflow: hidden;
  background: var(--bg-header);
  cursor: pointer;
  transition: transform 0.2s ease;
}

.anim-tile:not(.anim-tile--focus):hover,
.anim-tile:not(.anim-tile--focus):focus-visible {
  transform: scale(1.04);
}

.anim-tile:focus-visible {
  outline: 2px solid var(--leo);
  outline-offset: 3px;
}

.anim-tile--focus {
  cursor: default;
}

.anim-tile video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  transition: opacity 0.25s ease; /* keep in sync with FADE_MS in anim-gallery.js */
}

.anim-tile.is-changing video {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .anim-tile,
  .anim-tile video { transition: none; }
}

.tile {
  position: relative;
  margin: 0;
  min-width: 0;
}

.tile img {
  width: 100%;
  border-radius: 16px;
}

/* Cross-fade: gallery.js lays the incoming image over the current one and
   fades it in. Keep the duration in sync with FADE_MS there. */
.tile .incoming {
  position: absolute;
  inset: 0;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.5s ease-in-out;
}

.tile .incoming.is-in {
  opacity: 1;
}

/* ---------- Design page password screen ---------- */

.gate {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-block: clamp(48px, 10vh, 120px);
}

/* COWA / BUNGA / /design stacked, every line exactly as wide as the input so the
   block sits flush above it. Each line's font size is that width divided by
   the line's own width at 1em (--em, set in design.html). */
.gate__title {
  --lockup-w: min(360px, 100vw - 2 * var(--gutter)); /* = the input's width */
  width: var(--lockup-w);
  margin: 0 0 clamp(12px, 2vw, 24px);
  line-height: 0.95;
  text-align: right;
}

.gate__line {
  display: block;
  font-size: calc(var(--lockup-w) / var(--em));
  white-space: nowrap;
}

.gate__fields {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  max-width: 360px;
}

.gate[hidden] {
  display: none;
}

.gate button {
  align-self: flex-end; /* under the field, on the right */
}

.gate input,
.gate button {
  font: inherit;
  font-size: 16px; /* 16px+ stops iOS zooming in on focus */
  border: 0;
  border-radius: 12px;
  padding: 12px 16px;
}

.gate input {
  background: var(--bg-header);
  color: var(--fg);
}

.gate input::placeholder {
  color: var(--fg);
  opacity: 0.45;
}

.gate input:focus-visible,
.gate button:focus-visible {
  outline: 2px solid var(--mikey);
  outline-offset: 2px;
}

.gate button {
  background: var(--mikey);
  color: var(--fg);
  cursor: pointer;
}

.gate__error {
  margin: 0;
  color: var(--fg); /* small text: cream meets WCAG AA contrast, red can't here */
  font-size: 14px;
}

/* ---------- Design page case studies ---------- */

/* Two across the full row; one per row on narrow screens. */
/* Three across on wide screens, one per row below that. */
.case-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gap);
}

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

.case-card {
  display: block;
  margin: 0;
}

/* More room between the "case studies" heading and the case titles. */
#case-studies h2 {
  margin-bottom: clamp(20px, 2.6vw, 40px);
}

/* Title above the thumbnail: left-aligned, a bit smaller than the section
   title. Each case study picks its colour with a class in design.html. */
.case-card__title {
  margin: 0 0 clamp(10px, 1.2vw, 16px);
  /* never under 24px: coloured text needs to stay "large" for WCAG contrast */
  font-size: max(24px, var(--section-title-size) * 0.75);
  font-weight: normal;
  line-height: 1.1;
}

/* The thumbnail box. isolation + its own layer keep everything inside clipped
   cleanly to the rounded corners. */
.case-card__thumb {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: 16px;
  background: var(--bg-header);
  isolation: isolate;
  box-shadow: var(--image-shadow);
  transform: translateZ(0);
}

/* The image fills the thumbnail (cropped to fit). */
.case-card__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  /* Optional zoom (style="--zoom: 1.1"). Always scaled from the centre; the
     --left crop shifts it so it reads as zooming in on the top-left instead. */
  transform: translate(var(--shift-x, 0%), var(--shift-y, 0%)) scale(var(--zoom, 1));
}

/* Anchor the crop to the left edge (for images whose content sits left). */
.case-card__image--left {
  object-position: left center;
  /* keep the top-left corner in place when zoomed (header stays in view) */
  --shift-x: calc((var(--zoom, 1) - 1) * 50%);
  --shift-y: calc((var(--zoom, 1) - 1) * 50%);
}

/* Show the whole image instead of cropping, on white (for images whose own
   background is white, so the extra space blends in). */
.case-card__image--fit {
  object-fit: contain;
  background: #fff;
}

/* By default just the image. On hover the image blurs slightly, a dark overlay
   fades in, and "check it" rises into place from slightly below. Shown all the time
   on touch screens (no hover there). */
.case-card__thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: rgb(0 0 0 / 0.7);
  opacity: 0;
  transition: opacity 0.35s ease;
}

.case-card__cta {
  position: relative;
  z-index: 1; /* above the overlay */
  font-size: clamp(26px, 3.2vw, 48px);
  line-height: 1;
  text-shadow: 0 2px 16px rgb(0 0 0 / 0.5);
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.35s ease, transform 0.45s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.case-card:hover .case-card__thumb::after,
.case-card:focus-within .case-card__thumb::after,
.case-card:hover .case-card__cta,
.case-card:focus-within .case-card__cta {
  opacity: 1;
}

.case-card:hover .case-card__cta,
.case-card:focus-within .case-card__cta {
  transform: none;
}

/* The slight blur is on the image itself (not a backdrop blur on the overlay,
   which pulled light in at the rounded corners). Blurring softens the image's
   own edges, so it's also enlarged a touch while blurred to keep those soft
   edges outside the frame - under the blur and overlay it doesn't read as a
   zoom. */
.case-card__image {
  transition: filter 0.35s ease, transform 0.35s ease;
}

.case-card:hover .case-card__image,
.case-card:focus-within .case-card__image {
  filter: blur(6px);
  transform: translate(var(--shift-x, 0%), var(--shift-y, 0%)) scale(calc(var(--zoom, 1) * 1.03));
}

@media (hover: none) {
  .case-card__thumb::after,
  .case-card__cta { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .case-card__thumb::after,
  .case-card__image,
  .case-card__cta { transition: none; }
  .case-card__cta { transform: none; }
}

/* ---------- Design page philosophy + process ---------- */

/* Two centred lines, the key words in the theme colours, both as wide as the
   process below. Each line's font size is that width divided by the line's own
   width at 1em (--em, set in design.html). */
.philosophy {
  --quote-w: min(1040px, 100vw - 2 * var(--gutter));
  width: var(--quote-w);
  margin: 0 auto;
  line-height: 1.1;
  text-align: center;
}

.philosophy__line {
  display: block;
  font-size: calc(var(--quote-w) / var(--em));
  white-space: nowrap;
}

/* Process: one column on narrow screens; on wide ones two columns read in a
   Z (need → research, ideate → create) with iterate centred underneath.
   The arrows are cream. */
.process {
  --gap-x: 64px;  /* room for the sideways arrows */
  --gap-y: 56px;  /* room for the downward arrows */
  --s: 5px;       /* arrow line thickness */
  --p: 6px;      /* space between an arrow and the cards */
  display: grid;
  gap: var(--gap-y) var(--gap-x);
  max-width: 640px;
  margin: 0 auto;
  padding: 0;
  list-style: none;
}

.process > li {
  position: relative;
  padding: clamp(14px, 1.6vw, 20px) clamp(16px, 2vw, 24px);
  border-radius: 16px;
  background: var(--bg-header);
  box-shadow: var(--image-shadow);
}

.process > li:nth-child(1) { --c: var(--leo); }
.process > li:nth-child(2) { --c: var(--raph); }
.process > li:nth-child(3) { --c: var(--mikey); }
.process > li:nth-child(4) { --c: var(--donny); }
.process > li:nth-child(5) { --c: var(--leo); }

/* Arrow pieces: the line is the step's ::before (and ::after for the bent
   ones), the arrowhead is its name's ::before, a rounded chevron. */
.process > li + li::before,
.process > li + li > .process__name::before {
  content: "";
  position: absolute;
  box-sizing: border-box;
}

.process > li + li::before {
  border-radius: var(--s);
  background: var(--fg);
}

.process > li + li > .process__name::before {
  width: 26px;
  height: 16px;
  background: var(--fg);
  -webkit-mask: var(--chevron) center / contain no-repeat;
  mask: var(--chevron) center / contain no-repeat;
  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 26 16'%3E%3Cpath d='M3 3l10 10 10-10' fill='none' stroke='%23000' stroke-width='5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* One column: straight down from each step to the next. */
.process > li + li::before {
  top: calc(var(--p) - var(--gap-y));
  left: calc(50% - var(--s) / 2);
  width: var(--s);
  height: calc(var(--gap-y) - 2 * var(--p) - 4px);
}

.process > li + li > .process__name::before {
  top: calc(-16px - var(--p));
  left: calc(50% - 13px);
}

@media (min-width: 900px) {
  /* Every box is a column wide but only as tall as its content, centred on
     its row's middle. */
  .process {
    grid-template-columns: 1fr 1fr;
    align-items: center;
    max-width: 1040px;
  }

  /* iterate: centred under the two columns, one column wide, with a little
     extra room above so the arrow into it has space to turn. */
  .process > li:nth-child(5) {
    grid-column: 1 / -1;
    justify-self: center;
    width: calc(50% - var(--gap-x) / 2);
    margin-top: 24px;
  }

  /* Along a row (need → research, ideate → create): a sideways arrow. */
  .process > li:nth-child(even)::before {
    top: calc(50% - var(--s) / 2);
    left: calc(var(--p) - var(--gap-x));
    width: calc(var(--gap-x) - 2 * var(--p) - 4px);
    height: var(--s);
  }

  .process > li:nth-child(even) > .process__name::before {
    top: calc(50% - 8px);
    left: calc(-21px - var(--p));
    transform: rotate(-90deg);
  }

  /* End of a row to the next one (research → ideate, create → iterate): down
     from the step above, back across with rounded corners, down into this
     one. process.js measures (the values here are the fallback):
       --rise  gap from the bottom of that step to the top of this one
       --bend  how far above this step the line runs across: halfway down the
               gap between the two rows
       --run   how far across, from the centre of this step to that one's.
     The top half (::before) turns from down to across, the bottom half
     (::after) from across to down; they meet halfway across. */
  .process > li:nth-child(3) {
    --run: calc(100% + var(--gap-x));
    --r: var(--rise, var(--gap-y));
  }

  .process > li:nth-child(5) {
    --run: calc(50% + var(--gap-x) / 2);
    --r: var(--rise, calc(var(--gap-y) + 24px));
  }

  .process > li + li:nth-child(odd) {
    --b: var(--bend, calc(var(--r) / 2));
  }

  .process > li + li:nth-child(odd)::before,
  .process > li + li:nth-child(odd)::after {
    content: "";
    position: absolute;
    box-sizing: border-box;
    width: calc(var(--run) / 2 + var(--s) / 2);
    border: 0 solid var(--fg);
    border-radius: 0;
    background: none;
  }

  .process > li + li:nth-child(odd)::before {
    top: calc(var(--p) - var(--r));
    left: calc(50% + var(--run) / 2);
    height: calc(var(--r) - var(--b) - var(--p) + var(--s) / 2);
    border-width: 0 var(--s) var(--s) 0;
    border-bottom-right-radius: 20px;
  }

  .process > li + li:nth-child(odd)::after {
    top: calc(-1 * var(--b) - var(--s) / 2);
    left: calc(50% - var(--s) / 2);
    height: calc(var(--b) - var(--p) + var(--s) / 2 - 4px);
    width: calc(var(--run) / 2 + var(--s) / 2 + 1px); /* overlap: no seam */
    border-width: var(--s) 0 0 var(--s);
    border-top-left-radius: 20px;
  }

  /* A dot on the start of the line, so that end is rounded like the others. */
  .process > li + li:nth-child(odd) > .process__name::after {
    content: "";
    position: absolute;
    top: calc(var(--p) - var(--r) - var(--s) / 2);
    left: calc(50% + var(--run) - var(--s) / 2);
    width: var(--s);
    height: var(--s);
    border-radius: 50%;
    background: var(--fg);
  }
}

.process__name {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 6px;
  color: var(--c);
  font-size: clamp(22px, 2vw, 28px);
  font-weight: normal;
}

/* The ∞ for iterate, in the cream text colour: next to the name on narrow
   screens, outside the card on both sides on wide ones. */
.process__forever {
  width: 2.2em;
  height: auto;
  margin: -0.3em 0;
  fill: none;
  stroke: var(--fg);
  stroke-width: 5;
  stroke-linejoin: round;
}

@media (min-width: 900px) {
  .process__forever {
    position: absolute;
    top: 50%;
    right: calc(100% + 28px);
    width: 88px;
    margin: 0;
    transform: translateY(-50%);
  }

  .process__forever--right {
    right: auto;
    left: calc(100% + 28px);
  }
}

/* The second ∞ (right of the card) only fits beside it on wide screens. */
@media (max-width: 899px) {
  .process__forever--right {
    display: none;
  }
}

.process ul {
  margin: 0;
  padding-left: 1.1em;
  font-size: clamp(15px, 1.2vw, 17px);
  line-height: 1.5;
}

.process ul li + li {
  margin-top: 2px;
}

/* ---------- "what i'm good at" ---------- */

/* The design page's section titles, 25% bigger than the usual size. */
#skills h2,
#case-studies h2 {
  font-size: calc(var(--section-title-size) * 1.25);
}

/* Like movie credits: two columns meeting in the middle, the left one
   right-aligned and the right one left-aligned, with one bullet between each
   pair in the theme colours. The list is in pair order (left, right, ...). */
.skills {
  --gap: clamp(32px, 5vw, 64px);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px var(--gap);
  max-width: 1040px;
  margin: 0 auto;
  padding: 0;
  list-style: none;
  font-size: clamp(15px, 1.5vw, 21px);
  line-height: 1.4;
}

.skills li:nth-child(odd) {
  text-align: right;
}

.skills li:nth-child(even) {
  position: relative;
}

/* The bullet: centred in the gap, level with the pair's first line. */
.skills li:nth-child(even)::before {
  content: "";
  position: absolute;
  top: calc(0.7em - 4px);
  left: calc(var(--gap) / -2 - 4px);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--leo);
}

.skills li:nth-child(4)::before { background: var(--raph); }
.skills li:nth-child(6)::before { background: var(--mikey); }
.skills li:nth-child(8)::before { background: var(--donny); }


/* ---------- About (main page) ---------- */

/* The bio on the left, four square photos in a 2 x 2 grid on the right. Laid
   out on the animation gallery's grid (4 columns, same gap), so each photo is
   exactly the size of a small animation tile, and the bio is sized to match
   the photo block's height (about.js). Narrow screens: the bio, then
   the photos two across (again matching the gallery). */
.about {
  display: grid;
  gap: var(--gap);
}

.about__photos {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap);
}

.about__photos img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 16px;
  box-shadow: var(--image-shadow);
}

@media (min-width: 701px) {
  .about {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    align-items: center;
  }

  .about__text {
    grid-column: 1 / span 2;
    padding-right: var(--gap); /* a little air before the photos */
  }

  .about__photos {
    grid-column: 3 / span 2;
  }
}

/* On wide screens about.js sets the bio's font size so it comes out exactly
   as tall as the photo block; this is the size otherwise. */
.about__text {
  font-size: clamp(20px, 1.625vw, 23.75px);
}

.about p {
  margin: 0 0 1em;
  font-size: 1em;
  line-height: 1.6;
}

.about p:last-child {
  margin-bottom: 0; /* so the photos line up with the last line of text */
}

/* ---------- Case study pages ---------- */

.case-page {
  max-width: 1200px;
  margin-inline: auto;
}

.case-back {
  display: inline-block;
  margin-bottom: clamp(8px, 1vw, 12px);
  font-size: 16px;
  opacity: 0.75;
  transition: opacity 0.2s ease;
}

.case-back:hover,
.case-back:focus-visible {
  opacity: 1;
}

.case-page__title {
  margin: 0 0 clamp(16px, 2vw, 28px);
  font-size: calc(var(--section-title-size) * 1.25);
  font-weight: normal;
  line-height: 1.05;
  text-align: center;
}

/* Pages without a hero image: a little more room under the title. */
.case-page__title + .case-row {
  margin-top: calc(var(--gap) * 1.5);
}

.case-page__hero {
  display: block;
  width: 100%;
  height: auto;
  margin-bottom: calc(var(--gap) * 2.5);
  border-radius: 16px;
  background: #fff;
  box-shadow: var(--image-shadow);
}

/* Content rows: text on the left, an image on the right; stacked (text first)
   on narrow screens. */
.case-row {
  display: grid;
  gap: var(--gap) calc(var(--gap) * 2);
  align-items: center; /* text and image centred on each other */
  margin-bottom: calc(var(--gap) * 2.5);
}

@media (min-width: 800px) {
  .case-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

.case-row h3 {
  margin: 0 0 12px;
  font-size: calc(var(--section-title-size) * 0.9375); /* 25% up from 0.75 */
  font-weight: normal;
}

.case-row p {
  max-width: 60ch;
  margin: 0 0 1em;
  font-size: clamp(16px, 1.3vw, 19px);
  line-height: 1.6;
}

.case-row__media {
  margin: 0;
}

.case-row__media img,
.case-row__media video {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 16px;
  box-shadow: var(--image-shadow);
}

/* Stand-in until the real image is added. */
.case-row__placeholder {
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  border-radius: 16px;
  background: var(--bg-header);
  font-size: 14px;
  opacity: 0.6;
}

