:root {
  --bg: #F2F6FA;
  --ink: #0B0B0D;
  --ink-70: #3C4249;
  --ink-50: #5E656C;
  --line: rgba(11, 11, 13, 0.16);
  --line-soft: rgba(11, 11, 13, 0.09);
  --mint: #CFEFDB;
  --accent: #EEF23A;
  /* Dot field, after uiverse.io/Juanes200122/yellow-zebra-53 (MIT).
         Still far below the original rgba(12,12,12,0.171) at 2px, which reads
         as a pattern competing with the work. At 1.4px it stays paper tooth
         under the grain already on the page.
         The two themes are matched by perceived contrast, not by alpha. The
         dark grid is 0.075 white on the dark ground, which is a CIELAB
         deltaL* of 7.6; reaching the same 7.6 on paper takes 0.092, because
         L* is much more sensitive near black than near white. Copying 0.075
         across would land at 6.1 and read visibly weaker than dark. Move the
         two together, and re-solve rather than assuming they share a number. */
  --dot: rgba(11, 11, 13, 0.092);
  --dot-gap: 30px;
  --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --sans: "Instrument Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  color-scheme: light dark;
  --pad: clamp(20px, 5vw, 88px);
  /* Surfaces, edges and photo treatment are tokenised so the dark theme is a
         palette swap rather than a second stylesheet. Light values below are the
         literals this page shipped with; light mode renders unchanged. */
  --surface: rgba(255, 255, 255, 0.42);
  --surface-2: rgba(255, 255, 255, 0.5);
  --surface-hi: rgba(255, 255, 255, 0.85);
  --surface-row: rgba(255, 255, 255, 0.75);
  --surface-out: rgba(255, 255, 255, 0.7);
  --backer: #fff;
  --edge: var(--ink);
  --edge-strong: var(--ink);
  --nav-bg: rgba(242, 246, 250, 0.72);
  --ghost: rgba(11, 11, 13, 0.055);
  --mint-0: rgba(207, 239, 219, 0);
  --blob-a-o: 0.72;
  --blob-b-o: 0.5;
  --blob-c-o: 0.55;
  --blob-contact-o: 0.62;
  --grain-blend: multiply;
  --grain-o: 0.032;
  --photo-b: 1;
  --photo-b-hi: 1;
  --lift-tile: 0 18px 40px -26px rgba(11, 11, 13, 0.5);
  --lift-honour: 0 18px 34px -24px rgba(11, 11, 13, 0.55);
  --lift-chip: 0 10px 20px -12px rgba(11, 11, 13, 0.45);
  --edge-hover: var(--edge);
  --btn-solid-bg: var(--ink);
  --ambient-o: 0.85;
  --tile-glow-o: 0.75;
  --tile-glow-soft-o: 0.3;
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background-color: var(--bg);
  background-image: radial-gradient(var(--dot) 1.4px, transparent 0);
  background-size: var(--dot-gap) var(--dot-gap);
  background-position: -5px -5px;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
}

::selection {
  background: var(--accent);
  color: #0B0B0D;
}

a {
  color: var(--ink);
  text-decoration: none;
}

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 4px;
}

a:hover {
  color: var(--ink);
}

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

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

h1,
h2,
h3,
h4 {
  margin: 0;
  font-weight: 600;
  letter-spacing: -0.028em;
  text-wrap: balance;
}

/* ---------- reveal ---------- */
[data-anim="on"] [data-reveal] {
  opacity: 0;
  transform: translateY(16px);
}

[data-anim="on"] [data-reveal].is-in {
  opacity: 1;
  transform: none;
  transition: opacity 0.7s cubic-bezier(0.22, 0.61, 0.36, 1), transform 0.7s cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

  [data-anim="on"] [data-reveal] {
    opacity: 1;
    transform: none;
  }

  /* No animation means no reason to hold three blurred compositor layers. */
  .blob {
    animation: none !important;
    will-change: auto;
  }
}

/* ---------- shell ---------- */
.wrap {
  width: 100%;
  max-width: 1240px;
  margin: 0 auto;
  padding-left: var(--pad);
  padding-right: var(--pad);
}

.grain {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 6;
  background-image: var(--grain);
  opacity: var(--grain-o);
  mix-blend-mode: var(--grain-blend);
}

/* ---------- nav ---------- */
.nav {
  position: sticky;
  top: 0;
  z-index: 5;
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  background: var(--nav-bg);
  border-bottom: 1px solid var(--line-soft);
}

.nav-in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  height: 62px;
}

.mark {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.mark-badge {
  width: 26px;
  height: 26px;
  border: 1.4px solid var(--edge-strong);
  border-radius: 8px;
  display: grid;
  place-items: center;
  font-size: 11px;
  letter-spacing: -0.06em;
  line-height: 1;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 4px;
}

.nav-links a {
  position: relative;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--ink-70);
  padding: 8px 12px;
  border-radius: 8px;
  transition: color 0.2s ease, background 0.2s ease;
}

.nav-links a::after {
  content: "";
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 5px;
  height: 1.4px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.28s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.nav-links a:hover {
  color: var(--ink);
}

.nav-links a:hover::after {
  transform: scaleX(1);
}

.pill-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 38px;
  font-family: var(--mono);
  font-size: 12.5px;
  font-weight: 500;
  padding: 0 16px;
  border: 1.4px solid var(--edge-strong);
  border-radius: 999px;
  transition: background 0.22s ease, color 0.22s ease, transform 0.22s ease;
}

.pill-cta:hover {
  background: var(--ink);
  color: var(--bg);
  transform: translateY(-1px);
}

.pill-cta svg {
  transition: transform 0.24s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.pill-cta:hover svg {
  transform: translate(3px, -3px);
}

.nav-end {
  display: flex;
  align-items: center;
  gap: 10px;
}

.pill-cta {
  flex: 0 0 auto;
}

/* The toggle only does something with JS, so it stays out of the document
       until the head bootstrap marks the page ready, and no layout shift comes
       with it because that bootstrap runs before first paint. Note the gate is
       the bootstrap, not the click handler, so a page where the head script
       runs and the body script does not would show an inert button. */
.theme-toggle {
  display: none;
}

:root[data-theme-ready] .theme-toggle {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex: 0 0 auto;
  padding: 0;
  background: none;
  cursor: pointer;
  color: var(--ink);
  border: 1.4px solid var(--edge-strong);
  border-radius: 999px;
  transition: background 0.22s ease, color 0.22s ease, transform 0.22s ease;
}

.theme-toggle:hover {
  background: var(--ink);
  color: var(--bg);
  transform: translateY(-1px);
}

.theme-toggle svg {
  display: block;
}

:root[data-theme="dark"] .theme-toggle .icon-moon {
  display: none;
}

:root:not([data-theme="dark"]) .theme-toggle .icon-sun {
  display: none;
}

/* The clip-path animation below is driven entirely by the Web Animations
       call in the script, so the default cross-fade has to be switched off or
       the two run against each other. */
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

/* Unconditional on purpose. The pseudo tree only exists while a
       transition is running, so gating this on an attribute bought nothing and
       raced with itself: a second click aborted the first, whose rejected
       ready promise then tore the attribute down mid-flight and dropped the
       group back to the UA default duration. */
::view-transition-group(root) {
  animation-duration: 400ms;
}

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

  .theme-toggle:hover {
    transform: none;
  }
}

/* ---------- hero ---------- */
.hero {
  position: relative;
  overflow: hidden;
  padding: clamp(64px, 12vh, 128px) 0 clamp(56px, 9vh, 104px);
}

.mesh {
  position: absolute;
  inset: -20% -10%;
  z-index: 0;
  pointer-events: none;
}

.blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  will-change: transform;
}

.blob-a {
  width: 52%;
  height: 62%;
  left: -8%;
  top: -6%;
  opacity: var(--blob-a-o);
  animation: drift-a 26s ease-in-out infinite alternate;
}

.blob-b {
  width: 46%;
  height: 58%;
  left: 34%;
  top: 18%;
  opacity: var(--blob-b-o);
  animation: drift-b 32s ease-in-out infinite alternate;
}

.blob-c {
  width: 40%;
  height: 50%;
  right: -6%;
  top: 4%;
  opacity: var(--blob-c-o);
  background: radial-gradient(circle, var(--mint) 0%, var(--mint-0) 70%);
  animation: drift-a 30s ease-in-out infinite alternate-reverse;
}

@keyframes drift-a {
  from {
    transform: translate3d(0, 0, 0) scale(1);
  }

  to {
    transform: translate3d(4%, 3%, 0) scale(1.08);
  }
}

@keyframes drift-b {
  from {
    transform: translate3d(0, 0, 0) scale(1.04);
  }

  to {
    transform: translate3d(-5%, -2%, 0) scale(0.96);
  }
}

.ghost {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  user-select: none;
  left: 50%;
  top: 46%;
  transform: translate3d(0, 0, 0);
  font-family: var(--mono);
  font-weight: 400;
  font-size: clamp(300px, 44vw, 660px);
  line-height: 0.78;
  letter-spacing: -0.16em;
  color: var(--ghost);
  white-space: nowrap;
  margin-left: -0.62em;
  margin-top: -0.5em;
}

.hero-in {
  position: relative;
  z-index: 1;
}

.eyebrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-70);
}

.eyebrow .sep {
  width: 22px;
  height: 1px;
  background: var(--line);
}

h1.display {
  margin: 26px 0 0;
  font-size: clamp(40px, 6.6vw, 88px);
  line-height: 0.98;
  letter-spacing: -0.038em;
  max-width: 17ch;
}

.hero-sub {
  margin-top: 26px;
  max-width: 60ch;
  font-size: clamp(16px, 1.25vw, 19px);
  color: var(--ink-70);
}

.hero-sub strong {
  color: var(--ink);
  font-weight: 600;
}

.hero-sub em {
  font-style: italic;
  color: var(--ink);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 34px;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 0 22px;
  border-radius: 999px;
  border: 1.4px solid var(--edge-strong);
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 500;
  transition: transform 0.22s ease, background 0.22s ease, color 0.22s ease;
}

.btn-solid {
  background: var(--btn-solid-bg);
  border-color: var(--btn-solid-bg);
  color: var(--bg);
}

.btn-solid:hover {
  color: var(--bg);
  transform: translateY(-2px);
}

.btn-ghost:hover {
  background: var(--ink);
  color: var(--bg);
  transform: translateY(-2px);
}

.btn svg {
  transition: transform 0.24s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Scrambled email label. Monospace plus a ch-based min-width keeps the
       button exactly one width whether the text is scrambled or resolved. */
.scramble {
  font-family: var(--mono);
  font-variant-ligatures: none;
  white-space: pre;
}

.btn:hover svg {
  transform: translate(3px, -3px);
}

.platforms {
  margin-top: 54px;
  padding-top: 20px;
  border-top: 1px solid var(--line-soft);
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-50);
}

/* ---------- sections ---------- */
section {
  position: relative;
  scroll-margin-top: 80px;
}

.band {
  padding: clamp(72px, 11vh, 132px) 0;
}

.band-line {
  border-top: 1px solid var(--line-soft);
}

/* The heading inside .label. `font: inherit` and `letter-spacing: normal`
       undo the shared heading reset so the label looks exactly as it did
       when it was a <span>. */
.label-title {
  font: inherit;
  letter-spacing: normal;
  margin: 0;
}

.label {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 600;
}

.dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--ink);
  flex: 0 0 auto;
}

.label-link {
  text-decoration: none;
  transition: color 0.2s ease;
}

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

.label-note {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-50);
  font-weight: 400;
  letter-spacing: 0.02em;
}

.head-row {
  display: flex;
  align-items: baseline;
  gap: 16px;
  flex-wrap: wrap;
}

/* ---------- project ---------- */
.project {
  display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
  gap: clamp(28px, 4vw, 64px);
  margin-top: 52px;
}

.project + .project {
  margin-top: clamp(72px, 9vw, 120px);
  padding-top: clamp(72px, 9vw, 120px);
  border-top: 1px solid var(--line-soft);
}

.proj-title {
  font-size: clamp(32px, 3.6vw, 50px);
  line-height: 1.02;
  letter-spacing: -0.035em;
}

.proj-kicker {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-50);
  margin-bottom: 14px;
}

.proj-lead {
  margin-top: 20px;
  font-size: 17px;
  color: var(--ink-70);
  max-width: 46ch;
}

.proj-quote {
  margin-top: 16px;
  font-family: var(--mono);
  font-size: 14px;
  line-height: 1.7;
  color: var(--ink);
  max-width: 44ch;
}

.meta {
  list-style: none;
  margin: 26px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.meta li {
  display: flex;
  gap: 12px;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-70);
}

.meta .plus {
  color: var(--ink);
  flex: 0 0 auto;
}

.rule {
  height: 1px;
  background: var(--line-soft);
  margin: 26px 0;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  display: inline-flex;
  align-items: center;
  border: 1.2px solid var(--edge);
  border-radius: 999px;
  padding: 5px 12px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.03em;
  transition: transform 0.24s cubic-bezier(0.22, 0.61, 0.36, 1), box-shadow 0.24s ease, background 0.24s ease, color 0.24s ease;
}

.chip-soft {
  border-color: var(--line);
  color: var(--ink-70);
}

.chips .chip:hover {
  transform: translateY(-2px);
  background: var(--ink);
  color: var(--bg);
  box-shadow: var(--lift-chip);
}

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

  .chips .chip:hover {
    transform: none;
  }
}

.awards-detail {
  margin-top: 14px;
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.85;
  color: var(--ink-70);
  max-width: 52ch;
}

.links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 26px;
}

.link-out {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 0 15px;
  border: 1.2px solid var(--line);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-70);
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}

.link-out:hover {
  border-color: var(--ink);
  color: var(--ink);
  background: var(--surface-out);
  transform: translateY(-1px);
}

.link-out svg {
  transition: transform 0.24s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.link-out:hover svg {
  transform: translate(3px, -3px);
}

.shots {
  display: grid;
  gap: 14px;
  align-content: start;
}

.shots-key {
  grid-template-columns: 1fr;
}

.shots-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.frame {
  overflow: hidden;
  border-radius: 14px;
  border: 1.4px solid var(--edge);
  background: var(--surface-2);
}

.frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(1.02) brightness(var(--photo-b));
  transition: transform 0.6s cubic-bezier(0.22, 0.61, 0.36, 1), filter 0.8s ease;
}

.frame:hover img {
  transform: scale(1.035);
  filter: grayscale(0) contrast(1.02) brightness(var(--photo-b-hi));
}

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

.frame-key img {
  aspect-ratio: 16 / 10;
}

.frame-sq img {
  aspect-ratio: 3 / 2;
}

.frame-tall img {
  aspect-ratio: 9 / 19;
  object-position: top;
}

/* Ambient video, after ui.unlumen.com/components/video-ambient: a low
       resolution canvas samples the playing frame, then CSS scales and blurs it
       behind the player so the glow is the footage's own colour, live. The
       canvas is 64x36 because it is only ever seen through a 60px blur. */
.ambient {
  position: relative;
  margin-top: 14px;
}

.ambient-glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  transform: scale(1.06) translateZ(0);
  filter: blur(60px) saturate(1.5);
  opacity: var(--ambient-o);
  pointer-events: none;
  border-radius: 16px;
}

.ambient video {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: auto;
  border-radius: 14px;
  background: #0B0B0D;
}

@media (prefers-reduced-motion: reduce) {
  .ambient-glow {
    display: none;
  }
}

/* ---------- tiles ---------- */
.tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 28px);
  margin-top: 44px;
}

.tile {
  display: flex;
  flex-direction: column;
  gap: 0;
  border: 1.4px solid var(--edge);
  border-radius: 16px;
  padding: 12px 12px 18px;
  background: var(--surface);
  transition: transform 0.28s cubic-bezier(0.22, 0.61, 0.36, 1), background 0.28s ease, box-shadow 0.28s ease;
}

.tile:hover {
  transform: translateY(-5px);
  background: var(--surface-hi);
  box-shadow: var(--lift-tile);
  border-color: var(--edge-hover);
}

.tile-img {
  overflow: hidden;
  border-radius: 9px;
  background: var(--backer);
}

.tile-img img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  filter: grayscale(1) contrast(1.02) brightness(var(--photo-b));
  transition: transform 0.6s cubic-bezier(0.22, 0.61, 0.36, 1), filter 0.8s ease;
}

.tile:hover .tile-img img {
  transform: scale(1.04);
  filter: grayscale(0) contrast(1.02) brightness(var(--photo-b-hi));
}

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

.tile-body {
  padding: 16px 6px 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tile-name {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 16px;
  font-weight: 600;
}

.tile-plat {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--ink-70);
}

.tile-date {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-50);
}

.tile-note {
  margin-top: 8px;
  font-size: 14px;
  color: var(--ink-70);
  line-height: 1.5;
}

/* Tile ambient: the cover art stays the default state, and the clip fades
       in over it while the tile is hovered, lit by the same canvas glow the
       hero players use. Nothing downloads until the pointer arrives. */
.tile {
  position: relative;
}

.tile-glow {
  position: absolute;
  inset: 4px;
  z-index: 0;
  width: calc(100% - 8px);
  height: calc(100% - 8px);
  border-radius: 13px;
  filter: blur(42px) saturate(1.5);
  opacity: 0;
  transition: opacity 0.5s ease;
  pointer-events: none;
}

.tile.playing .tile-glow {
  opacity: var(--tile-glow-o);
}

/* Footage with a bright, saturated palette throws too much colour at
       this size, so those tiles get a dimmer, wider glow. */
.tile.playing .tile-glow-soft {
  opacity: var(--tile-glow-soft-o);
  filter: blur(54px) saturate(1.1);
}

.tile-img,
.tile-body {
  position: relative;
  z-index: 1;
}

.tile-vid {
  position: absolute;
  inset: 0;
  z-index: 2;
  opacity: 0;
  transition: opacity 0.45s ease;
  pointer-events: none;
}

.tile-vid video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tile.playing .tile-vid {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .tile-vid {
    display: none;
  }
}

.tag {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  border: 1.1px solid var(--edge-strong);
  border-radius: 999px;
  padding: 3px 8px;
  line-height: 1;
}

/* ---------- explorations ---------- */
.note-card {
  margin-top: clamp(48px, 6vw, 76px);
  border: 1px dashed var(--line);
  border-radius: 16px;
  padding: clamp(22px, 3vw, 34px);
  display: grid;
  grid-template-columns: minmax(0, 0.4fr) minmax(0, 1fr);
  gap: clamp(18px, 3vw, 42px);
}

.note-card h4 {
  font-size: 22px;
  letter-spacing: -0.025em;
}

/* ---------- about ---------- */
.about {
  display: grid;
  grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.28fr);
  gap: clamp(28px, 4vw, 68px);
  margin-top: 48px;
  align-items: start;
}

.portrait-wrap {
  position: relative;
}

.portrait {
  width: 100%;
  max-width: 300px;
  aspect-ratio: 1;
  border-radius: 50%;
  overflow: hidden;
  border: 1.4px solid var(--edge-strong);
}

.portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 22%;
  filter: grayscale(1) brightness(var(--photo-b));
}

.about-body p + p {
  margin-top: 18px;
}

.about-body {
  font-size: 17px;
  color: var(--ink-70);
  max-width: 62ch;
}

.about-body strong {
  color: var(--ink);
  font-weight: 600;
}

.facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 22px 34px;
  margin-top: 34px;
}

.fact-k {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-50);
}

.fact-v {
  font-family: var(--mono);
  font-size: 13.5px;
  margin-top: 6px;
  line-height: 1.5;
}

.timeline {
  list-style: none;
  margin: 40px 0 0;
  padding: 0;
  border-top: 1px solid var(--line-soft);
}

.timeline li {
  display: grid;
  grid-template-columns: 82px minmax(0, 1fr);
  gap: 18px;
  align-items: baseline;
  padding: 15px 0;
  border-bottom: 1px solid var(--line-soft);
  font-family: var(--mono);
  font-size: 13.5px;
}

.timeline .yr {
  color: var(--ink-50);
  letter-spacing: 0.08em;
}

.earlier {
  margin-top: 26px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-50);
  line-height: 1.8;
}

/* ---------- honours ---------- */
.honours {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(20px, 2.6vw, 34px) clamp(16px, 2vw, 28px);
  margin-top: 52px;
}

.honour {
  position: relative;
  padding-bottom: 20px;
}

.honour-img {
  overflow: hidden;
  border-radius: 13px;
  border: 1.4px solid var(--edge);
  background: var(--backer);
  transition: transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1), box-shadow 0.3s ease;
}

.honour-img img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  filter: grayscale(1) contrast(1.03) brightness(var(--photo-b));
  transition: filter 0.7s ease;
}

.honour:hover .honour-img {
  transform: translateY(-4px);
  box-shadow: var(--lift-honour);
  border-color: var(--edge-hover);
}

.honour:hover .honour-img img {
  filter: grayscale(0.35) contrast(1.03) saturate(1.15) brightness(var(--photo-b-hi));
}

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

.honour-cap {
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  background: var(--backer);
  border: 1.2px solid var(--edge-strong);
  border-radius: 999px;
  padding: 6px 16px;
  font-family: var(--mono);
  font-size: 11.5px;
  white-space: nowrap;
}

/* ---------- press ---------- */
.press {
  list-style: none;
  margin: 48px 0 0;
  padding: 0;
  border-top: 1px solid var(--line);
}

.press > li {
  border-bottom: 1px solid var(--line-soft);
}

.press-row {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr) 92px 28px;
  gap: 20px;
  align-items: center;
  padding: 20px 10px;
  min-height: 64px;
  border-radius: 12px;
  transition: background 0.24s ease;
}

a.press-row:hover {
  background: var(--surface-row);
}

.press-src {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-50);
}

.press-title {
  font-size: 17px;
  letter-spacing: -0.012em;
}

.press-yr {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-50);
  text-align: right;
}

.press-arrow {
  display: grid;
  place-items: center;
  color: var(--ink-50);
  transition: transform 0.26s cubic-bezier(0.22, 0.61, 0.36, 1), color 0.24s ease;
}

a.press-row:hover .press-arrow {
  transform: translate(4px, -4px);
  color: var(--ink);
}

.covered {
  margin-top: 30px;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px 18px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-70);
}

.covered b {
  color: var(--ink-50);
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  font-size: 11px;
}

/* ---------- contact ---------- */
.contact {
  position: relative;
  overflow: hidden;
  padding: clamp(84px, 13vh, 150px) 0 0;
}

.contact-in {
  position: relative;
  z-index: 1;
}

.contact h3 {
  font-size: clamp(36px, 5.4vw, 74px);
  line-height: 1.0;
  letter-spacing: -0.04em;
  max-width: 16ch;
}

.contact-links {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 38px;
}

.foot {
  margin-top: clamp(64px, 9vh, 110px);
  padding: 22px 0 30px;
  border-top: 1px solid var(--line-soft);
  display: flex;
  flex-wrap: wrap;
  gap: 10px 24px;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--ink-50);
  letter-spacing: 0.04em;
}

/* ---------- responsive ---------- */
@media (max-width: 1000px) {
  .project {
    grid-template-columns: minmax(0, 1fr);
  }

  .about {
    grid-template-columns: minmax(0, 1fr);
  }

  .portrait {
    max-width: 190px;
  }

  .tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .honours {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .press-row {
    grid-template-columns: 150px minmax(0, 1fr) 76px 24px;
    gap: 14px;
  }
}

@media (max-width: 720px) {
  body {
    font-size: 16px;
  }

  .nav-links {
    display: none;
  }

  .nav-in {
    height: 58px;
  }

  .pill-cta {
    min-height: 44px;
    padding: 0 18px;
  }

  .ghost {
    font-size: 300px;
    top: 52%;
  }

  .tiles {
    grid-template-columns: minmax(0, 1fr);
  }

  .honours {
    grid-template-columns: minmax(0, 1fr);
  }

  .shots-3 {
    display: flex;
    gap: 10px;
    overflow-x: auto;
    padding-bottom: 4px;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
  }

  .shots-3 > .frame {
    flex: 0 0 60%;
    scroll-snap-align: start;
  }

  .note-card {
    grid-template-columns: minmax(0, 1fr);
  }

  .facts {
    grid-template-columns: minmax(0, 1fr);
  }

  .press-row {
    grid-template-columns: minmax(0, 1fr) 26px;
    gap: 6px 14px;
    padding: 18px 8px;
    align-items: start;
  }

  .press-src {
    grid-column: 1;
    order: 1;
  }

  .press-title {
    grid-column: 1;
    order: 3;
  }

  .press-yr {
    grid-column: 1;
    order: 4;
    text-align: left;
  }

  .press-arrow {
    grid-column: 2;
    grid-row: 1 / span 3;
    order: 2;
    align-self: center;
  }

  .timeline li {
    grid-template-columns: 64px minmax(0, 1fr);
    gap: 12px;
    font-size: 13px;
  }

  .btn,
  .link-out {
    min-height: 46px;
  }

  .label-note {
    margin-left: 0;
    width: 100%;
  }
}

/* ---------- dark ----------
       A darkroom, not an inverted sheet of paper.

       The ground keeps the light theme's cool blue cast instead of going
       neutral charcoal, and every foreground is tinted from that same hue
       rather than grayed. The three text tokens land within half a point of
       the light theme's own contrast ratios (ink-70 8.9:1 against 9.4:1,
       ink-50 5.4:1 against 5.4:1), so the hierarchy reads at the same weight
       rather than merely passing.

       Three things cannot be swapped and are rebuilt:
       1. The grain multiplies to nothing on a dark ground, so it screens.
       2. The shadow lift is invisible, so elevation becomes edge-light:
          the surface, the border and a top highlight brighten together.
       3. The blobs and the ambient video glow stop being pigment on paper
          and become light, which is far louder, so their opacity drops.
       Photographs pull back to 86 percent brightness; grayscale stills that
       sit correctly on paper blaze against near-black.

       THE TOKEN LIST BELOW APPEARS TWICE AND THE TWO COPIES MUST MATCH.
       The media query serves visitors who have expressed no preference, and
       covers the no-JS path (the reveal system already degrades to visible).
       The attribute rule serves an explicit choice made with the toggle,
       which is why it has to beat the media query rather than live inside
       it. Keep both in sync when a token changes. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0B0E13;
    --ink: #E9EDF3;
    --ink-70: #A7B1BF;
    --ink-50: #7E8896;
    --line: rgba(233, 237, 243, 0.20);
    --line-soft: rgba(233, 237, 243, 0.11);
    --mint: #6FE3A8;
    --mint-0: rgba(111, 227, 168, 0);
    --dot: rgba(233, 237, 243, 0.075);
    --ghost: rgba(233, 237, 243, 0.05);
    --surface: rgba(233, 237, 243, 0.035);
    --surface-2: rgba(233, 237, 243, 0.04);
    --surface-hi: rgba(233, 237, 243, 0.075);
    --surface-row: rgba(233, 237, 243, 0.055);
    --surface-out: rgba(233, 237, 243, 0.08);
    --backer: #14181F;
    --edge: rgba(233, 237, 243, 0.20);
    --edge-hover: rgba(233, 237, 243, 0.34);
    --edge-strong: rgba(233, 237, 243, 0.44);
    --btn-solid-bg: var(--accent);
    --nav-bg: rgba(11, 14, 19, 0.72);
    --grain-blend: screen;
    --grain-o: 0.05;
    --photo-b: 0.72;
    --photo-b-hi: 0.95;
    /* A dim yellow is olive: any low-intensity yellow over near-black
           lands around rgb(88,92,32), army green rather than lamplight.
           Lowering opacity cannot fix that because the problem is hue, not
           amount, so the blobs get a higher-lightness, lower-chroma yellow
           for dark and let the CTA carry the saturated brand accent. */
    --glow: #F7F0AE;
    --blob-a-o: 0.34;
    --blob-b-o: 0.22;
    --blob-c-o: 0.26;
    --blob-contact-o: 0.3;
    --ambient-o: 0.3;
    --tile-glow-o: 0.28;
    --tile-glow-soft-o: 0.12;
    --lift-tile: inset 0 1px 0 rgba(233, 237, 243, 0.10);
    --lift-honour: inset 0 1px 0 rgba(233, 237, 243, 0.10);
    --lift-chip: none;
  }
}

:root[data-theme="dark"] {
  --bg: #0B0E13;
  --ink: #E9EDF3;
  --ink-70: #A7B1BF;
  --ink-50: #7E8896;
  --line: rgba(233, 237, 243, 0.20);
  --line-soft: rgba(233, 237, 243, 0.11);
  --mint: #6FE3A8;
  --mint-0: rgba(111, 227, 168, 0);
  --dot: rgba(233, 237, 243, 0.075);
  --ghost: rgba(233, 237, 243, 0.05);
  --surface: rgba(233, 237, 243, 0.035);
  --surface-2: rgba(233, 237, 243, 0.04);
  --surface-hi: rgba(233, 237, 243, 0.075);
  --surface-row: rgba(233, 237, 243, 0.055);
  --surface-out: rgba(233, 237, 243, 0.08);
  --backer: #14181F;
  --edge: rgba(233, 237, 243, 0.20);
  --edge-hover: rgba(233, 237, 243, 0.34);
  --edge-strong: rgba(233, 237, 243, 0.44);
  --btn-solid-bg: var(--accent);
  --nav-bg: rgba(11, 14, 19, 0.72);
  --grain-blend: screen;
  --grain-o: 0.05;
  --photo-b: 0.72;
  --photo-b-hi: 0.95;
  /* A dim yellow is olive: any low-intensity yellow over near-black
           lands around rgb(88,92,32), army green rather than lamplight.
           Lowering opacity cannot fix that because the problem is hue, not
           amount, so the blobs get a higher-lightness, lower-chroma yellow
           for dark and let the CTA carry the saturated brand accent. */
  --glow: #F7F0AE;
  --blob-a-o: 0.34;
  --blob-b-o: 0.22;
  --blob-c-o: 0.26;
  --blob-contact-o: 0.3;
  --ambient-o: 0.3;
  --tile-glow-o: 0.28;
  --tile-glow-soft-o: 0.12;
  --lift-tile: inset 0 1px 0 rgba(233, 237, 243, 0.10);
  --lift-honour: inset 0 1px 0 rgba(233, 237, 243, 0.10);
  --lift-chip: none;
}

:root[data-theme="dark"] {
  color-scheme: dark;
}

:root[data-theme="light"] {
  color-scheme: light;
}

.tile-img img,
.honour-img img {
  height: auto
}
