:root {
  /* Night Signal: near-black ground, signal white, glass panels above it. */
  --ground: #0f1011;
  --white: #0f1011;        /* section "light" ground is still the dark ground */
  --black: var(--fg);        /* text on that ground */
  --dark: #16181a;
  --paper-deep: rgba(var(--fg-rgb), 0.06);
  --glass: rgba(var(--fg-rgb), 0.08);
  --glass-raised: rgba(var(--fg-rgb), 0.16);
  /* The aurora: one compound accent, never three colours sprinkled separately. */
  --aurora: linear-gradient(105deg, #d1ddd3 0%, #cdabfe 52%, #fe7141 100%);
  --coral: #fe7141;
  --reveal: #d1ddd3;
  --mediate: #fe7141;
  --translate: #cdabfe;
  --reveal-wash: #eef3ef;
  --mediate-wash: #fff1ea;
  --translate-wash: #f4edff;
  --line: rgba(var(--fg-rgb), 0.14);
  --line-invert: rgba(var(--fg-rgb), 0.14);
  --max: 1320px;
  --ease: cubic-bezier(0.455, 0.03, 0.515, 0.955);
  --radius: 8px;
  /* One foreground, expressed as a triplet so alpha variants can be built
     from it. A theme flips this and every rule above follows. */
  --fg: #ffffff;
  --fg-rgb: 255, 255, 255;
  --nav-glass: rgba(15, 16, 17, 0.66);
  --mono: system-ui, sans-serif;
  --sans: "Hanken Grotesk", -apple-system, BlinkMacSystemFont, sans-serif;
  --serif: "Fraunces", Georgia, serif;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: var(--sans);
  font-weight: 400;
  color: var(--fg);
  background: var(--ground);
  letter-spacing: 0;
}

body.is-ready {
  opacity: 1;
  transition: opacity 360ms ease;
}

body.page-exit {
  opacity: 0;
  transition: opacity 180ms ease;
}

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

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

p {
  font-size: 16px;
  line-height: 1.62;
  margin: 0;
}

.eyebrow,
.mono,
.tag,
.meta-grid,
.button,
.nav-link,
.section-number,
.card-meta,
.footer {
  font-family: var(--mono);
  letter-spacing: 0;
}

.brand-mark {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 1px solid currentColor;
  font-family: var(--mono);
  font-size: 11px;
}

.section {
  padding: 88px 24px;
}

.section-inner {
  max-width: var(--max);
  margin: 0 auto;
}

.hero {
  min-height: 92vh;
  display: flex;
  align-items: center;
  padding-top: 150px;
  padding-bottom: 90px;
  position: relative;
  overflow: hidden;
}

/* Drifting cloud field in the three verb colours, with a halftone screen over it.
   Three large soft radial blooms move on slow, differently-timed loops so they never
   repeat the same arrangement. */
.hero:before {
  content: "";
  position: absolute;
  inset: -12%;
  pointer-events: none;
  background:
    radial-gradient(38% 44% at 20% 28%, rgba(205, 171, 254, 0.30), transparent 68%),
    radial-gradient(42% 38% at 78% 30%, rgba(254, 113, 65, 0.22), transparent 66%),
    radial-gradient(46% 46% at 52% 78%, rgba(209, 221, 211, 0.22), transparent 70%);
  filter: blur(70px) saturate(1.1);
  animation: cloud-drift 34s ease-in-out infinite alternate;
  mask-image: linear-gradient(to bottom, black 8%, transparent 82%);
}

.hero:after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(circle at center, rgba(var(--fg-rgb), 0.10) 1px, transparent 1.6px);
  background-size: 6px 6px;
  mask-image: linear-gradient(to bottom, black, transparent 72%);
}

@keyframes cloud-drift {
  0% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  33% {
    transform: translate3d(3%, -2.5%, 0) scale(1.08);
  }
  66% {
    transform: translate3d(-2.5%, 2%, 0) scale(1.04);
  }
  100% {
    transform: translate3d(1.5%, 3%, 0) scale(1.11);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero:before {
    animation: none;
  }
}

.hero-grid {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 68px;
  align-items: center;
  width: 100%;
}

.hero h1,
.case-hero h1 {
  margin: 0;
  max-width: 17ch;
  font-family: var(--serif);
  font-size: clamp(42px, 5.6vw, 88px);
  line-height: 1.02;
  font-weight: 300;
  letter-spacing: -0.015em;
}

.hero h1 em,
.case-hero h1 em,
.section-title em {
  font-style: italic;
  font-weight: 300;
}

.hero-copy {
  display: grid;
  gap: 24px;
  max-width: 560px;
}

.hero-copy p {
  opacity: 0.8;
}

.hero-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 18px;
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--glass);
  backdrop-filter: blur(14px);
}

.stat {
  padding: 0;
}

.stat strong {
  display: block;
  font-family: var(--mono);
  font-size: clamp(24px, 2.2vw, 34px);
  line-height: 1;
  font-weight: 500;
  background: var(--aurora);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.stat span {
  display: block;
  margin-top: 10px;
  font-family: var(--mono);
  font-size: 10px;
  line-height: 1.35;
  text-transform: uppercase;
  opacity: 0.62;
}

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

.tag {
  border: 1px solid currentColor;
  border-radius: 0;
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1;
  text-transform: uppercase;
}

/* Filled tags carry their own edge through colour. The extra outline was making
   every chip read as a form field. */
.tag {
  color: rgba(var(--fg-rgb), 0.82);
  border-color: var(--line);
  font-family: var(--mono);
}

.tag.reveal,
.tag.mediate,
.tag.translate,
.tag.synthesis {
  border-color: transparent;
  color: #0f1011;
}

.tag.reveal {
  background: var(--reveal);
}

.tag.mediate {
  background: var(--mediate);
}

.tag.translate {
  background: var(--translate);
}

.tag.synthesis {
  background: var(--aurora);
  color: #0f1011;
}

.scroll-cue {
  position: absolute;
  bottom: 28px;
  left: 24px;
  font-family: var(--mono);
  font-size: 12px;
  text-transform: uppercase;
}

/* Splash. Three phrases arrive a beat apart, the group lifts, the second line
   answers it, then the whole thing clears to the page. */
.splash {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: 40px 24px;
  background: var(--white);
  transition: opacity 900ms ease, visibility 900ms;
}

.splash.is-done {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.splash:before {
  content: "";
  position: absolute;
  inset: -12%;
  pointer-events: none;
  background:
    radial-gradient(38% 44% at 24% 32%, rgba(205, 171, 254, 0.6), transparent 68%),
    radial-gradient(42% 38% at 76% 28%, rgba(254, 113, 65, 0.42), transparent 66%),
    radial-gradient(46% 46% at 50% 74%, rgba(209, 221, 211, 0.7), transparent 70%);
  filter: blur(56px) saturate(1.1);
  animation: cloud-drift 34s ease-in-out infinite alternate;
}

.splash-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
}

.splash-inner {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 30px;
  max-width: 900px;
  text-align: left;
  transition: transform 1000ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.splash-inner.lifted {
  transform: translateY(-46px);
}

.splash-line {
  margin: 0;
  font-size: clamp(24px, 3vw, 42px);
  line-height: 1.28;
  font-weight: 300;
  letter-spacing: -0.01em;
  color: var(--black);
  text-shadow: 0 1px 22px rgba(var(--fg-rgb), 0.5);
}

.splash-second {
  font-size: clamp(17px, 1.7vw, 24px);
  font-weight: 300;
  font-style: italic;
  line-height: 1.4;
  color: rgba(0, 0, 0, 0.82);
  opacity: 0;
  transition: opacity 500ms ease;
}

.splash-second.is-shown {
  opacity: 1;
}

.splash-phrase {
  display: block;
}

.splash-second .splash-phrase {
  display: inline;
}

.splash-skip {
  position: absolute;
  z-index: 2;
  right: 26px;
  bottom: 24px;
  border: 1px solid var(--line);
  background: transparent;
  color: rgba(0, 0, 0, 0.6);
  padding: 9px 14px;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  cursor: pointer;
}

.splash-skip:hover {
  border-color: currentColor;
  color: var(--black);
}

body.splash-active {
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .splash:before {
    animation: none;
  }
  .splash-inner {
    transition: none;
  }
}

/* LineNav. The horizontal line menu, and the only navigation on the page.
   --effect (0..1) is eased per item by the rAF loop in script.js, so label colour,
   vertical shift and tick height all read the same continuously-animating value. */
.line-nav {
  --accent-color: var(--mediate);
  --text-color: rgba(0, 0, 0, 0.5);
  --marker-color: rgba(0, 0, 0, 0.2);
  --tick-height: 14px;
  --max-shift: 5px;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  padding: 20px 30px 16px;
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
  transition: opacity 420ms ease, transform 420ms ease, color 400ms ease;
}

/* Arrives once the reader starts moving, so the opening screen stays clean. */
.line-nav.is-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.line-nav.on-dark {
  --text-color: rgba(var(--fg-rgb), 0.62);
  --marker-color: rgba(var(--fg-rgb), 0.28);
}

.line-nav__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: color-mix(in srgb, var(--text-color) 100%, transparent);
  flex: 0 0 auto;
}

.line-nav.on-dark .line-nav__brand {
  color: rgba(var(--fg-rgb), 0.9);
}

.line-nav__brand .brand-mark {
  width: 30px;
  height: 30px;
  font-size: 11px;
}

.line-sidebar__list {
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  align-items: start;
}

.line-sidebar__item {
  position: relative;
  cursor: pointer;
  padding-top: 16px;
  padding-right: 24px;
}

.line-sidebar__item::before {
  content: "";
  position: absolute;
  inset: -8px -20px;
}

.line-sidebar__label {
  position: relative;
  display: inline-flex;
  align-items: baseline;
  font-family: var(--mono);
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.2;
  color: color-mix(in srgb, var(--accent-color) calc(var(--effect, 0) * 100%), var(--text-color));
  transform: translateY(calc(var(--effect, 0) * var(--max-shift)));
}

.line-sidebar__index {
  margin-right: 7px;
  font-size: 0.85em;
  opacity: calc(0.45 + var(--effect, 0) * 0.55);
}

/* Vertical tick above each label, growing as the cursor nears it. */
.line-sidebar__marker {
  position: absolute;
  top: 0;
  left: 0;
  width: 26px;
  height: 1px;
  background-color: color-mix(in srgb, var(--accent-color) calc(var(--effect, 0) * 100%), var(--marker-color));
  transform-origin: left center;
  transform: scaleX(calc(1 + var(--effect, 0) * 1.4));
}

/* Half-height tick sitting in the gap between two items. */


.line-nav.case-nav {
  justify-content: space-between;
  gap: 24px;
}

.line-nav__link {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-color);
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
  transition: color 220ms ease, border-color 220ms ease;
}

.line-nav__link:hover {
  color: var(--accent-color);
  border-bottom-color: var(--accent-color);
}

@media (max-width: 720px) {
  .line-nav {
    gap: 18px;
    padding: 14px 16px 12px;
  }

  .line-nav__name {
    display: none;
  }

  .line-sidebar__list {
    grid-template-columns: repeat(2, 1fr);
    row-gap: 12px;
  }
}

/* BlurText. Three-stage arrival per word: blur 10px at zero opacity and offset,
   through a half-resolved midpoint, to sharp. Staggered per word by --bt-delay. */
.hero-verse {
  max-width: 30ch;
  font-size: clamp(19px, 1.6vw, 24px);
  line-height: 1.45;
  font-style: italic;
  color: rgba(0, 0, 0, 0.72);
}

.blur-text .bt-word {
  display: inline-block;
  opacity: 0;
  will-change: transform, filter, opacity;
}

.blur-text.is-lit .bt-word {
  animation: blur-text-in 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  animation-delay: var(--bt-delay, 0ms);
}

.blur-text.from-top .bt-word {
  --bt-from-y: -50px;
  --bt-mid-y: 5px;
}

.blur-text.from-bottom .bt-word {
  --bt-from-y: 50px;
  --bt-mid-y: -5px;
}

@keyframes blur-text-in {
  0% {
    filter: blur(10px);
    opacity: 0;
    transform: translateY(var(--bt-from-y, -50px));
  }
  50% {
    filter: blur(5px);
    opacity: 0.5;
    transform: translateY(var(--bt-mid-y, 5px));
  }
  100% {
    filter: blur(0);
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .blur-text.is-lit .bt-word {
    animation: none;
  }
}

/* Blur highlight. The opening statement arrives out of focus and resolves word by
   word, so the sentence is read rather than skimmed. Key words carry an accent
   highlight that sweeps in behind them as they land. */
.blur-highlight .bh-word {
  display: inline-block;
  filter: blur(9px);
  opacity: 0.25;
  transform: translateY(0.14em);
  transition: filter 620ms cubic-bezier(0.22, 0.61, 0.36, 1), opacity 620ms ease, transform 620ms ease;
  transition-delay: var(--bh-delay, 0ms);
}

.blur-highlight.is-lit .bh-word {
  filter: blur(0);
  opacity: 1;
  transform: translateY(0);
}

.blur-highlight .bh-key {
  position: relative;
  padding: 0 0.08em;
}

.blur-highlight .bh-key::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.02em;
  height: 0.44em;
  z-index: -1;
  background: var(--bh-accent, var(--mediate));
  opacity: 0;
  transform: scaleX(0.15);
  transform-origin: left center;
  transition: transform 540ms cubic-bezier(0.22, 0.61, 0.36, 1), opacity 540ms ease;
  transition-delay: calc(var(--bh-delay, 0ms) + 240ms);
}

.blur-highlight.is-lit .bh-key::after {
  opacity: 0.55;
  transform: scaleX(1);
}

@media (prefers-reduced-motion: reduce) {
  .blur-highlight .bh-word,
  .blur-highlight .bh-key::after {
    transition: none;
    filter: none;
    opacity: 1;
    transform: none;
  }
}

.section-kicker {
  display: grid;
  grid-template-columns: 90px minmax(0, 1fr);
  border-top: 1px solid currentColor;
  padding-top: 22px;
  gap: 24px;
  align-items: end;
  margin-bottom: 44px;
  border-bottom: 1px solid currentColor;
  padding-bottom: 18px;
}

.section-number {
  font-size: 12px;
}

.section-title {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(30px, 3.6vw, 52px);
  line-height: 1.06;
  font-weight: 300;
  letter-spacing: -0.012em;
}

/* Featured Case is a full colour field, in Chula's own Reveal colour. It also keeps
   two black sections (Work, About) from running into each other. */


.featured-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(320px, 0.75fr);
  gap: 48px;
  align-items: center;
}

.image-frame {
  position: relative;
  border: 1px solid currentColor;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--dark);
}

.image-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Same processing language as the project cards, at hero scale. */
.image-frame.halftone::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image: radial-gradient(circle at center, rgba(var(--fg-rgb), 0.3) 1px, transparent 1.8px);
  background-size: 6px 6px;
  mix-blend-mode: overlay;
  opacity: 0.4;
  transition: opacity 700ms ease;
}

.image-frame.halftone:hover::after {
  opacity: 0;
}

.scan {
  filter: saturate(0.6) contrast(1.04);
  transition: filter 700ms ease, transform 700ms ease;
}

.image-frame:hover .scan {
  filter: none;
  transform: scale(1.015);
}

.featured .image-frame {
  min-height: 520px;
}

.featured-copy {
  display: grid;
  gap: 24px;
}

.featured-copy h2,
.case-section h2,
.about h2 {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(22px, 2.3vw, 32px);
  line-height: 1.18;
  font-weight: 300;
  letter-spacing: -0.006em;
}

.button {
  display: inline-flex;
  width: fit-content;
  align-items: center;
  gap: 10px;
  border: 1px solid currentColor;
  border-radius: var(--radius);
  padding: 12px 18px;
  text-transform: uppercase;
  font-size: 12px;
  font-weight: 500;
  transition: transform 0.3s var(--ease), background 0.3s var(--ease), color 0.3s var(--ease), box-shadow 0.3s var(--ease);
}

.button.dark {
  color: var(--black);
  background: var(--white);
}

.button.light {
  color: var(--white);
  background: var(--black);
}

.button:hover {
  transform: translateY(-2px);
}

.button .arrow,
.button span[aria-hidden] {
  transition: transform 0.3s var(--ease);
}

.button:hover span[aria-hidden] {
  transform: translateX(3px);
}



.work-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 24px;
}

.filter-button {
  border: 1px solid var(--line-invert);
  border-radius: 0;
  background: transparent;
  color: var(--white);
  padding: 9px 12px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.3s var(--ease), color 0.3s var(--ease), border-color 0.3s var(--ease);
}

.filter-button.is-active,
.filter-button:hover {
  background: var(--white);
  color: var(--black);
}

/* Bento grid. Cells of deliberate, unequal size rather than a balanced waterfall.
   The flagship takes a 2x2 block; the rest alternate wide, tall and square. */
.work-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 8px;
  gap: 14px;
  align-items: start;
}

/* Without JS the rows fall back to auto, which still reads correctly,
   just with ragged bottoms instead of a packed one. */
.work-grid:not([data-masonry]) { grid-auto-rows: auto; }

/* Spans set width only. Height comes from the cover at its own proportion,
   so no image is ever cropped to fill a cell. */
.project-card.flagship { grid-column: span 2; }
.project-card.big { grid-column: span 2; }
.project-card.medium { grid-column: span 2; }
.project-card.small { grid-column: span 1; }

@media (max-width: 1100px) {
  .work-grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .work-grid .project-card {
    grid-column: span 1;
  }
}

@media (max-width: 640px) {
  .work-grid {
    grid-template-columns: 1fr;
  }
  .work-grid .project-card {
    grid-column: span 1;
  }
}

.project-card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  backdrop-filter: blur(14px);
  border: 1px solid var(--line-invert);
  border-top: 4px solid var(--accent, var(--line-invert));
  border-radius: var(--radius);
  background: var(--glass);
  color: var(--fg);
  overflow: hidden;
  transition: transform 0.3s var(--ease), border-color 0.3s var(--ease), opacity 0.3s var(--ease), box-shadow 0.3s var(--ease);
}

/* The spotlight reads as ink warming under the cursor, not as a neon glow. */
.project-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  background: radial-gradient(
    260px circle at var(--glow-x, 50%) var(--glow-y, 50%),
    rgba(254, 113, 65, calc(var(--glow, 0) * 0.16)) 0%,
    transparent 62%
  );
  transition: opacity 0.3s var(--ease);
}

.project-card:hover {
  background: var(--glass-raised);
}

@supports (grid-template-rows: masonry) {
  .project-card {
    margin-bottom: 0;
  }
}

@supports (display: grid-lanes) {
  .project-card {
    margin-bottom: 0;
  }
}

.project-card:focus-visible {
  outline: 2px solid var(--accent, var(--white));
  outline-offset: 4px;
}

/* The category chip sits on top of the tinted image, so it has to invert against
   the accent instead of sharing it. */
.project-card::after {
  content: attr(data-label);
  position: absolute;
  top: 14px;
  left: 14px;
  z-index: 3;
  border-radius: 0;
  border: 1px solid var(--accent, var(--white));
  background: var(--black);
  color: var(--accent, var(--white));
  padding: 7px 9px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1;
  text-transform: uppercase;
}

.project-card.is-muted {
  display: none;
}

.project-card[data-category*="Reveal"] {
  --accent: var(--reveal);
}

.project-card[data-category*="Mediate"] {
  --accent: var(--mediate);
}

.project-card[data-category*="Translate"] {
  --accent: var(--translate);
}

#liminal-grounds {
  --accent: var(--reveal);
}

/* Duotone halftone. The image loads processed, tinted in its own R/M/T colour and
   screened with a dot grid, then resolves to the real photograph on hover. This is
   the practice's own Reveal verb made literal, and it gives nine source images shot
   under wildly different conditions one shared surface. */
.project-media {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  background: rgba(0, 0, 0, 0.35);
  border-bottom: 1px solid var(--line-invert);
}

.project-media img,
.project-media video {
  display: block;
  width: 100%;
  height: auto;
  filter: saturate(0.72) contrast(1.02);
  transition: filter 620ms ease, transform 620ms ease;
}

/* Colour wash. soft-light keeps the photograph's own midtones instead of crushing
   them onto the accent, which is what made the first version look grim. */
.project-media::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: var(--accent, transparent);
  mix-blend-mode: soft-light;
  opacity: 0.62;
  transition: opacity 620ms ease;
}

/* Dot screen, light enough to read as print texture rather than a scrim. */
.project-media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image: radial-gradient(circle at center, rgba(var(--fg-rgb), 0.34) 0.85px, transparent 1.5px);
  background-size: 5px 5px;
  mix-blend-mode: overlay;
  opacity: 0.45;
  transition: opacity 620ms ease;
}

.project-media.contain {
  background: #f6f6f4;
}



/* Static covers carry a second frame that cross-fades in on hover, so a card that
   cannot autoplay still answers the cursor. */
.project-media img.swap {
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
  object-fit: cover;
  opacity: 0;
}

.project-media.contain img.swap {
  object-fit: contain;
}

.project-card:hover .project-media img.swap,
.project-card:focus-visible .project-media img.swap {
  opacity: 1;
}

.project-card:hover .project-media img,
.project-card:hover .project-media video,
.project-card:focus-visible .project-media img,
.project-card:focus-visible .project-media video {
  filter: none;
  transform: scale(1.03);
}

.project-card:hover .project-media::before,
.project-card:hover .project-media::after,
.project-card:focus-visible .project-media::before,
.project-card:focus-visible .project-media::after {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .project-media img,
  .project-media video,
  .project-media::before,
  .project-media::after {
    transition: none;
  }
}

.project-body {
  position: relative;
  z-index: 3;
  display: grid;
  gap: 12px;
  align-content: start;
  padding: 20px;
  color: var(--fg);
}

.project-body p {
  opacity: 0.76;
}

.project-card .card-link-label {
  opacity: 0.55;
  transition: opacity 0.35s var(--ease);
}

.project-card:hover .card-link-label,
.project-card:focus-visible .card-link-label {
  opacity: 1;
}

.project-title-row {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: 16px;
  border-bottom: 2px solid var(--accent);
  padding-bottom: 10px;
}

.project-body h3 {
  margin: 0;
  font-size: clamp(19px, 1.7vw, 25px);
  line-height: 1.1;
  font-weight: 400;
}

.project-arrow {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 16px;
  transition: transform 180ms ease;
}

.project-card:hover .project-arrow {
  transform: translateX(6px);
}

.card-link-label {
  width: fit-content;
  margin-top: auto;
  border: 1px solid currentColor;
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 12px;
  text-transform: uppercase;
}

.detail-content {
  display: grid;
  gap: 16px;
  padding-top: 8px;
}

.detail-content ul,
.case-section ul,
.method-block ul {
  margin: 0;
  padding-left: 18px;
}

.detail-content li,
.case-section li,
.method-block li {
  margin: 8px 0;
  line-height: 1.45;
}

.card-meta {
  display: grid;
  gap: 6px;
  border-top: 1px solid var(--line-invert);
  padding-top: 12px;
  font-size: 12px;
  line-height: 1.35;
}

.mini-gallery {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: 6px;
}

.mini-gallery img {
  height: 150px;
  min-width: 220px;
  object-fit: cover;
  border: 1px solid var(--line-invert);
  scroll-snap-align: start;
}



.method-layout {
  display: grid;
  grid-template-columns: minmax(240px, 0.35fr) minmax(0, 1fr);
  gap: 56px;
}

.method-intro {
  position: sticky;
  top: 96px;
  align-self: start;
  display: grid;
  gap: 18px;
}

.method-block {
  display: grid;
  gap: 30px;
}

.method-panel {
  border-left: 6px solid var(--accent, var(--black));
  background: var(--wash, transparent);
  padding: 26px 26px 26px 24px;
  display: grid;
  gap: 14px;
}

.method-panel h3,
.move h3 {
  margin: 0;
  font-size: clamp(21px, 2.2vw, 30px);
  line-height: 1.15;
  font-weight: 400;
}

.method-panel.reveal {
  --accent: var(--reveal);
  --wash: var(--reveal-wash);
}

.method-panel.mediate {
  --accent: var(--mediate);
  --wash: var(--mediate-wash);
}

.method-panel.translate {
  --accent: var(--translate);
  --wash: var(--translate-wash);
}

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

.evidence-links a {
  border: 1px solid var(--line);
  border-radius: 0;
  padding: 8px 11px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}

.evidence-links a:hover {
  border-color: currentColor;
  color: var(--accent, currentColor);
}

/* Hairline separators rather than a full box grid. Same information, less cage. */
.moves-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  column-gap: 40px;
  row-gap: 0;
}

.move {
  padding: 24px 0;
  display: grid;
  gap: 10px;
  border-top: 1px solid var(--line);
}

.move .mono {
  color: rgba(0, 0, 0, 0.45);
  font-size: 12px;
}



.about-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 0.6fr);
  gap: 56px;
}

.contact-list {
  display: grid;
  gap: 1px;
  border: 1px solid var(--line-invert);
  background: var(--line-invert);
}

.contact-list a,
.contact-list span {
  display: block;
  background: var(--black);
  padding: 14px;
  font-family: var(--mono);
  font-size: 13px;
}

/* Portrait marquee: a slow band of photographs, doubled in the markup so the loop
   has no seam. Pauses on hover so a face can actually be looked at. */
.portrait-strip {
  margin-top: 60px;
  overflow: hidden;
  mask-image: linear-gradient(to right, transparent, black 5%, black 95%, transparent);
}

.portrait-track {
  display: flex;
  gap: 12px;
  width: max-content;
  animation: portrait-scroll 120s linear infinite;
}

.portrait-strip:hover .portrait-track {
  animation-play-state: paused;
}

.portrait-track img {
  height: 210px;
  width: auto;
  object-fit: cover;
  filter: grayscale(1) contrast(1.04);
  transition: filter 0.4s var(--ease), transform 0.4s var(--ease);
}

.portrait-track img:hover {
  filter: none;
  transform: translateY(-5px);
}

@keyframes portrait-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .portrait-track { animation: none; }
}

@media (max-width: 920px) {
  .portrait-track img { height: 140px; }
}

/* Shelf: papers as spines you pull out, not rows in a table. */
.shelf {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 10px;
  align-items: stretch;
}

.shelf-book {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 260px;
  padding: 18px 16px;
  border: 1px solid var(--line);
  border-top: 5px solid var(--accent, var(--line));
  background: var(--paper-deep);
  transition: transform 0.3s var(--ease), background 0.3s var(--ease);
}

.shelf-book.reveal { --accent: var(--reveal); }
.shelf-book.mediate { --accent: var(--mediate); }
.shelf-book.translate { --accent: var(--translate); }

.shelf-book:hover {
  transform: translateY(-8px);
  background: var(--white);
}

.shelf-num {
  font-size: 11px;
  opacity: 0.5;
}

.shelf-title {
  font-size: 17px;
  line-height: 1.24;
  margin-top: auto;
}

.shelf-kind,
.shelf-words {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  opacity: 0.55;
}

.shelf--compact .shelf-book {
  min-height: 128px;
  background: transparent;
  border-color: var(--line-invert);
}

.shelf--compact .shelf-book:hover {
  background: rgba(242, 237, 226, 0.07);
}

/* Reading page. */
.paper-hero .section-inner {
  max-width: 860px;
}

.paper-sub {
  font-size: clamp(19px, 2vw, 27px);
  line-height: 1.3;
  font-weight: 300;
  margin-top: 14px;
  opacity: 0.85;
}

.paper-meta {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  opacity: 0.55;
  margin-top: 18px;
}

.paper-note {
  max-width: 54ch;
  margin-top: 20px;
}

.paper-hero .button {
  margin-top: 28px;
}

.paper-extras {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
  max-width: var(--max);
  margin: 0 auto;
}

.paper-extra {
  margin: 0;
}

.paper-extra img,
.paper-extra video {
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
}

.paper-extra figcaption {
  margin-top: 10px;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  opacity: 0.6;
}

.paper-pdf-link {
  border-bottom: 1px solid currentColor;
  margin-left: 6px;
}

.paper-body-wrap .section-inner {
  max-width: 720px;
}

.paper-body p {
  margin-bottom: 1.15em;
}

.paper-body h3 {
  font-size: 19px;
  font-weight: 500;
  line-height: 1.3;
  margin: 2em 0 0.7em;
}

/* Writing: papers as a list of hairline-separated rows, not cards. */
.writing-list {
  display: grid;
  gap: 0;
  max-width: var(--max);
}

.writing-item {
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr) auto;
  gap: 26px;
  align-items: start;
  padding: 26px 0;
  border-top: 1px solid var(--line);
  transition: opacity 0.3s var(--ease);
}

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

.writing-item:hover {
  opacity: 0.62;
}

.writing-item img {
  width: 74px;
  height: auto;
  border: 1px solid var(--line);
}

.writing-item .mono {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  opacity: 0.55;
  margin-bottom: 8px;
}

.writing-item h3 {
  margin: 0 0 8px;
  font-size: clamp(18px, 1.8vw, 24px);
  line-height: 1.2;
  font-weight: 400;
  max-width: 46ch;
}

.writing-item p {
  max-width: 58ch;
  opacity: 0.72;
}

.writing-arrow {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  opacity: 0.5;
  padding-top: 4px;
}

@media (max-width: 720px) {
  .writing-item {
    grid-template-columns: 56px minmax(0, 1fr);
  }
  .writing-item img {
    width: 56px;
  }
  .writing-arrow {
    display: none;
  }
}

.footer {
  font-family: var(--mono);
  letter-spacing: 0;
}

.brand-mark {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 1px solid currentColor;
  font-family: var(--mono);
  font-size: 11px;
}

.section {
  padding: 88px 24px;
}

.section-inner {
  max-width: var(--max);
  margin: 0 auto;
}

.hero {
  min-height: 92vh;
  display: flex;
  align-items: center;
  padding-top: 150px;
  padding-bottom: 90px;
  position: relative;
  overflow: hidden;
}

/* Drifting cloud field in the three verb colours, with a halftone screen over it.
   Three large soft radial blooms move on slow, differently-timed loops so they never
   repeat the same arrangement. */
.hero:before {
  content: "";
  position: absolute;
  inset: -12%;
  pointer-events: none;
  background:
    radial-gradient(38% 44% at 20% 28%, rgba(205, 171, 254, 0.30), transparent 68%),
    radial-gradient(42% 38% at 78% 30%, rgba(254, 113, 65, 0.22), transparent 66%),
    radial-gradient(46% 46% at 52% 78%, rgba(209, 221, 211, 0.22), transparent 70%);
  filter: blur(70px) saturate(1.1);
  animation: cloud-drift 34s ease-in-out infinite alternate;
  mask-image: linear-gradient(to bottom, black 8%, transparent 82%);
}

.hero:after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(circle at center, rgba(var(--fg-rgb), 0.10) 1px, transparent 1.6px);
  background-size: 6px 6px;
  mask-image: linear-gradient(to bottom, black, transparent 72%);
}

@keyframes cloud-drift {
  0% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  33% {
    transform: translate3d(3%, -2.5%, 0) scale(1.08);
  }
  66% {
    transform: translate3d(-2.5%, 2%, 0) scale(1.04);
  }
  100% {
    transform: translate3d(1.5%, 3%, 0) scale(1.11);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero:before {
    animation: none;
  }
}

.hero-grid {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 68px;
  align-items: center;
  width: 100%;
}

.hero h1,
.case-hero h1 {
  margin: 0;
  max-width: 17ch;
  font-family: var(--serif);
  font-size: clamp(42px, 5.6vw, 88px);
  line-height: 1.02;
  font-weight: 300;
  letter-spacing: -0.015em;
}

.hero h1 em,
.case-hero h1 em,
.section-title em {
  font-style: italic;
  font-weight: 300;
}

.hero-copy {
  display: grid;
  gap: 24px;
  max-width: 560px;
}

.hero-copy p {
  opacity: 0.8;
}

.hero-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 18px;
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--glass);
  backdrop-filter: blur(14px);
}

.stat {
  padding: 0;
}

.stat strong {
  display: block;
  font-family: var(--mono);
  font-size: clamp(24px, 2.2vw, 34px);
  line-height: 1;
  font-weight: 500;
  background: var(--aurora);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.stat span {
  display: block;
  margin-top: 10px;
  font-family: var(--mono);
  font-size: 10px;
  line-height: 1.35;
  text-transform: uppercase;
  opacity: 0.62;
}

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

.tag {
  border: 1px solid currentColor;
  border-radius: 0;
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1;
  text-transform: uppercase;
}

/* Filled tags carry their own edge through colour. The extra outline was making
   every chip read as a form field. */
.tag {
  color: rgba(var(--fg-rgb), 0.82);
  border-color: var(--line);
  font-family: var(--mono);
}

.tag.reveal,
.tag.mediate,
.tag.translate,
.tag.synthesis {
  border-color: transparent;
  color: #0f1011;
}

.tag.reveal {
  background: var(--reveal);
}

.tag.mediate {
  background: var(--mediate);
}

.tag.translate {
  background: var(--translate);
}

.tag.synthesis {
  background: var(--aurora);
  color: #0f1011;
}

.scroll-cue {
  position: absolute;
  bottom: 28px;
  left: 24px;
  font-family: var(--mono);
  font-size: 12px;
  text-transform: uppercase;
}

/* Splash. Three phrases arrive a beat apart, the group lifts, the second line
   answers it, then the whole thing clears to the page. */
.splash {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: 40px 24px;
  background: var(--white);
  transition: opacity 900ms ease, visibility 900ms;
}

.splash.is-done {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.splash:before {
  content: "";
  position: absolute;
  inset: -12%;
  pointer-events: none;
  background:
    radial-gradient(38% 44% at 24% 32%, rgba(205, 171, 254, 0.6), transparent 68%),
    radial-gradient(42% 38% at 76% 28%, rgba(254, 113, 65, 0.42), transparent 66%),
    radial-gradient(46% 46% at 50% 74%, rgba(209, 221, 211, 0.7), transparent 70%);
  filter: blur(56px) saturate(1.1);
  animation: cloud-drift 34s ease-in-out infinite alternate;
}

.splash-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
}

.splash-inner {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 30px;
  max-width: 900px;
  text-align: left;
  transition: transform 1000ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.splash-inner.lifted {
  transform: translateY(-46px);
}

.splash-line {
  margin: 0;
  font-size: clamp(24px, 3vw, 42px);
  line-height: 1.28;
  font-weight: 300;
  letter-spacing: -0.01em;
  color: var(--black);
  text-shadow: 0 1px 22px rgba(var(--fg-rgb), 0.5);
}

.splash-second {
  font-size: clamp(17px, 1.7vw, 24px);
  font-weight: 300;
  font-style: italic;
  line-height: 1.4;
  color: rgba(0, 0, 0, 0.82);
  opacity: 0;
  transition: opacity 500ms ease;
}

.splash-second.is-shown {
  opacity: 1;
}

.splash-phrase {
  display: block;
}

.splash-second .splash-phrase {
  display: inline;
}

.splash-skip {
  position: absolute;
  z-index: 2;
  right: 26px;
  bottom: 24px;
  border: 1px solid var(--line);
  background: transparent;
  color: rgba(0, 0, 0, 0.6);
  padding: 9px 14px;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  cursor: pointer;
}

.splash-skip:hover {
  border-color: currentColor;
  color: var(--black);
}

body.splash-active {
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .splash:before {
    animation: none;
  }
  .splash-inner {
    transition: none;
  }
}

/* LineNav. The horizontal line menu, and the only navigation on the page.
   --effect (0..1) is eased per item by the rAF loop in script.js, so label colour,
   vertical shift and tick height all read the same continuously-animating value. */
.line-nav {
  --accent-color: var(--mediate);
  --text-color: rgba(0, 0, 0, 0.5);
  --marker-color: rgba(0, 0, 0, 0.2);
  --tick-height: 14px;
  --max-shift: 5px;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  padding: 20px 30px 16px;
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
  transition: opacity 420ms ease, transform 420ms ease, color 400ms ease;
}

/* Arrives once the reader starts moving, so the opening screen stays clean. */
.line-nav.is-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.line-nav.on-dark {
  --text-color: rgba(var(--fg-rgb), 0.62);
  --marker-color: rgba(var(--fg-rgb), 0.28);
}

.line-nav__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: color-mix(in srgb, var(--text-color) 100%, transparent);
  flex: 0 0 auto;
}

.line-nav.on-dark .line-nav__brand {
  color: rgba(var(--fg-rgb), 0.9);
}

.line-nav__brand .brand-mark {
  width: 30px;
  height: 30px;
  font-size: 11px;
}

.line-sidebar__list {
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  align-items: start;
}

.line-sidebar__item {
  position: relative;
  cursor: pointer;
  padding-top: 16px;
  padding-right: 24px;
}

.line-sidebar__item::before {
  content: "";
  position: absolute;
  inset: -8px -20px;
}

.line-sidebar__label {
  position: relative;
  display: inline-flex;
  align-items: baseline;
  font-family: var(--mono);
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.2;
  color: color-mix(in srgb, var(--accent-color) calc(var(--effect, 0) * 100%), var(--text-color));
  transform: translateY(calc(var(--effect, 0) * var(--max-shift)));
}

.line-sidebar__index {
  margin-right: 7px;
  font-size: 0.85em;
  opacity: calc(0.45 + var(--effect, 0) * 0.55);
}

/* Vertical tick above each label, growing as the cursor nears it. */
.line-sidebar__marker {
  position: absolute;
  top: 0;
  left: 0;
  width: 26px;
  height: 1px;
  background-color: color-mix(in srgb, var(--accent-color) calc(var(--effect, 0) * 100%), var(--marker-color));
  transform-origin: left center;
  transform: scaleX(calc(1 + var(--effect, 0) * 1.4));
}

/* Half-height tick sitting in the gap between two items. */


.line-nav.case-nav {
  justify-content: space-between;
  gap: 24px;
}

.line-nav__link {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-color);
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
  transition: color 220ms ease, border-color 220ms ease;
}

.line-nav__link:hover {
  color: var(--accent-color);
  border-bottom-color: var(--accent-color);
}

@media (max-width: 720px) {
  .line-nav {
    gap: 18px;
    padding: 14px 16px 12px;
  }

  .line-nav__name {
    display: none;
  }

  .line-sidebar__list {
    grid-template-columns: repeat(2, 1fr);
    row-gap: 12px;
  }
}

/* BlurText. Three-stage arrival per word: blur 10px at zero opacity and offset,
   through a half-resolved midpoint, to sharp. Staggered per word by --bt-delay. */
.hero-verse {
  max-width: 30ch;
  font-size: clamp(19px, 1.6vw, 24px);
  line-height: 1.45;
  font-style: italic;
  color: rgba(0, 0, 0, 0.72);
}

.blur-text .bt-word {
  display: inline-block;
  opacity: 0;
  will-change: transform, filter, opacity;
}

.blur-text.is-lit .bt-word {
  animation: blur-text-in 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  animation-delay: var(--bt-delay, 0ms);
}

.blur-text.from-top .bt-word {
  --bt-from-y: -50px;
  --bt-mid-y: 5px;
}

.blur-text.from-bottom .bt-word {
  --bt-from-y: 50px;
  --bt-mid-y: -5px;
}

@keyframes blur-text-in {
  0% {
    filter: blur(10px);
    opacity: 0;
    transform: translateY(var(--bt-from-y, -50px));
  }
  50% {
    filter: blur(5px);
    opacity: 0.5;
    transform: translateY(var(--bt-mid-y, 5px));
  }
  100% {
    filter: blur(0);
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .blur-text.is-lit .bt-word {
    animation: none;
  }
}

/* Blur highlight. The opening statement arrives out of focus and resolves word by
   word, so the sentence is read rather than skimmed. Key words carry an accent
   highlight that sweeps in behind them as they land. */
.blur-highlight .bh-word {
  display: inline-block;
  filter: blur(9px);
  opacity: 0.25;
  transform: translateY(0.14em);
  transition: filter 620ms cubic-bezier(0.22, 0.61, 0.36, 1), opacity 620ms ease, transform 620ms ease;
  transition-delay: var(--bh-delay, 0ms);
}

.blur-highlight.is-lit .bh-word {
  filter: blur(0);
  opacity: 1;
  transform: translateY(0);
}

.blur-highlight .bh-key {
  position: relative;
  padding: 0 0.08em;
}

.blur-highlight .bh-key::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.02em;
  height: 0.44em;
  z-index: -1;
  background: var(--bh-accent, var(--mediate));
  opacity: 0;
  transform: scaleX(0.15);
  transform-origin: left center;
  transition: transform 540ms cubic-bezier(0.22, 0.61, 0.36, 1), opacity 540ms ease;
  transition-delay: calc(var(--bh-delay, 0ms) + 240ms);
}

.blur-highlight.is-lit .bh-key::after {
  opacity: 0.55;
  transform: scaleX(1);
}

@media (prefers-reduced-motion: reduce) {
  .blur-highlight .bh-word,
  .blur-highlight .bh-key::after {
    transition: none;
    filter: none;
    opacity: 1;
    transform: none;
  }
}

.section-kicker {
  display: grid;
  grid-template-columns: 90px minmax(0, 1fr);
  border-top: 1px solid currentColor;
  padding-top: 22px;
  gap: 24px;
  align-items: end;
  margin-bottom: 44px;
  border-bottom: 1px solid currentColor;
  padding-bottom: 18px;
}

.section-number {
  font-size: 12px;
}

.section-title {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(30px, 3.6vw, 52px);
  line-height: 1.06;
  font-weight: 300;
  letter-spacing: -0.012em;
}

/* Featured Case is a full colour field, in Chula's own Reveal colour. It also keeps
   two black sections (Work, About) from running into each other. */


.featured-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(320px, 0.75fr);
  gap: 48px;
  align-items: center;
}

.image-frame {
  position: relative;
  border: 1px solid currentColor;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--dark);
}

.image-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Same processing language as the project cards, at hero scale. */
.image-frame.halftone::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image: radial-gradient(circle at center, rgba(var(--fg-rgb), 0.3) 1px, transparent 1.8px);
  background-size: 6px 6px;
  mix-blend-mode: overlay;
  opacity: 0.4;
  transition: opacity 700ms ease;
}

.image-frame.halftone:hover::after {
  opacity: 0;
}

.scan {
  filter: saturate(0.6) contrast(1.04);
  transition: filter 700ms ease, transform 700ms ease;
}

.image-frame:hover .scan {
  filter: none;
  transform: scale(1.015);
}

.featured .image-frame {
  min-height: 520px;
}

.featured-copy {
  display: grid;
  gap: 24px;
}

.featured-copy h2,
.case-section h2,
.about h2 {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(22px, 2.3vw, 32px);
  line-height: 1.18;
  font-weight: 300;
  letter-spacing: -0.006em;
}

.button {
  display: inline-flex;
  width: fit-content;
  align-items: center;
  gap: 10px;
  border: 1px solid currentColor;
  border-radius: var(--radius);
  padding: 12px 18px;
  text-transform: uppercase;
  font-size: 12px;
  font-weight: 500;
  transition: transform 0.3s var(--ease), background 0.3s var(--ease), color 0.3s var(--ease), box-shadow 0.3s var(--ease);
}

.button.dark {
  color: var(--black);
  background: var(--white);
}

.button.light {
  color: var(--white);
  background: var(--black);
}

.button:hover {
  transform: translateY(-2px);
}

.button .arrow,
.button span[aria-hidden] {
  transition: transform 0.3s var(--ease);
}

.button:hover span[aria-hidden] {
  transform: translateX(3px);
}



.work-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 24px;
}

.filter-button {
  border: 1px solid var(--line-invert);
  border-radius: 0;
  background: transparent;
  color: var(--white);
  padding: 9px 12px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.3s var(--ease), color 0.3s var(--ease), border-color 0.3s var(--ease);
}

.filter-button.is-active,
.filter-button:hover {
  background: var(--white);
  color: var(--black);
}

.project-card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  backdrop-filter: blur(14px);
  border: 1px solid var(--line-invert);
  border-top: 4px solid var(--accent, var(--line-invert));
  border-radius: var(--radius);
  background: var(--glass);
  color: var(--fg);
  overflow: hidden;
  transition: transform 0.3s var(--ease), border-color 0.3s var(--ease), opacity 0.3s var(--ease), box-shadow 0.3s var(--ease);
}

/* The spotlight reads as ink warming under the cursor, not as a neon glow. */
.project-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  background: radial-gradient(
    260px circle at var(--glow-x, 50%) var(--glow-y, 50%),
    rgba(254, 113, 65, calc(var(--glow, 0) * 0.16)) 0%,
    transparent 62%
  );
  transition: opacity 0.3s var(--ease);
}

.project-card:hover {
  background: var(--glass-raised);
}

@supports (grid-template-rows: masonry) {
  .project-card {
    margin-bottom: 0;
  }
}

@supports (display: grid-lanes) {
  .project-card {
    margin-bottom: 0;
  }
}

.project-card:focus-visible {
  outline: 2px solid var(--accent, var(--white));
  outline-offset: 4px;
}

/* The category chip sits on top of the tinted image, so it has to invert against
   the accent instead of sharing it. */
.project-card::after {
  content: attr(data-label);
  position: absolute;
  top: 14px;
  left: 14px;
  z-index: 3;
  border-radius: 0;
  border: 1px solid var(--accent, var(--white));
  background: var(--black);
  color: var(--accent, var(--white));
  padding: 7px 9px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1;
  text-transform: uppercase;
}

.project-card.is-muted {
  display: none;
}

.project-card[data-category*="Reveal"] {
  --accent: var(--reveal);
}

.project-card[data-category*="Mediate"] {
  --accent: var(--mediate);
}

.project-card[data-category*="Translate"] {
  --accent: var(--translate);
}

#liminal-grounds {
  --accent: var(--reveal);
}

/* Duotone halftone. The image loads processed, tinted in its own R/M/T colour and
   screened with a dot grid, then resolves to the real photograph on hover. This is
   the practice's own Reveal verb made literal, and it gives nine source images shot
   under wildly different conditions one shared surface. */
.project-media {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  background: rgba(0, 0, 0, 0.35);
  border-bottom: 1px solid var(--line-invert);
}

.project-media img,
.project-media video {
  display: block;
  width: 100%;
  height: auto;
  filter: saturate(0.72) contrast(1.02);
  transition: filter 620ms ease, transform 620ms ease;
}

/* Colour wash. soft-light keeps the photograph's own midtones instead of crushing
   them onto the accent, which is what made the first version look grim. */
.project-media::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: var(--accent, transparent);
  mix-blend-mode: soft-light;
  opacity: 0.62;
  transition: opacity 620ms ease;
}

/* Dot screen, light enough to read as print texture rather than a scrim. */
.project-media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image: radial-gradient(circle at center, rgba(var(--fg-rgb), 0.34) 0.85px, transparent 1.5px);
  background-size: 5px 5px;
  mix-blend-mode: overlay;
  opacity: 0.45;
  transition: opacity 620ms ease;
}

.project-media.contain {
  background: #f6f6f4;
}



/* Static covers carry a second frame that cross-fades in on hover, so a card that
   cannot autoplay still answers the cursor. */
.project-media img.swap {
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
  object-fit: cover;
  opacity: 0;
}

.project-media.contain img.swap {
  object-fit: contain;
}

.project-card:hover .project-media img.swap,
.project-card:focus-visible .project-media img.swap {
  opacity: 1;
}

.project-card:hover .project-media img,
.project-card:hover .project-media video,
.project-card:focus-visible .project-media img,
.project-card:focus-visible .project-media video {
  filter: none;
  transform: scale(1.03);
}

.project-card:hover .project-media::before,
.project-card:hover .project-media::after,
.project-card:focus-visible .project-media::before,
.project-card:focus-visible .project-media::after {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .project-media img,
  .project-media video,
  .project-media::before,
  .project-media::after {
    transition: none;
  }
}

.project-body {
  position: relative;
  z-index: 3;
  display: grid;
  gap: 12px;
  align-content: start;
  padding: 20px;
  color: var(--fg);
}

.project-body p {
  opacity: 0.76;
}

.project-card .card-link-label {
  opacity: 0.55;
  transition: opacity 0.35s var(--ease);
}

.project-card:hover .card-link-label,
.project-card:focus-visible .card-link-label {
  opacity: 1;
}

.project-title-row {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: 16px;
  border-bottom: 2px solid var(--accent);
  padding-bottom: 10px;
}

.project-body h3 {
  margin: 0;
  font-size: clamp(19px, 1.7vw, 25px);
  line-height: 1.1;
  font-weight: 400;
}

.project-arrow {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 16px;
  transition: transform 180ms ease;
}

.project-card:hover .project-arrow {
  transform: translateX(6px);
}

.card-link-label {
  width: fit-content;
  margin-top: auto;
  border: 1px solid currentColor;
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 12px;
  text-transform: uppercase;
}

.detail-content {
  display: grid;
  gap: 16px;
  padding-top: 8px;
}

.detail-content ul,
.case-section ul,
.method-block ul {
  margin: 0;
  padding-left: 18px;
}

.detail-content li,
.case-section li,
.method-block li {
  margin: 8px 0;
  line-height: 1.45;
}

.card-meta {
  display: grid;
  gap: 6px;
  border-top: 1px solid var(--line-invert);
  padding-top: 12px;
  font-size: 12px;
  line-height: 1.35;
}

.mini-gallery {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: 6px;
}

.mini-gallery img {
  height: 150px;
  min-width: 220px;
  object-fit: cover;
  border: 1px solid var(--line-invert);
  scroll-snap-align: start;
}



.method-layout {
  display: grid;
  grid-template-columns: minmax(240px, 0.35fr) minmax(0, 1fr);
  gap: 56px;
}

.method-intro {
  position: sticky;
  top: 96px;
  align-self: start;
  display: grid;
  gap: 18px;
}

.method-block {
  display: grid;
  gap: 30px;
}

.method-panel {
  border-left: 6px solid var(--accent, var(--black));
  background: var(--wash, transparent);
  padding: 26px 26px 26px 24px;
  display: grid;
  gap: 14px;
}

.method-panel h3,
.move h3 {
  margin: 0;
  font-size: clamp(21px, 2.2vw, 30px);
  line-height: 1.15;
  font-weight: 400;
}

.method-panel.reveal {
  --accent: var(--reveal);
  --wash: var(--reveal-wash);
}

.method-panel.mediate {
  --accent: var(--mediate);
  --wash: var(--mediate-wash);
}

.method-panel.translate {
  --accent: var(--translate);
  --wash: var(--translate-wash);
}

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

.evidence-links a {
  border: 1px solid var(--line);
  border-radius: 0;
  padding: 8px 11px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}

.evidence-links a:hover {
  border-color: currentColor;
  color: var(--accent, currentColor);
}

/* Hairline separators rather than a full box grid. Same information, less cage. */
.moves-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  column-gap: 40px;
  row-gap: 0;
}

.move {
  padding: 24px 0;
  display: grid;
  gap: 10px;
  border-top: 1px solid var(--line);
}

.move .mono {
  color: rgba(0, 0, 0, 0.45);
  font-size: 12px;
}



.about-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 0.6fr);
  gap: 56px;
}

.contact-list {
  display: grid;
  gap: 1px;
  border: 1px solid var(--line-invert);
  background: var(--line-invert);
}

.contact-list a,
.contact-list span {
  display: block;
  background: var(--black);
  padding: 14px;
  font-family: var(--mono);
  font-size: 13px;
}

/* Portrait strip. A slow marquee of photographs across the foot of the About
   section, doubled in the markup so the loop has no seam. */
.portrait-strip {
  margin-top: 64px;
  overflow: hidden;
  mask-image: linear-gradient(to right, transparent, black 6%, black 94%, transparent);
}

.portrait-track {
  display: flex;
  gap: 14px;
  width: max-content;
  animation: portrait-scroll 110s linear infinite;
}

.portrait-strip:hover .portrait-track {
  animation-play-state: paused;
}

.portrait-track img {
  height: 220px;
  width: auto;
  object-fit: cover;
  border-radius: 0;
  border: 1px solid var(--line-invert);
  filter: grayscale(1) contrast(1.05);
  transition: filter 500ms ease, transform 500ms ease;
}

.portrait-track img:hover {
  filter: none;
  transform: translateY(-4px);
}

@keyframes portrait-scroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .portrait-track {
    animation: none;
  }
}

@media (max-width: 920px) {
  .portrait-track img {
    height: 150px;
  }
}

.footer {
  margin-top: 72px;
  padding-top: 18px;
  border-top: 1px solid var(--line-invert);
  font-size: 12px;
  text-transform: uppercase;
}

/* The hero used to be bottom-aligned inside a 92vh box, which was fine while
   the copy column was tall. With the archive button gone and the intro split
   into a claim plus a support line, the short pages opened on half a screen of
   empty ground. The block is centred now, so the leftover height is shared
   above and below instead of all landing on top. */
.case-hero {
  min-height: 76vh;
  display: grid;
  align-items: center;
  padding-top: 118px;
}

.project-case.reveal {
  --accent: var(--reveal);
}

.project-case.mediate {
  --accent: var(--mediate);
}

.project-case.translate {
  --accent: var(--translate);
}

.project-case .eyebrow,
.project-case .callout {
  border-color: var(--accent);
}

.project-case .callout {
  color: var(--black);
}

.project-hero-image {
  background: var(--paper-deep);
  align-self: center;
}

/* Let the media set the frame's height instead of forcing a tall box around it. */
.project-hero-image video,
.project-hero-image img {
  width: 100%;
  height: auto;
  object-fit: contain;
}

.project-hero-image.cover video,
.project-hero-image.cover img {
  object-fit: contain;
}



.project-hero-image.cover {
  background: var(--black);
}

.case-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(320px, 0.65fr);
  gap: 54px;
  align-items: end;
}

.case-hero-copy {
  display: grid;
  gap: 22px;
}

.meta-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  font-size: 12px;
}

.meta-grid div {
  background: var(--white);
  padding: 14px;
  min-height: 74px;
}

.meta-grid strong {
  display: block;
  text-transform: uppercase;
  margin-bottom: 6px;
}

.case-section {
  padding: 76px 24px;
}

.chula-poster-opener {
  min-height: 100vh;
  padding: 88px clamp(18px, 4vw, 56px) 56px;
  display: grid;
  place-items: center;
  background:
    radial-gradient(circle at 50% 12%, rgba(245, 75, 151, 0.08), transparent 36%),
    var(--white);
}

.chula-poster-opener img {
  display: block;
  width: min(640px, 86vw);
  max-height: calc(100vh - 150px);
  object-fit: contain;
  box-shadow: 0 28px 80px rgba(26, 26, 26, 0.16);
}



.case-two {
  max-width: var(--max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(220px, 0.35fr) minmax(0, 1fr);
  gap: 56px;
}

.case-copy {
  display: grid;
  gap: 22px;
  max-width: 850px;
}

.archive-index {
  display: grid;
  gap: 1px;
  background: var(--line-invert);
  border: 1px solid var(--line-invert);
}

.archive-index article {
  background: var(--black);
  display: grid;
  grid-template-columns: 76px minmax(0, 1fr);
  gap: 22px;
  padding: 20px;
}

.case-gallery {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}

.case-gallery figure {
  margin: 0;
  min-width: min(720px, 88vw);
  scroll-snap-align: start;
}

.case-gallery img,
.case-gallery video {
  display: block;
  width: 100%;
  height: 460px;
  object-fit: contain;
  background: #f4f4f4;
  border: 1px solid currentColor;
}

.case-gallery video {
  background: #101010;
}

/* Spreads layout: for projects whose evidence is an existing portfolio document.
   One hero, one paragraph, then the spreads laid out full width, stacked, at their
   own proportions. No horizontal scroll, nothing cropped. */
.case-gallery.spreads {
  display: grid;
  grid-template-columns: 1fr;
  gap: 28px;
  overflow: visible;
  scroll-snap-type: none;
  max-width: var(--max);
  margin: 0 auto;
}

.case-gallery.spreads figure {
  min-width: 0;
}

.case-gallery.spreads img {
  width: 100%;
  height: auto;
  object-fit: contain;
  background: transparent;
  border-radius: var(--radius);
  border: 1px solid var(--line);
}

/* Wall: many small artefacts at once, so the quantity is the point. Captions are
   redundant when every tile is the same kind of thing. */
.case-gallery.wall {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
  overflow: visible;
  scroll-snap-type: none;
  max-width: var(--max);
  margin: 0 auto;
}

.case-gallery.wall figure {
  min-width: 0;
  position: relative;
  overflow: hidden;
}

.case-gallery.wall img,
.case-gallery.wall video {
  width: 100%;
  height: 190px;
  object-fit: cover;
  border-radius: 0;
  border: 1px solid var(--line);
  background: transparent;
  transition: transform 320ms ease, filter 320ms ease;
}

.case-gallery.wall img:hover,
.case-gallery.wall video:hover {
  transform: scale(1.06);
  z-index: 2;
  position: relative;
}

.case-gallery.wall figcaption {
  position: absolute;
  left: 1px;
  right: 1px;
  bottom: 1px;
  margin: 0;
  padding: 10px;
  background: rgba(255, 255, 255, 0.9);
  color: var(--black);
  font-size: 11px;
  line-height: 1.35;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 220ms ease, transform 220ms ease;
  pointer-events: none;
}

.case-gallery.wall figure:hover figcaption,
.case-gallery.wall figure:focus-within figcaption {
  opacity: 1;
  transform: translateY(0);
}

/* Lead media: a full-width opener for projects a film explains faster than prose. */
.lead-media {
  padding-top: 0;
}

.lead-media .case-video video {
  width: 100%;
  max-height: 78vh;
  object-fit: cover;
}

.case-gallery figcaption {
  margin-top: 10px;
  font-family: var(--mono);
  font-size: 12px;
}

/* Full-width video block, for the one walkthrough that carries a whole case. */
.case-video {
  margin: 0;
  max-width: var(--max);
}

.case-video video {
  display: block;
  width: 100%;
  background: #101010;
  border: 1px solid currentColor;
}

.case-video figcaption {
  margin-top: 12px;
  max-width: 720px;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.5;
}

.walkthrough-note {
  margin-top: 20px;
  font-family: var(--mono);
  font-size: 12px;
}

.walkthrough-note a {
  border-bottom: 1px solid currentColor;
}

.image-frame video {
  display: block;
  width: 100%;
  height: auto;
}

/* Table and step blocks for case pages that carry a comparison or a sequence. */
.case-table-wrap {
  max-width: var(--max);
  margin: 0 auto;
  overflow-x: auto;
}

.case-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  text-align: left;
}

.case-table th {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  font-weight: 520;
  letter-spacing: 0.02em;
  padding: 0 18px 12px 0;
  border-bottom: 1px solid currentColor;
  white-space: nowrap;
}

.case-table td {
  padding: 16px 18px 16px 0;
  border-bottom: 1px solid var(--line);
  line-height: 1.45;
  vertical-align: top;
}

.case-section.dark .case-table td {
  border-bottom-color: var(--line-invert);
}

.case-table tr td:nth-child(3) {
  font-family: var(--mono);
  font-size: 13px;
  text-transform: uppercase;
  color: var(--accent, currentColor);
  white-space: nowrap;
}

.case-steps {
  max-width: var(--max);
  margin: 0 auto;
  display: grid;
  gap: 0;
}

.case-steps article {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  gap: 24px;
  padding: 26px 0;
  border-top: 1px solid var(--line);
}

.case-section.dark .case-steps article {
  border-top-color: var(--line-invert);
}

.case-steps .mono {
  font-size: 12px;
  opacity: 0.55;
}

.case-steps h3 {
  margin: 0 0 8px;
  font-size: clamp(18px, 1.7vw, 23px);
  line-height: 1.2;
  font-weight: 400;
}

@media (max-width: 720px) {
  .case-steps article {
    grid-template-columns: 1fr;
    gap: 8px;
  }
}

.callout {
  border-left: 4px solid var(--reveal);
  padding-left: 22px;
  font-size: clamp(20px, 2.1vw, 29px);
  line-height: 1.3;
  font-weight: 300;
}

@media (max-width: 1180px) {
  .work-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 920px) {
  .hero-grid,
  .featured-layout,
  .method-layout,
  .about-grid,
  .case-hero-grid,
  .case-two {
    grid-template-columns: 1fr;
  }

  .method-intro {
    position: static;
  }

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

  .moves-grid,
  .hero-stats,
  .meta-grid {
    grid-template-columns: 1fr;
  }

  .section {
    padding: 86px 18px;
  }

  .featured .image-frame {
    min-height: 320px;
  }
}

@media (max-width: 560px) {
  .hero h1,
  .case-hero h1 {
    font-size: 54px;
  }

  .section-kicker {
    grid-template-columns: 1fr;
  }


  .mini-gallery img {
    min-width: 190px;
  }
}

/* Media sitting inside a section, next to the claim it supports. */
.section-media {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 18px;
  margin-top: 12px;
}

.section-media figure {
  margin: 0;
  position: relative;
}

.section-media figure.swap-pair img + img {
  position: absolute;
  inset: 0;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 420ms ease;
}

.section-media figure.swap-pair:hover img + img {
  opacity: 1;
}

.section-media video,
.section-media img {
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid currentColor;
  background: #0d0d0d;
}

.section-media figcaption {
  margin-top: 8px;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  opacity: 0.62;
}

/* A run of dark sections should read as one block, not as stripes. */
.case-section.dark + .case-section.dark {
  padding-top: 0;
}

/* AccordionGallery. Collapsed panels sit desaturated and slightly rotated; the open
   one flattens, regains colour, and reveals its caption. Ported from the React
   component using CSS transitions instead of GSAP. */
.accordion-gallery {
  --ag-accent: var(--accent, var(--fg));
  --ag-overlay: #0a0a0a;
  --ag-text: var(--fg);
  display: flex;
  gap: 10px;
  width: 100%;
  max-width: var(--max);
  margin: 0 auto;
  height: 460px;
  perspective: 1400px;
}

.ag-panel {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  border-radius: 0;
  cursor: pointer;
  background: #0a0a0a;
  transform-style: preserve-3d;
  transition: flex-grow 600ms cubic-bezier(0.22, 0.61, 0.36, 1), transform 600ms cubic-bezier(0.22, 0.61, 0.36, 1);
  outline: none;
}

.ag-panel:focus-visible {
}

.ag-panel__frame {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
}

.ag-panel__media {
  position: absolute;
  inset: 0;
}

.ag-panel__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1);
  transition: filter 600ms ease;
  user-select: none;
}

.ag-panel--active .ag-panel__media img {
  filter: grayscale(0);
}

.ag-panel__overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, transparent 45%, rgba(10, 10, 10, 0.78) 100%),
    rgba(10, 10, 10, 0.35);
  transition: background 600ms ease;
}

.ag-panel--active .ag-panel__overlay {
  background: linear-gradient(180deg, transparent 52%, rgba(10, 10, 10, 0.74) 100%);
}

.ag-panel__label {
  position: absolute;
  left: 20px;
  right: 20px;
  bottom: 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  pointer-events: none;
  z-index: 2;
}

.ag-panel__bar {
  flex: 0 0 auto;
  width: 3px;
  height: 24px;
  border-radius: 0;
  background: var(--ag-accent);
  opacity: 0;
  transform: translateX(-14px);
  transition: opacity 500ms ease, transform 500ms ease;
}

.ag-panel__text {
  color: var(--ag-text);
  font-family: var(--mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  opacity: 0;
  transform: translateX(-14px);
  transition: opacity 500ms ease 60ms, transform 500ms ease 60ms;
}

.ag-panel--active .ag-panel__bar,
.ag-panel--active .ag-panel__text {
  opacity: 1;
  transform: translateX(0);
}

/* Case-page nav: a Work dropdown plus two flat links, in the Origin manner. */
.line-nav.case-nav {
  align-items: center;
  gap: 34px;
  padding: 18px 30px;
}

.line-nav__brand {
  padding: 0;
  line-height: 1;
}

.line-nav__links {
  display: flex;
  align-items: center;
  align-self: center;
  gap: 10px;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}

.line-nav__links::-webkit-scrollbar {
  display: none;
}

/* The brand carries a 34px mark while the links are 12px text, so both need to be
   centred against the same row rather than against their own line boxes. */
.line-nav.case-nav > * {
  align-self: center;
}

.nav-drop {
  display: flex;
  align-items: center;
}

.nav-drop {
  position: relative;
}

.nav-drop__trigger,
.nav-drop__flat {
  display: inline-flex;
  align-items: center;
  line-height: 1;
  gap: 7px;
  border: 0;
  background: transparent;
  color: var(--text-color);
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  cursor: pointer;
  padding: 0;
  transition: color 0.3s var(--ease);
}

.case-switch__item {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 30px;
  padding: 7px 9px;
  border: 1px solid transparent;
  color: var(--text-color);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0;
  white-space: nowrap;
  transition: border-color 0.25s var(--ease), color 0.25s var(--ease), background 0.25s var(--ease);
}

.case-switch__item:hover,
.case-switch__item.is-here {
  border-color: currentColor;
  color: var(--accent-color);
  background: rgba(255, 255, 255, 0.55);
}

.case-switch__num {
  opacity: 0.55;
}

.case-switch__verb {
  display: none;
}

.case-switch__meta {
  padding-left: 10px;
}

.nav-drop__trigger:hover,
.nav-drop__flat:hover,
.nav-drop.is-open .nav-drop__trigger {
  color: var(--accent-color);
}

.nav-drop__chev {
  width: 6px;
  height: 6px;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translateY(-1px);
  transition: transform 0.3s var(--ease);
}

.nav-drop.is-open .nav-drop__chev {
  transform: rotate(-135deg) translateY(-2px);
}

.nav-drop__panel {
  position: absolute;
  top: calc(100% + 12px);
  left: -18px;
  min-width: 330px;
  padding: 8px;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease), visibility 0s 0.3s;
  z-index: 5;
}

.nav-drop.is-open .nav-drop__panel {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease), visibility 0s 0s;
}

.nav-drop__item {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: 0;
  color: var(--black);
  transition: background 0.3s var(--ease);
}

.nav-drop__item:hover {
  background: rgba(0, 0, 0, 0.045);
}

.nav-drop__item.is-here {
  background: rgba(0, 0, 0, 0.06);
}

.nav-drop__num {
  font-family: var(--mono);
  font-size: 10px;
  opacity: 0.45;
}

.nav-drop__title {
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav-drop__verb {
  font-family: var(--mono);
  font-size: 9.5px;
  text-transform: uppercase;
  padding: 4px 7px;
  border-radius: 0;
  color: var(--black);
}

.nav-drop__verb.reveal {
  background: var(--reveal);
}

.nav-drop__verb.mediate {
  background: var(--mediate);
}

.nav-drop__verb.translate {
  background: var(--translate);
}

@media (max-width: 720px) {
  .line-nav.case-nav {
    gap: 16px;
    padding-inline: 16px;
  }

  .line-nav__links {
    gap: 8px;
  }

  .case-switch__item {
    min-height: 28px;
    padding: 6px 8px;
    font-size: 9px;
  }

  .nav-drop__panel {
    min-width: 260px;
    left: -12px;
  }
}

/* Media sitting inside a section, next to the claim it supports. */
.section-media {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 18px;
  margin-top: 12px;
}

.section-media figure {
  margin: 0;
  position: relative;
}

.section-media figure.swap-pair img + img {
  position: absolute;
  inset: 0;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 420ms ease;
}

.section-media figure.swap-pair:hover img + img {
  opacity: 1;
}

.section-media video,
.section-media img {
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid currentColor;
  background: #0d0d0d;
}

.section-media figcaption {
  margin-top: 8px;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  opacity: 0.62;
}

/* A run of dark sections should read as one block, not as stripes. */
.case-section.dark + .case-section.dark {
  padding-top: 0;
}

/* AccordionGallery. Collapsed panels sit desaturated and slightly rotated; the open
   one flattens, regains colour, and reveals its caption. Ported from the React
   component using CSS transitions instead of GSAP. */
.accordion-gallery {
  --ag-accent: var(--accent, var(--fg));
  --ag-overlay: #0a0a0a;
  --ag-text: var(--fg);
  display: flex;
  gap: 10px;
  width: 100%;
  max-width: var(--max);
  margin: 0 auto;
  height: 460px;
  perspective: 1400px;
}

.ag-panel {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  border-radius: 0;
  cursor: pointer;
  background: #0a0a0a;
  transform-style: preserve-3d;
  transition: flex-grow 600ms cubic-bezier(0.22, 0.61, 0.36, 1), transform 600ms cubic-bezier(0.22, 0.61, 0.36, 1);
  outline: none;
}

.ag-panel:focus-visible {
}

.ag-panel__frame {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
}

.ag-panel__media {
  position: absolute;
  inset: 0;
}

.ag-panel__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1);
  transition: filter 600ms ease;
  user-select: none;
}

.ag-panel--active .ag-panel__media img {
  filter: grayscale(0);
}

.ag-panel__overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, transparent 45%, rgba(10, 10, 10, 0.78) 100%),
    rgba(10, 10, 10, 0.35);
  transition: background 600ms ease;
}

.ag-panel--active .ag-panel__overlay {
  background: linear-gradient(180deg, transparent 52%, rgba(10, 10, 10, 0.74) 100%);
}

.ag-panel__label {
  position: absolute;
  left: 20px;
  right: 20px;
  bottom: 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  pointer-events: none;
  z-index: 2;
}

.ag-panel__bar {
  flex: 0 0 auto;
  width: 3px;
  height: 24px;
  border-radius: 0;
  background: var(--ag-accent);
  opacity: 0;
  transform: translateX(-14px);
  transition: opacity 500ms ease, transform 500ms ease;
}

.ag-panel__text {
  color: var(--ag-text);
  font-family: var(--mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  opacity: 0;
  transform: translateX(-14px);
  transition: opacity 500ms ease 60ms, transform 500ms ease 60ms;
}

.ag-panel--active .ag-panel__bar,
.ag-panel--active .ag-panel__text {
  opacity: 1;
  transform: translateX(0);
}

/* Case switcher: ten projects along the bar, current one held lit. */
.line-nav.case-nav {
  align-items: flex-start;
  padding: 20px 30px 20px;
}



@media (max-width: 720px) {
  .accordion-gallery {
    flex-direction: column;
    height: auto;
    perspective: none;
  }

  .ag-panel {
    min-height: 200px;
    transform: none !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ag-panel,
  .ag-panel__media img {
    transition: none;
  }
}

/* Writing shelf: each paper gets a generated mark, glass panel per mood.md. */
.shelf {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
  align-items: stretch;
}

.shelf-book {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
  padding: 0 0 18px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-top: 3px solid var(--accent, var(--line));
  border-radius: var(--radius);
  background: var(--glass);
  backdrop-filter: blur(14px);
  color: var(--fg);
}

.shelf-book:hover {
  transform: translateY(-6px);
  background: var(--glass-raised);
}

.shelf-cover {
  display: block;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.28);
}

.shelf-cover img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  transition: transform 0.5s var(--ease);
}

.shelf-book:hover .shelf-cover img { transform: scale(1.03); }

.shelf-num { padding: 0 16px; font-size: 11px; opacity: 0.5; }
.shelf-title { padding: 0 16px; margin-top: auto; font-size: 16px; line-height: 1.25; }
.shelf-kind,
.shelf-words { padding: 0 16px; font-size: 10px; text-transform: uppercase; letter-spacing: 0.03em; opacity: 0.5; }

/* Photo marquee, now the last thing on the page. */
.portrait-track img {
  height: 190px;
  width: auto;
  object-fit: cover;
  border-radius: var(--radius);
  filter: grayscale(1) contrast(1.05) brightness(0.92);
}

.portrait-track img:hover { filter: none; transform: translateY(-5px); }

/* Method panels: glass on the dark ground, not the pale washes left over
   from the paper direction. Colour survives only as the wayfinding edge. */
.method-panel {
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent, var(--line));
  border-radius: var(--radius);
  background: var(--glass);
  backdrop-filter: blur(14px);
  padding: 28px;
  color: var(--fg);
}

.method-panel.reveal { --wash: none; }
.method-panel.mediate { --wash: none; }
.method-panel.translate { --wash: none; }

/* Serif carries the headline, one italic word does the emphasis. */
.hero h1 em,
.section-title em,
.section h2 em,
.case-copy h2 em {
  font-style: italic;
  font-weight: 300;
}

.eyebrow--sans {
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: 0;
  text-transform: none;
  opacity: 0.7;
}

/* Letterboxed covers sit on the dark ground, not on a white slab
   left over from the paper direction. */
.project-media.contain {
  background: rgba(var(--fg-rgb), 0.04);
}

/* The method statement is a headline, so the serif carries it. */
.method-statement,
.method-intro h2,
.method h2 {
  font-family: var(--serif);
  font-weight: 300;
}

/* Each paper's generated mark, alongside its title. */
.paper-hero-grid {
  display: grid;
  grid-template-columns: 1fr 240px;
  gap: 54px;
  align-items: start;
  max-width: 1000px;
}

.paper-mark {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--glass);
  backdrop-filter: blur(14px);
  overflow: hidden;
}

.paper-mark img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
}

@media (max-width: 920px) {
  .paper-hero-grid { grid-template-columns: 1fr; gap: 30px; }
  .paper-mark { max-width: 200px; }
}

/* The hover frame follows the same rule as the cover: shown whole, never cropped. */
.project-media img.swap,
.project-media video.swap {
  object-fit: contain;
  background: rgba(var(--fg-rgb), 0.03);
}

/* Palette-swap fallout: three rules still resolved to white-on-white or
   near-black-on-near-black after the ground was inverted. */
.archive-index {
  background: var(--line);
  border-color: var(--line);
}

.archive-index article {
  background: var(--glass);
  backdrop-filter: blur(14px);
  color: var(--fg);
}

.contact-list a,
.contact-list span {
  background: var(--glass);
  color: var(--fg);
  border: 1px solid var(--line);
}

.contact-list a:hover {
  background: var(--glass-raised);
}

.nav-drop__flat {
  color: rgba(var(--fg-rgb), 0.72);
}

.nav-drop__flat:hover,
.nav-drop__flat:focus-visible {
  color: var(--fg);
}

/* Step numbers in How I Work: mono, and light enough to read on the dark ground. */
.move p.mono,
.move .mono {
  color: rgba(var(--fg-rgb), 0.5);
}

/* Capabilities, in place of the counted stats. Grouped so the list scans,
   mono only on the group label because a label is a system word, not prose. */
.hero {
  min-height: auto;
  align-items: center;
  padding-top: 156px;
  padding-bottom: 92px;
}

.hero-grid {
  align-items: start;
  row-gap: 54px;
}

.hero-capabilities {
  grid-column: 1 / -1;
  display: grid;
  gap: 12px;
  padding: 24px 26px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--glass);
  backdrop-filter: blur(14px);
}

.cap-row {
  display: grid;
  grid-template-columns: 88px minmax(0, 1fr);
  gap: 18px;
  align-items: baseline;
}

.cap-label {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.45;
  padding-top: 3px;
}

.cap-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}

.cap {
  display: inline-flex;
  align-items: center;
  padding: 5px 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 12.5px;
  line-height: 1.2;
  color: rgba(var(--fg-rgb), 0.86);
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}

.cap:hover {
  border-color: rgba(var(--fg-rgb), 0.4);
  color: var(--fg);
}

@media (max-width: 720px) {
  .cap-row { grid-template-columns: 1fr; gap: 8px; }
}

/* Compact bento. Covers are allowed exactly two shapes, 4:3 and 3:4, so heights
   line up and the grid packs tight. The crop is shallow because the shape follows
   the source image's own orientation. */
.work-grid {
  gap: 10px;
  grid-auto-rows: 6px;
}

.project-media {
  border-bottom: 1px solid var(--line-invert);
}

.project-card.wide .project-media { aspect-ratio: 4 / 3; }
.project-card.portrait .project-media { aspect-ratio: 3 / 4; }

.project-card.wide .project-media img,
.project-card.wide .project-media video,
.project-card.portrait .project-media img,
.project-card.portrait .project-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: transparent;
}

.project-body {
  gap: 8px;
  padding: 14px 16px 16px;
}

.project-body h3 {
  font-size: clamp(16px, 1.4vw, 20px);
  line-height: 1.18;
}

.project-body p {
  font-size: 13.5px;
  line-height: 1.42;
}

.project-card .tag {
  font-size: 9.5px;
  padding: 3px 8px;
}

/* The shelf: the dissertation stands full height on the left, the other six
   fill a 2 x 3 block beside it. Marks are 3:4 and 4:3 to match. */
.shelf {
  display: grid;
  grid-template-columns: 1.15fr 1fr 1fr;
  grid-template-rows: repeat(3, auto);
  gap: 12px;
}

.shelf-book.shelf-lead {
  grid-column: 1;
  grid-row: 1 / span 3;
}

.shelf-cover img {
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

.shelf-book.shelf-lead .shelf-cover img {
  aspect-ratio: 3 / 4;
}

.shelf-book.shelf-lead .shelf-title {
  font-size: clamp(19px, 1.7vw, 24px);
}

.shelf-book { padding: 0 0 15px; gap: 6px; }
.shelf-num { padding: 0 14px; }
.shelf-title { padding: 0 14px; font-size: 15px; }
.shelf-kind,
.shelf-words { padding: 0 14px; }

@media (max-width: 980px) {
  .shelf { grid-template-columns: 1fr 1fr; }
  .shelf-book.shelf-lead { grid-column: 1 / -1; grid-row: auto; }
  .shelf-book.shelf-lead .shelf-cover img { aspect-ratio: 16 / 9; }
}

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

/* The reading page shows the mark at its own shape. */
.paper-mark img { aspect-ratio: auto; }

/* The lead book carries a lede, so its extra height is copy rather than a void. */
.shelf-lead .shelf-title { margin-top: 16px; }

.shelf-lede {
  padding: 0 14px;
  margin-top: 8px;
  font-size: 13.5px;
  line-height: 1.45;
  opacity: 0.72;
}

.shelf-lead .shelf-kind { margin-top: auto; padding-top: 16px; }

/* The nav's own tokens still held the ink-on-paper values, which is why the
   brand mark and the Work trigger read as invisible. Every section sits on the
   dark ground now, so the light-on-dark values are the base, not the exception. */
.line-nav {
  --text-color: rgba(var(--fg-rgb), 0.68);
  --marker-color: rgba(var(--fg-rgb), 0.28);
}

.line-nav.on-dark {
  --text-color: rgba(var(--fg-rgb), 0.78);
  --marker-color: rgba(var(--fg-rgb), 0.34);
}

.line-nav__brand,
.line-nav.on-dark .line-nav__brand {
  color: var(--text-color);
}

.brand-mark {
  border-color: var(--marker-color);
}

.line-nav__brand:hover,
.line-nav__brand:hover .brand-mark,
.nav-drop__trigger:hover {
  color: var(--fg);
}

.line-nav__brand:hover .brand-mark {
  border-color: rgba(var(--fg-rgb), 0.55);
}

/* Filter buttons: inactive ones were near-black on near-black. */
.filter-button {
  color: rgba(var(--fg-rgb), 0.62);
  border-color: var(--line);
  background: transparent;
}

.filter-button:hover {
  color: var(--fg);
  border-color: rgba(var(--fg-rgb), 0.4);
  background: var(--glass);
}

.filter-button.is-active {
  color: #0f1011;
  background: var(--fg);
  border-color: var(--fg);
}

/* Dense packing so a wide card that cannot fit the remaining columns is
   backfilled by a later narrow one instead of leaving a hole. */
.work-grid {
  grid-auto-flow: row dense;
}

/* The nav is fixed over the content, so it needs its own ground. Glass, per
   mood.md, rather than a solid bar: it separates by translucency, not colour. */
.line-nav {
  background: var(--nav-glass);
  backdrop-filter: blur(18px) saturate(1.1);
  border-bottom: 1px solid rgba(var(--fg-rgb), 0.08);
}

/* ---- Writing shelf: one tall book, then three across and two down ------- */
.shelf {
  display: grid;
  grid-template-columns: 1.35fr 1fr 1fr 1fr;
  grid-template-rows: auto auto;
  gap: 12px;
  align-items: stretch;
}

.shelf-book.shelf-lead {
  grid-column: 1;
  grid-row: 1 / span 2;
}

.shelf-book { height: 100%; }

@media (max-width: 1100px) {
  .shelf { grid-template-columns: repeat(3, 1fr); }
  .shelf-book.shelf-lead { grid-column: 1 / -1; grid-row: auto; }
  .shelf-book.shelf-lead .shelf-cover img { aspect-ratio: 16 / 9; }
}

@media (max-width: 720px) {
  .shelf { grid-template-columns: repeat(2, 1fr); }
}

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

/* The coral duotone and halftone screen sat on top of every cover. That wash is
   what made the grid busy, and on a letterboxed cover it painted the bare bars
   solid coral. Covers now show as themselves. */
.project-media::before,
.project-media::after {
  content: none;
}

.project-media img,
.project-media video {
  filter: saturate(0.88) contrast(1.02);
}

/* ---- Work grid, placed from the sketch --------------------------------
   Four columns, five rows, every cell filled. Each card's cover takes
   whatever height the text leaves, so the cell shape decides the crop:
   the tall cell holds the portrait piece, the wide cells hold the video work. */
.work-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 300px;
  grid-auto-flow: row;
  gap: 14px;
  align-items: stretch;
}

.work-grid .project-card { height: 100%; }

.c1 { grid-column: 1; }
.c2 { grid-column: 2; }
.c3 { grid-column: 3; }
.c4 { grid-column: 4; }
.c1-2 { grid-column: 1 / span 2; }
.c3-4 { grid-column: 3 / span 2; }

.r1 { grid-row: 1; }
.r2 { grid-row: 2; }
.r3 { grid-row: 3; }
.r5 { grid-row: 5; }
.r1-2 { grid-row: 1 / span 2; }
.r3-4 { grid-row: 3 / span 2; }
.r4-5 { grid-row: 4 / span 2; }

/* The cover fills the space the text does not use. */
.work-grid .project-media {
  flex: 1 1 auto;
  min-height: 0;
  aspect-ratio: auto;
}

.work-grid .project-media img,
.work-grid .project-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.work-grid .project-body {
  flex: 0 0 auto;
  gap: 7px;
  padding: 13px 15px 15px;
}

.work-grid .sm .project-body p {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 12.5px;
}

.work-grid .sm .project-body h3 { font-size: 16px; }
.work-grid .lg .project-body h3 { font-size: clamp(19px, 1.6vw, 24px); }
.work-grid .tall .project-body h3 { font-size: 17px; }

/* Below the wide breakpoint the placement is dropped and the cards simply flow. */
@media (max-width: 1100px) {
  .work-grid {
    grid-template-columns: repeat(2, 1fr);
    grid-auto-rows: 320px;
  }
  .work-grid .project-card {
    grid-column: auto !important;
    grid-row: auto !important;
  }
  .work-grid .lg { grid-column: 1 / -1 !important; }
}

@media (max-width: 680px) {
  .work-grid {
    grid-template-columns: 1fr;
    grid-auto-rows: 300px;
  }
  .work-grid .lg { grid-column: 1 !important; }
}

/* ---- Opening: the personal introduction and the manifesto in one section ---- */
.hero {
  min-height: auto;
  align-items: center;
  padding-top: 150px;
  padding-bottom: 96px;
}

.hero-grid {
  grid-template-columns: 1fr 1fr;
  gap: 64px;
  align-items: center;
}

.hero-intro {
  display: grid;
  gap: 20px;
  max-width: 560px;
}

.hero-intro h1 {
  margin: 0;
}

.hero-intro p {
  margin: 0;
  opacity: 0.82;
}

.hero-lede {
  font-size: clamp(16px, 1.35vw, 19px);
  line-height: 1.5;
  opacity: 1 !important;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px;
  margin-top: 6px;
}

.hero-mail {
  font-family: var(--mono);
  font-size: 12px;
  color: rgba(var(--fg-rgb), 0.62);
  border-bottom: 1px solid transparent;
}

.hero-mail:hover {
  color: var(--fg);
  border-bottom-color: var(--coral);
}

/* Five fixed frames filling a 3 x 3 block: the composition holds still while
   the photographs move through it. */
.hero-photos {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 130px);
  gap: 10px;
}

.ph {
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--glass);
  border: 1px solid var(--line);
}

.ph1 { grid-column: 1; grid-row: 1 / span 2; }
.ph2 { grid-column: 2 / span 2; grid-row: 1; }
.ph3 { grid-column: 2; grid-row: 2; }
.ph4 { grid-column: 3; grid-row: 2 / span 2; }
.ph5 { grid-column: 1 / span 2; grid-row: 3; }

.ph img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(1.05) brightness(0.94);
  transition: opacity 0.42s var(--ease), transform 0.42s var(--ease);
}

.ph img:first-child { z-index: 2; opacity: 1; }
.ph img:last-child { z-index: 1; opacity: 1; }

/* The jump: the front frame lifts away and the one behind it is already there. */
.ph.is-swapping img:first-child {
  opacity: 0;
  transform: scale(1.07);
}

.ph:hover img { filter: none; }

/* The manifesto sits under the introduction, in the same section. */
.manifesto-block {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: 46px 64px;
  align-items: start;
  margin-top: 92px;
  padding-top: 52px;
  border-top: 1px solid var(--line);
}

.manifesto-block h2 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(30px, 3.6vw, 54px);
  line-height: 1.06;
  letter-spacing: -0.015em;
}

.manifesto-copy {
  display: grid;
  gap: 18px;
}

.manifesto-block .tag-row {
  grid-column: 1 / -1;
}

/* Footer, now that About has folded into the opening. */
.site-footer {
  padding: 56px 60px 68px;
  border-top: 1px solid var(--line);
}

.footer-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.footer-note {
  margin: 0;
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  opacity: 0.45;
}

@media (max-width: 920px) {
  .hero-grid { grid-template-columns: 1fr; gap: 40px; }
  .hero-photos { grid-template-rows: repeat(3, 110px); }
  .manifesto-block { grid-template-columns: 1fr; gap: 28px; margin-top: 64px; }
  .site-footer { padding: 44px 18px 56px; }
}

/* The opening now holds three stacked blocks, so the section stacks them
   instead of laying them out in a flex row. */
.hero {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.hero > .section-inner,
.hero > .hero-grid,
.hero > .manifesto-block {
  width: 100%;
}

/* The scroll cue is a flex child of the opening now, so it lines up with the
   content column rather than the page edge. */
.hero .scroll-cue {
  align-self: flex-start;
  width: 100%;
  max-width: 1320px;
  margin: 44px auto 0;
  padding: 0;
}

/* ---- Photo wall, rebuilt ------------------------------------------------
   Three columns, each one portrait plus one landscape frame. Both orders add
   up to the same height (4/3 + 3/4 of a column width), so the three columns
   end level and the wall has no ragged edge. Frames keep 3:4 and 4:3, and a
   frame only receives a photograph of its own shape, so nothing is cropped. */
.hero-photos {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: none;
  gap: 10px;
  align-items: start;
}

.ph-col {
  display: grid;
  gap: 10px;
  align-content: start;
}

.ph {
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--glass);
  border: 1px solid var(--line);
  grid-column: auto;
  grid-row: auto;
}

.ph--portrait { aspect-ratio: 3 / 4; }
.ph--land { aspect-ratio: 4 / 3; }

/* ---- Contact, now under the skill blocks rather than in the footer ---- */
.hero-contact {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
  margin-top: 10px;
  padding-top: 28px;
  border-top: 1px solid var(--line);
}

.hero-contact .contact-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.hero-contact .contact-list a {
  padding: 8px 14px;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 12px;
}

.footer-mail {
  font-family: var(--mono);
  font-size: 11px;
  color: rgba(var(--fg-rgb), 0.5);
}

.footer-mail:hover { color: var(--fg); }

@media (max-width: 920px) {
  .hero-photos { gap: 8px; }
  .ph-col { gap: 8px; }
}

/* The contact list used to be a bordered stack of rows. As a row of pills it
   should not carry a container box of its own. */
.hero-contact .contact-list {
  background: transparent;
  border: 0;
  padding: 0;
}

/* The cue is absolutely positioned, so auto margins cannot centre it. Pin it to
   the same left edge as the content column. */
.hero .scroll-cue {
  position: absolute;
  left: 50%;
  bottom: 30px;
  transform: translateX(-50%);
  width: calc(100% - 120px);
  max-width: 1320px;
  margin: 0;
  align-self: auto;
}

@media (max-width: 920px) {
  .hero .scroll-cue { width: calc(100% - 36px); }
}

@property --crease {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

/* ---- FoldText -----------------------------------------------------------
   Each word is a panel hinged on its top edge. It starts folded away from the
   reader with a crease shading across it, then swings flat. The overshoot at
   the end is the back-out easing from the original component. */
.fold-text {
  display: block;
  line-height: 1.0;
  letter-spacing: -0.02em;
}

.fold-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.fold-visual { display: inline; }
.fold-space { display: inline; }

.fold-seg {
  display: inline-block;
  line-height: inherit;
  perspective: 850px;
  transform-style: preserve-3d;
  vertical-align: baseline;
}

.fold-piece {
  position: relative;
  display: inline-block;
  line-height: inherit;
  transform-origin: 50% 0%;
  transform: rotateX(-92deg);
  opacity: 0;
  backface-visibility: hidden;
  will-change: transform, opacity;
  --crease: 0.75;
  transition:
    transform 0.8s cubic-bezier(0.34, 1.56, 0.64, 1),
    opacity 0.5s ease-out,
    --crease 0.8s ease-out;
}

.fold-piece::after {
  content: "";
  position: absolute;
  inset: -0.08em -0.02em;
  pointer-events: none;
  border-radius: 0.08em;
  opacity: var(--crease, 0);
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.62) 0%, rgba(0, 0, 0, 0.24) 42%, rgba(var(--fg-rgb), 0.2) 100%);
  transition: opacity 0.8s ease-out;
  transition-delay: inherit;
}

.fold-text.is-unfolded .fold-piece {
  transform: rotateX(0deg);
  opacity: 1;
}

.fold-text.is-unfolded .fold-piece::after { opacity: 0; }

/* The manifesto line carries the fold, so it sits above the rest in scale. */
.manifesto-block h2.fold-text {
  font-size: clamp(34px, 4.6vw, 68px);
  line-height: 1.02;
}

@media (prefers-reduced-motion: reduce) {
  .fold-piece {
    transform: none !important;
    opacity: 1 !important;
    transition: none !important;
  }
  .fold-piece::after { opacity: 0 !important; }
}

/* ---- Scroll reveal ------------------------------------------------------
   The hidden state only exists once the script has marked the document, so a
   page with no JS still renders every block in place. */
.has-reveal [data-rise] {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  transition:
    opacity 0.62s cubic-bezier(0.22, 0.61, 0.36, 1),
    transform 0.72s cubic-bezier(0.22, 0.61, 0.36, 1);
  transition-delay: var(--rise-delay, 0ms);
  will-change: opacity, transform;
}

.has-reveal [data-rise][data-risen] {
  opacity: 1;
  transform: none;
}

/* A folded headline handles its own entrance, so it should not also rise. */
.has-reveal [data-fold][data-rise] { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .has-reveal [data-rise] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* One italic word inside the folded headline, per the type rule in mood.md. */
.fold-em {
  font-style: italic;
}

/* ---- Opening, laid out from the sketch --------------------------------
   Photographs fill the left. The right column carries the whole statement:
   greeting, credentials, a rule, the folded line, the argument, the skills.
   Contact runs full width underneath both. */
.hero-grid {
  grid-template-columns: 0.95fr 1fr;
  gap: 56px;
  align-items: start;
}

.hero-intro {
  display: grid;
  gap: 18px;
  max-width: 640px;
  align-content: start;
}

.hero-rule {
  width: 100%;
  height: 0;
  margin: 12px 0 6px;
  border: 0;
  border-top: 1px solid var(--line);
}

/* In a half-width column the folded line takes a smaller size than it did
   across the page, but still stands well above the body copy. */
.hero-intro h2.fold-text {
  margin: 0;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(30px, 3.1vw, 46px);
  line-height: 1.04;
  letter-spacing: -0.015em;
}

.hero-intro .tag-row {
  margin-top: 8px;
}

/* Twelve frames, three columns of four. Two portraits and two landscapes per
   column in any order come to the same height, so the columns end level. */
.hero-photos {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  align-items: start;
}

.ph-col { gap: 8px; }

.hero-outro {
  display: grid;
  gap: 14px;
  margin-top: 64px;
  padding-top: 30px;
  border-top: 1px solid var(--line);
}

.outro-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
  margin: 0;
}

.outro-label {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 300;
  font-size: clamp(18px, 1.7vw, 24px);
  opacity: 0.9;
}

.hero-outro .contact-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  background: transparent;
  border: 0;
  padding: 0;
}

.hero-outro .contact-list a {
  padding: 8px 14px;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 12px;
}

@media (max-width: 980px) {
  .hero-grid { grid-template-columns: 1fr; gap: 40px; }
  .hero-photos { grid-template-columns: repeat(3, 1fr); }
  .hero-outro { margin-top: 46px; }
}

/* ---- Writing shelf: the mark is the resting state, the page is the reveal --- */
.shelf-cover {
  position: relative;
  display: block;
  overflow: hidden;
}

.shelf-cover img {
  transition: opacity 0.42s var(--ease), transform 0.5s var(--ease);
}

.shelf-page {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  opacity: 0;
  filter: saturate(0.9) contrast(1.02);
}

.shelf-book:hover .shelf-mark {
  opacity: 0;
  transform: scale(1.04);
}

.shelf-book:hover .shelf-page {
  opacity: 1;
}

@media (hover: none) {
  .shelf-page { display: none; }
}

/* ---- Section headings without the rules above and below ---------------- */
.section-kicker {
  border-top: 0;
  border-bottom: 0;
  padding-top: 0;
  padding-bottom: 0;
  margin-bottom: 30px;
}

/* ---- Opening: spacing carries the break where the rule used to be ------ */
.hero-intro { gap: 16px; }

.hero-intro h2.fold-text {
  margin-top: 26px;
  margin-bottom: 4px;
}

.hero-intro .tag-row { margin-top: 12px; }

.hero-outro {
  display: grid;
  gap: 12px;
  margin-top: 26px;
  padding-top: 24px;
  border-top: 1px solid var(--line);
}

.hero-outro .outro-label { font-size: clamp(16px, 1.4vw, 20px); }

/* ---- Academic Writing sits below Method and reads as the lighter of the
   two: smaller heading, tighter cards, less air around it. ---------------- */
.section.writing { padding-top: 56px; }

.writing .section-title {
  font-size: clamp(26px, 3vw, 42px);
}

.writing-intro {
  max-width: 60ch;
  margin: 0 0 28px;
  font-size: 15px;
  opacity: 0.78;
}

.writing .shelf {
  grid-template-columns: 1.15fr 1fr 1fr 1fr;
  gap: 10px;
}

.writing .shelf-title { font-size: 14px; }
.writing .shelf-lead .shelf-title { font-size: clamp(16px, 1.3vw, 19px); }
.writing .shelf-lede { font-size: 12.5px; line-height: 1.4; }
.writing .shelf-kind,
.writing .shelf-words { font-size: 9.5px; }
.writing .shelf-book { padding-bottom: 12px; gap: 4px; }
.writing .shelf-num,
.writing .shelf-title,
.writing .shelf-lede,
.writing .shelf-kind,
.writing .shelf-words { padding-left: 12px; padding-right: 12px; }

/* Photographs sit at partial desaturation rather than flat black and white.
   Full greyscale went cold, full colour would pull the wall apart; muting the
   colour keeps twelve different pictures reading as one block. Hover restores
   the photograph as shot. */
.ph img {
  filter: grayscale(0.5) saturate(0.9) contrast(1.02) brightness(1.03);
}

.ph:hover img {
  filter: grayscale(0) saturate(1) contrast(1) brightness(1);
}

.hero-outro .contact-list span {
  padding: 8px 14px;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 12px;
  background: var(--glass);
  border: 1px solid var(--line);
  color: rgba(var(--fg-rgb), 0.86);
}

/* ---- Hub Mark -----------------------------------------------------------
   Two-colour version on the dark ground, per the mark guidelines: 30px is
   above the 24px digital minimum, node sizes and stroke weights are the
   published geometry, and nothing is recoloured, rotated or shadowed. */
.brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  background: none;
  flex: none;
}

.hub-mark {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.hub-mark .arm {
  stroke: #cdabfe;
  stroke-width: 16;
  stroke-linecap: round;
}

.hub-mark .spine {
  stroke: #fe7141;
  stroke-width: 16;
  stroke-linecap: round;
}

.hub-mark .node-lilac { fill: #cdabfe; }
.hub-mark .node-coral { fill: #fe7141; }

/* Clear space is one node radius, taken from the mark's own geometry. */
.line-nav__brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 4px;
}

.line-nav__brand:hover .brand-mark { border-color: transparent; }

/* Rollup shows the journey at rest and the pitch on hover, so the second
   frame keeps the same fit as the first. */
#roll-up .project-media img.swap {
  object-fit: cover;
  background: transparent;
}

/* ---- The thermal cover reads better whole than cropped -------------------
   Its frames are dense data screens, so filling the cell cuts the numbers in
   half. Show it entire and let the card ground carry the rest. */
#thermal-commons .project-media {
  background: rgba(var(--fg-rgb), 0.03);
}

#thermal-commons .project-media video,
#thermal-commons .project-media img {
  object-fit: contain;
}

/* ---- The other introduction ---------------------------------------------
   A frosted phrase in the opening. Hover clears some of the frost, clicking
   lifts a sheet with the rest. */
.aside-line {
  margin: 0;
  opacity: 0.82;
}

.aside-trigger {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  vertical-align: baseline;
}

.aside-veil {
  position: relative;
  color: rgba(var(--fg-rgb), 0.9);
  filter: blur(4.4px);
  transition: filter 0.42s var(--ease), opacity 0.42s var(--ease);
  border-bottom: 1px dashed rgba(var(--fg-rgb), 0.28);
  padding-bottom: 1px;
  user-select: none;
}

.aside-trigger:hover .aside-veil,
.aside-trigger:focus-visible .aside-veil {
  filter: blur(1.4px);
}

.aside-cue {
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--coral);
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity 0.32s var(--ease), transform 0.32s var(--ease);
}

.aside-trigger:hover .aside-cue,
.aside-trigger:focus-visible .aside-cue {
  opacity: 1;
  transform: none;
}

body.aside-open {
  overflow: hidden;
}

.aside {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: 40px 24px;
}

.aside[hidden] { display: none; }

.aside__scrim {
  position: absolute;
  inset: 0;
  background: rgba(9, 10, 11, 0.72);
  backdrop-filter: blur(10px);
  opacity: 0;
  transition: opacity 0.36s var(--ease);
}

.aside.is-open .aside__scrim { opacity: 1; }

/* The sheet lifts and tilts up like a folder being opened. */
.aside__sheet {
  position: relative;
  width: min(760px, 100%);
  max-height: min(84vh, 900px);
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: rgba(28, 30, 32, 0.86);
  backdrop-filter: blur(22px) saturate(1.1);
  box-shadow: 0 40px 90px rgba(0, 0, 0, 0.5);
  transform-origin: 50% 100%;
  transform: perspective(1200px) rotateX(-8deg) translateY(26px) scale(0.985);
  opacity: 0;
  transition: transform 0.42s cubic-bezier(0.22, 0.75, 0.3, 1), opacity 0.32s var(--ease);
}

.aside.is-open .aside__sheet {
  transform: none;
  opacity: 1;
}

.aside__bar {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 26px;
  border-bottom: 1px solid var(--line);
  background: rgba(28, 30, 32, 0.9);
  backdrop-filter: blur(18px);
}

.aside__back {
  padding: 7px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: rgba(var(--fg-rgb), 0.86);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: background 0.3s var(--ease), color 0.3s var(--ease), border-color 0.3s var(--ease);
}

.aside__back:hover {
  background: var(--glass);
  border-color: rgba(var(--fg-rgb), 0.4);
  color: var(--fg);
}

.aside__label {
  margin: 0;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.45;
}

.aside__body {
  padding: 34px 34px 44px;
}

.aside__body h2 {
  margin: 0 0 26px;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(28px, 3.2vw, 44px);
  line-height: 1.05;
  letter-spacing: -0.015em;
}

.aside__list {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: aside;
  display: grid;
  gap: 24px;
}

.aside__list li {
  counter-increment: aside;
  position: relative;
  padding-left: 42px;
  font-size: 16px;
  line-height: 1.62;
  opacity: 0.9;
}

.aside__list li::before {
  content: counter(aside, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 4px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--coral);
  opacity: 0.75;
}

@media (max-width: 620px) {
  .aside { padding: 18px 14px; }
  .aside__body { padding: 24px 20px 32px; }
  .aside__list li { padding-left: 32px; font-size: 15px; }
}

@media (prefers-reduced-motion: reduce) {
  .aside__sheet { transform: none !important; transition: opacity 0.2s linear !important; }
  .aside-veil { transition: none !important; }
}

/* ---- Hub Field ----------------------------------------------------------
   The landing background is the logo repeated as a network: lilac arms rising
   into a coral hub, one output dropping below, signals moving along the edges
   between hubs. It sits behind the content and never competes with it. */
.hubfield {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  opacity: 0.75;
}

.hubfield__canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.hero > .section-inner,
.hero > .hero-grid {
  position: relative;
  z-index: 1;
}

/* ---- Water: the photographs breathe rather than sit still --------------- */
@keyframes ph-drift {
  0%   { transform: translate3d(0, 0, 0) scale(1.012); }
  50%  { transform: translate3d(0, -6px, 0) scale(1.024); }
  100% { transform: translate3d(0, 0, 0) scale(1.012); }
}

.ph img:first-child {
  animation: ph-drift var(--drift, 15s) ease-in-out infinite;
  animation-delay: var(--drift-delay, 0s);
}

.ph-col:nth-child(1) .ph:nth-child(1) img:first-child { --drift: 17s; --drift-delay: -1s; }
.ph-col:nth-child(1) .ph:nth-child(2) img:first-child { --drift: 21s; --drift-delay: -6s; }
.ph-col:nth-child(1) .ph:nth-child(3) img:first-child { --drift: 15s; --drift-delay: -3s; }
.ph-col:nth-child(1) .ph:nth-child(4) img:first-child { --drift: 19s; --drift-delay: -9s; }
.ph-col:nth-child(2) .ph:nth-child(1) img:first-child { --drift: 20s; --drift-delay: -4s; }
.ph-col:nth-child(2) .ph:nth-child(2) img:first-child { --drift: 16s; --drift-delay: -11s; }
.ph-col:nth-child(2) .ph:nth-child(3) img:first-child { --drift: 23s; --drift-delay: -2s; }
.ph-col:nth-child(2) .ph:nth-child(4) img:first-child { --drift: 18s; --drift-delay: -7s; }
.ph-col:nth-child(3) .ph:nth-child(1) img:first-child { --drift: 22s; --drift-delay: -5s; }
.ph-col:nth-child(3) .ph:nth-child(2) img:first-child { --drift: 17s; --drift-delay: -13s; }
.ph-col:nth-child(3) .ph:nth-child(3) img:first-child { --drift: 19s; --drift-delay: -8s; }
.ph-col:nth-child(3) .ph:nth-child(4) img:first-child { --drift: 24s; --drift-delay: -0.5s; }

.ph:hover img:first-child { animation-play-state: paused; }

/* ---- ScrollReveal: words clear as the paragraph rises ------------------- */
.sr-word {
  display: inline-block;
  opacity: 0.42;
  filter: blur(4px);
  will-change: opacity, filter;
}

/* ---- ScrollFloat: characters rise into place --------------------------- */
.sf-active { overflow: hidden; }

.sf-visual { display: inline-block; }

.sf-char {
  display: inline-block;
  opacity: 0;
  transform: translateY(120%) scale(0.7, 2.3);
  transform-origin: 50% 0%;
  will-change: opacity, transform;
}

.sf-em { font-style: italic; }

/* ---- SpotlightCard replaces the traced neon edge ----------------------- */
.project-card {
  --spot: 0;
  --mx: 50%;
  --my: 50%;
}

.project-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  border-radius: inherit;
  opacity: var(--spot, 0);
  transition: opacity 0.42s var(--ease);
  background: radial-gradient(
    340px circle at var(--mx) var(--my),
    rgba(205, 171, 254, 0.16),
    rgba(254, 113, 65, 0.08) 42%,
    transparent 68%
  );
}

.project-card::before { content: none; }

/* ---- Thermal cover in landscape ---------------------------------------- */
#thermal-commons .project-media {
  aspect-ratio: 16 / 9;
  flex: none;
}

@media (prefers-reduced-motion: reduce) {
  .ph img:first-child { animation: none !important; }
  .sr-word { opacity: 1 !important; filter: none !important; }
  .sf-char { opacity: 1 !important; transform: none !important; }
}

/* ---- Capability notes ---------------------------------------------------
   Each tag carries one line about the work behind it. It shows on hover and on
   keyboard focus, and lives in the DOM so a screen reader reaches it too. */
.tag.has-note {
  position: relative;
  cursor: help;
  outline-offset: 3px;
}

.tag-note {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translate(-50%, 6px);
  z-index: 30;
  width: max-content;
  max-width: 300px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: rgba(24, 26, 28, 0.94);
  backdrop-filter: blur(18px) saturate(1.1);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
  color: rgba(var(--fg-rgb), 0.9);
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0;
  text-transform: none;
  white-space: normal;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.26s var(--ease), transform 0.26s var(--ease), visibility 0.26s;
}

.tag-note::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  margin-left: -6px;
  border: 6px solid transparent;
  border-top-color: rgba(24, 26, 28, 0.94);
}

.tag.has-note:hover .tag-note,
.tag.has-note:focus-visible .tag-note,
.tag.has-note:focus .tag-note {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}

/* Notes on the outer tags anchor to their own edge so they stay on screen. */
.tag.has-note.note-left .tag-note {
  left: 0;
  transform: translate(0, 6px);
}

.tag.has-note.note-left:hover .tag-note,
.tag.has-note.note-left:focus-visible .tag-note,
.tag.has-note.note-left:focus .tag-note {
  transform: translate(0, 0);
}

.tag.has-note.note-left .tag-note::after { left: 34px; }

.tag.has-note.note-right .tag-note {
  left: auto;
  right: 0;
  transform: translate(0, 6px);
}

.tag.has-note.note-right:hover .tag-note,
.tag.has-note.note-right:focus-visible .tag-note,
.tag.has-note.note-right:focus .tag-note {
  transform: translate(0, 0);
}

.tag.has-note.note-right .tag-note::after { left: auto; right: 28px; }

/* The opening starts on the greeting now that the eyebrow is gone. */
.hero-intro h1 { margin-top: 0; }

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

/* Notes now run longer, so they need more room and a touch more line height. */
.tag-note {
  max-width: 360px;
  font-size: 12.5px;
  line-height: 1.55;
}

/* ---- What discipline each paper belongs to ----------------------------- */
.shelf-field {
  padding: 0 12px;
  margin-top: 2px;
  font-size: 11px;
  line-height: 1.35;
  opacity: 0.5;
}

.writing .shelf-field { font-size: 10.5px; }

.shelf-lead .shelf-field { font-size: 12px; }

.paper-field {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
  margin: 6px 0 0;
  font-size: 13.5px;
  opacity: 0.8;
}

.paper-field__label {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.5;
}

/* ---- A companion shown as its actual pages, side by side --------------- */
.paper-extra--spread { width: 100%; }

.paper-spread {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.paper-spread img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: rgba(var(--fg-rgb), 0.03);
}

@media (max-width: 720px) {
  .paper-spread { grid-template-columns: 1fr; }
  .tag-note { max-width: min(300px, 78vw); }
}

/* ---- Work cards, quieter -------------------------------------------------
   The card was carrying four separate signals at once: a coloured top edge, a
   2px coloured rule under the title, a boxed VIEW CASE, and an arrow. The whole
   card is already a link, so most of that is repetition. What is left is a
   plate in a catalogue: image, title, one line, and a mark that answers the
   cursor. */

/* The rule under the title goes. */
.project-title-row {
  border-bottom: 0;
  padding-bottom: 0;
  align-items: baseline;
}

/* The boxed button goes with it. The card is the link. */
.work-grid .card-link-label {
  border: 0;
  padding: 0;
  margin-top: 10px;
  font-size: 10px;
  letter-spacing: 0.1em;
  color: rgba(var(--fg-rgb), 0.34);
  transition: color 0.32s var(--ease), transform 0.32s var(--ease);
  transform: translateX(0);
}

.work-grid .project-card:hover .card-link-label,
.work-grid .project-card:focus-visible .card-link-label {
  color: rgba(var(--fg-rgb), 0.72);
}

/* The title takes the display face, so the hierarchy comes from type rather
   than from a coloured bar. */
.work-grid .project-body h3 {
  font-family: var(--serif);
  font-weight: 300;
  letter-spacing: -0.01em;
  line-height: 1.08;
}

.work-grid .sm .project-body h3 { font-size: clamp(18px, 1.5vw, 22px); }
.work-grid .lg .project-body h3 { font-size: clamp(22px, 2vw, 30px); }
.work-grid .tall .project-body h3 { font-size: clamp(19px, 1.6vw, 24px); }

.work-grid .project-body p {
  color: rgba(var(--fg-rgb), 0.62);
  font-size: 13px;
  line-height: 1.5;
}

/* Three lines rather than two: a description cut after four words reads as
   broken, not as restraint. */
.work-grid .sm .project-body p {
  -webkit-line-clamp: 3;
}

/* The accent stays as a hairline on the top edge, the one place it appears. */
.work-grid .project-card[data-category*="Reveal"],
.work-grid .project-card[data-category*="Mediate"],
.work-grid .project-card[data-category*="Translate"] {
  border-top-width: 1.5px;
}

.work-grid .project-card {
  /* Sides and bottom only: the top edge belongs to the category accent, and a
     border-color shorthand here would flatten it to grey. */
  border-right-color: rgba(var(--fg-rgb), 0.1);
  border-bottom-color: rgba(var(--fg-rgb), 0.1);
  border-left-color: rgba(var(--fg-rgb), 0.1);
  background: rgba(var(--fg-rgb), 0.045);
  transition: background 0.42s var(--ease), border-color 0.42s var(--ease), transform 0.42s var(--ease);
}

.work-grid .project-card:hover {
  background: rgba(var(--fg-rgb), 0.075);
  border-right-color: rgba(var(--fg-rgb), 0.18);
  border-bottom-color: rgba(var(--fg-rgb), 0.18);
  border-left-color: rgba(var(--fg-rgb), 0.18);
}

.work-grid .project-body {
  gap: 6px;
  padding: 16px 18px 18px;
}

/* The arrow is the only thing that moves, and only on hover. */
.work-grid .project-arrow {
  font-size: 14px;
  color: rgba(var(--fg-rgb), 0.34);
  transition: transform 0.36s var(--ease), color 0.36s var(--ease);
}

.work-grid .project-card:hover .project-arrow,
.work-grid .project-card:focus-visible .project-arrow {
  transform: translateX(5px);
  color: var(--fg);
}

/* The category chip sits on the image, so it needs less weight than a tag in
   running text. */
.work-grid .project-card .tag {
  font-size: 9px;
  letter-spacing: 0.09em;
  padding: 4px 9px;
  opacity: 0.92;
}

/* ------------------------------------------------------------------
   Disclosure: one control, used everywhere depth is optional.
   Native <details>, so expanded state and keyboard focus come free.
   The affordance is the point: a reader has to SEE that there is more
   before they can choose to want it.
   ------------------------------------------------------------------ */

:root { interpolate-size: allow-keywords; }

.disclose {
  border-top: 1px solid var(--line);
  margin: 28px 0 0;
}

.disclose-summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding: 18px 0;
  cursor: pointer;
  list-style: none;
  color: rgba(var(--fg-rgb), 0.62);
  transition: color 200ms var(--ease);
}

.disclose-summary::-webkit-details-marker { display: none; }
.disclose-summary:hover,
.disclose-summary:focus-visible { color: var(--fg); }

.disclose-title {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* The plus is the affordance. It rotates into a minus on open, so the
   control reads the same whether you arrived at it open or closed. */
.disclose-mark {
  position: relative;
  flex: 0 0 auto;
  width: 13px;
  height: 13px;
  align-self: center;
}

.disclose-mark::before,
.disclose-mark::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 1px;
  background: currentColor;
  transition: transform 260ms var(--ease);
}

.disclose-mark::after { transform: rotate(90deg); }
.disclose[open] .disclose-mark::after { transform: rotate(0deg); }

.disclose-body > :first-child { margin-top: 0; }
.disclose-body > :last-child { margin-bottom: 28px; }

/* Height animation where the browser supports interpolating to auto;
   everywhere else this degrades to an instant, still-working toggle. */
.disclose::details-content {
  block-size: 0;
  overflow: hidden;
  transition: block-size 280ms var(--ease), content-visibility 280ms allow-discrete;
}

.disclose[open]::details-content { block-size: auto; }

@media (prefers-reduced-motion: reduce) {
  .disclose::details-content { transition: none; }
  .disclose-mark::before,
  .disclose-mark::after { transition: none; }
}

/* ------------------------------------------------------------------
   Capability tags as real controls.
   They were <span tabindex=0> revealing a note on hover: invisible to
   anyone who did not happen to mouse over them, and unreachable on
   touch. Now a button with a cue, so the affordance is legible first
   and the depth is a choice second.
   ------------------------------------------------------------------ */

button.tag {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  background: none;
  font: inherit;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1;
  text-transform: uppercase;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

/* The cue. A small plus that becomes a minus, so the tag advertises that
   it has something behind it before anyone has to guess. */
.tag-cue {
  position: relative;
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  opacity: 0.55;
  transition: opacity 200ms var(--ease);
}

.tag-cue::before,
.tag-cue::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 1px;
  background: currentColor;
  transition: transform 260ms var(--ease);
}

.tag-cue::after { transform: rotate(90deg); }
button.tag[aria-expanded="true"] .tag-cue { opacity: 1; }
button.tag[aria-expanded="true"] .tag-cue::after { transform: rotate(0deg); }
button.tag:hover .tag-cue { opacity: 1; }

button.tag.has-note[aria-expanded="true"] .tag-note {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate(-50%, 0);
}

button.tag.has-note.note-left[aria-expanded="true"] .tag-note,
button.tag.has-note.note-right[aria-expanded="true"] .tag-note {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate(0, 0);
}

/* Tool names are scanned, not read. Give them chip shapes so the eye can
   skip the prose above them and still collect the stack. */
.tool-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}

.tool-chips > span {
  padding: 3px 7px;
  border: 1px solid var(--line);
  border-radius: 3px;
  font-family: var(--mono);
  font-size: 10.5px;
  line-height: 1.35;
  letter-spacing: 0.04em;
  text-transform: none;
  color: rgba(var(--fg-rgb), 0.72);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .tag-cue::before,
  .tag-cue::after { transition: none; }
}

/* ------------------------------------------------------------------
   Six Moves as a tab set. Six explanations stacked in a grid was a
   sixth wall in a section that already has several. One at a time,
   chosen by the reader.
   ------------------------------------------------------------------ */

.moves-tabs { gap: 6px; }

.move-tab {
  background: none;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1;
  text-transform: none;
  color: rgba(var(--fg-rgb), 0.5);
  border: 1px solid var(--line);
  padding: 8px 10px;
  -webkit-appearance: none;
  appearance: none;
  transition: color 200ms var(--ease), border-color 200ms var(--ease), background 200ms var(--ease);
}

.move-tab:hover { color: rgba(var(--fg-rgb), 0.9); }

.move-tab[aria-selected="true"] {
  color: var(--ground);
  background: var(--mediate);
  border-color: var(--mediate);
}



.moves-stack .move {
  display: grid;
  grid-template-columns: 44px 1fr;
  gap: 18px;
  align-items: start;
}

.moves-stack .move[hidden] { display: none; }
.moves-stack .move .mono { margin: 0; opacity: 0.5; }
.moves-stack .move h3 { margin: 0 0 8px; }
.moves-stack .move p:last-child { margin: 0; }

@media (max-width: 720px) {
  .moves-stack { min-height: 190px; }
  .moves-stack .move { grid-template-columns: 1fr; gap: 6px; }
}

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

/* A claim sentence carries its sub-block on its own, so it has to sit
   apart from the paragraph weight around it. */
.claim {
  font-family: var(--serif);
  font-size: clamp(1.05rem, 1.6vw, 1.35rem);
  line-height: 1.42;
  color: var(--fg);
  margin: 0 0 4px;
  text-wrap: balance;
}

/* ------------------------------------------------------------------
   Filtering, made visible. A card fades and settles before it leaves
   the flow, so the reader sees the grid respond instead of finding a
   different grid where the old one was.
   ------------------------------------------------------------------ */

.project-card {
  transition: opacity 190ms var(--ease), transform 190ms var(--ease);
}

.project-card.is-leaving,
.project-card.is-entering {
  opacity: 0;
  transform: scale(0.985);
}

.project-card.is-leaving { pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .project-card { transition: none; }
  .project-card.is-leaving,
  .project-card.is-entering { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------------
   A media row at one shared height, at every width.

   Each figure grows in proportion to its own aspect ratio, which is
   what makes the heights come out equal: a 1.55 landscape frame beside
   a 0.67 portrait one takes 2.3x the width, and the row fills the
   column exactly whatever the column happens to be. Script sets the
   ratios from the loaded images; until then equal widths is a sane
   place to stand.
   ------------------------------------------------------------------ */

.section-media.media-row {
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: 18px;
}

.section-media.media-row figure {
  flex: 1 1 0;
  min-width: 0;
}

.section-media.media-row img,
.section-media.media-row video {
  display: block;
  width: 100%;
  height: auto;
}

@media (max-width: 860px) {
  .section-media.media-row { gap: 10px; }
}

/* A narrower frame, for a picture that supports the point rather than
   carrying it. */
.section-media.media-narrow {
  grid-template-columns: none;
  max-width: 58%;
}

@media (max-width: 860px) {
  .section-media.media-narrow { max-width: 100%; }
}

/* ------------------------------------------------------------------
   Lightbox. Case images are detailed enough that a reader will want to
   look properly: a system map at 220px is a texture, not a diagram.
   ------------------------------------------------------------------ */

[data-zoom] { cursor: zoom-in; }

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(16px, 4vw, 56px);
  background: rgba(8, 9, 10, 0.94);
  backdrop-filter: blur(6px);
  opacity: 0;
  visibility: hidden;
  transition: opacity 220ms var(--ease), visibility 220ms;
}

.lightbox.is-open {
  opacity: 1;
  visibility: visible;
}

.lightbox img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--radius);
  transform: scale(0.985);
  transition: transform 220ms var(--ease);
}

.lightbox.is-open img { transform: none; }

.lightbox-close {
  position: absolute;
  top: clamp(12px, 2.4vw, 28px);
  right: clamp(12px, 2.4vw, 28px);
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  background: none;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--fg);
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: background 200ms var(--ease), border-color 200ms var(--ease);
}

.lightbox-close:hover,
.lightbox-close:focus-visible {
  background: rgba(var(--fg-rgb), 0.12);
  border-color: rgba(var(--fg-rgb), 0.4);
}

.lightbox-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(10px, 2vw, 22px);
  padding: 0 clamp(16px, 4vw, 56px);
  text-align: center;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), 0.6);
}

body.lightbox-open { overflow: hidden; }

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

/* ==================================================================
   Hero: the claim on the left, the photograph on the right.

   The picture is never cropped. It is a 2:3 portrait, so it is given a
   height to work within and takes whatever width that implies; the space
   left over beside it is the point, not a gap to fill.
   ================================================================== */

.hero-split {
  position: relative;
  min-height: 100svh;
  display: grid;
  align-items: center;
  overflow: hidden;
}

.hero-split__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(36px, 6vw, 88px);
  align-items: center;
  position: relative;
  z-index: 1;
}

.hero-split .hero-copy { max-width: 40ch; }

.hero-split h1 {
  margin: 0 0 clamp(20px, 3.4vh, 40px);
  font-size: clamp(3.4rem, 8.5vw, 7.2rem);
  line-height: 0.96;
  letter-spacing: -0.022em;
}

.hero-lede {
  margin: 0 0 clamp(16px, 2.4vh, 28px);
  max-width: 21ch;
  font-family: var(--serif);
  font-size: clamp(1.4rem, 3vw, 2.25rem);
  line-height: 1.22;
  text-wrap: balance;
}

.hero-sub {
  margin: 0;
  max-width: 50ch;
  font-size: clamp(0.98rem, 1.35vw, 1.1rem);
  line-height: 1.64;
  color: rgba(var(--fg-rgb), 0.72);
}

.hero-figure {
  margin: 0;
  justify-self: center;
}

.hero-figure img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: min(74vh, 720px);
  border-radius: var(--radius);
}

@media (min-width: 1024px) {
  .hero-split__grid {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  }
  .hero-figure { justify-self: end; }
}

@media (max-width: 1023px) {
  .hero-split { min-height: auto; padding-block: clamp(72px, 12vh, 140px); }
  .hero-figure img { max-height: 56vh; }
}

.section.intro { padding-top: clamp(56px, 9vh, 112px); }

/* The strip under the hero: one row, mixed shapes, no grid of twelve. */
.photo-strip {
  display: flex;
  gap: clamp(10px, 1.4vw, 18px);
  align-items: stretch;
  margin-bottom: clamp(40px, 7vh, 88px);
  overflow: hidden;
}

.photo-strip .ph {
  margin: 0;
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  height: clamp(180px, 24vw, 320px);
  border-radius: var(--radius);
  overflow: hidden;
}

.photo-strip .ph--land { flex-grow: 1.5; }

.photo-strip .ph img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}





.intro-lede {
  margin: 0 0 18px;
  font-size: clamp(1.02rem, 1.5vw, 1.16rem);
  line-height: 1.62;
}

.intro-skills h2 { margin-top: 0; }

.intro-outro {
  display: flex;
  align-items: flex-start;
  gap: clamp(18px, 3vw, 34px);
  margin-top: clamp(48px, 8vh, 96px);
  padding-top: clamp(28px, 4vh, 44px);
  border-top: 1px solid var(--line);
}

.intro-outro__rows { flex: 1 1 auto; min-width: 0; }

@media (max-width: 720px) {
  .photo-strip { overflow-x: auto; }
  .photo-strip .ph { flex: 0 0 42vw; height: 46vw; }
  .intro-outro { flex-direction: column; }
}

/* ==================================================================
   How I Work — order, not less content.

   The section was carrying five competing ways of grouping things at
   once. It now carries two: a method heading with a hairline under it,
   and a small label with a plus for anything foldable. Grouping above
   that is done with space.
   ================================================================== */

/* FIX 1 — the left column stops following the reader. It said its piece
   at the top; holding it fixed for the whole section made it argue with
   the right column the entire way down. */
.method-intro {
  position: static;
  top: auto;
}

/* Its three method words were the loudest thing on screen and the least
   important. Outlined, they read as the anchors they are. */
.method-intro .tag-row .tag {
  background: none;
  color: var(--accent-tag, rgba(var(--fg-rgb), 0.7));
  border: 1px solid currentColor;
}

.method-intro .tag-row .tag.reveal { --accent-tag: var(--reveal); }
.method-intro .tag-row .tag.mediate { --accent-tag: var(--mediate); }
.method-intro .tag-row .tag.translate { --accent-tag: var(--translate); }

/* FIX 2 — the method panels lose the card and the coloured bar. One
   hairline, one accent, all three identical. */
.method-panel {
  border-left: 0;
  border-top: 1px solid var(--line);
  background: none;
  padding: 22px 0 0;
  gap: 16px;
}

.method-panel h3 {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: baseline;
  gap: 14px;
}

/* The accent survives as a mark rather than a slab. */
.method-panel h3::before {
  content: "";
  width: 22px;
  height: 2px;
  background: var(--accent, currentColor);
  transform: translateY(-0.34em);
}

/* FIX 4 — three tiers. The space between them is what does the grouping,
   so nothing inside them needs another border to say where it belongs. */
.method-block { gap: 0; }

.method-tier {
  display: grid;
  gap: 26px;
}

.method-tier + .method-tier {
  margin-top: clamp(84px, 12vh, 168px);
}

.method-tier--principles { gap: 40px; }

/* The last tier is a summary, so it sits lighter than the two above it. */
.method-tier--principles h3 { font-size: clamp(18px, 1.6vw, 22px); }
.method-tier--principles .moves-tabs { margin-top: 4px; }

/* FIX 3's other half: the copy column has to be the wide one. */
.moves-stack .move > div {
  display: grid;
  gap: 10px;
  min-width: 0;
}

.moves-stack .move p { max-width: 62ch; }

/* ==================================================================
   The card.

   Opening this should not feel like another page of the site. It is the
   screen-printed card itself: closed as a fir, open as a canopy, with a
   person holding the trunk. The safe areas below are measured off the
   actual silhouettes, not guessed, because both shapes taper hard and
   type set to the bounding box lands outside the paper.
   ================================================================== */

.card {
  cursor: grab;
  touch-action: pan-y;
  --card-ink: #4a3b2f;
  --card-blue: #4b7cb8;
  --card-blue-deep: #2f5c99;
  --card-gold: #b07d3a;

  position: relative;
  z-index: 1;
  width: min(82vw, 1020px);
  height: min(84svh, 860px);
  margin: auto;
}

.card__paper {
  position: relative;
  width: 100%;
  height: 100%;
  color: var(--card-ink);
}

.card__tree {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  transition: opacity 620ms var(--ease), transform 620ms var(--ease);
}

/* The two silhouettes are too different to interpolate point by point, so
   the change is a cross-fade with a small opening movement behind it. */
.card__tree--closed { opacity: 1; transform: scale(1); }
.card__tree--open { opacity: 0; transform: scale(0.94); }

.card.is-open .card__tree--closed { opacity: 0; transform: scale(1.04); }
.card.is-open .card__tree--open { opacity: 1; transform: scale(1); }

/* ---- The two faces ---------------------------------------------------- */

.card__face {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  justify-items: center;
  text-align: center;
}

.card__face[hidden] { display: none; }

/* Closed: the fir is 22% wide at its crown and only passes 60% below the
   halfway line, so the writing sits low and narrow. */
.card__face--closed {
  padding: 26% 30% 12%;
  align-content: end;
}

/* The closed card says how to open it and nothing else. A folded card does
   not print its contents on the outside. */
.card__pull {
  display: grid;
  justify-items: center;
  gap: 10px;
  font-size: 11px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(47, 92, 153, 0.75);
}

.card__pull-arrow {
  position: relative;
  width: 1px;
  height: 30px;
  background: currentColor;
  transform-origin: bottom;
  animation: pull-hint 2.4s var(--ease) infinite;
}

.card__pull-arrow::after {
  content: "";
  position: absolute;
  top: -1px;
  left: -4px;
  width: 9px;
  height: 9px;
  border-left: 1px solid currentColor;
  border-top: 1px solid currentColor;
  transform: rotate(45deg);
}

@keyframes pull-hint {
  0%   { transform: scaleY(0.4); opacity: 0.3; }
  45%  { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(1) translateY(-8px); opacity: 0; }
}

/* Open: the canopy is widest between 52% and 76% down and pulls in sharply
   above and below, and the trunk at the base is silhouette, not page. */
.card__face--open {
  /* Grid centring and percentage padding both fought this: percentages on
     padding resolve against WIDTH, so a 9% top was really 9% of 1180px and
     the crown stayed empty above the writing. Block layout, vertical units. */
  display: block;
  display: flex;
  flex-direction: column;
  padding: 5svh 18% 20%;
  overflow: hidden;
}

/* The crown is the narrow part of the canopy, so only the title goes there,
   and it goes there narrow. */
.card__crown {
  width: min(58%, 470px);
  margin: 0 auto clamp(14px, 2.4vh, 26px);
}

.card__kicker {
  margin: 0 0 12px;
  font-size: 10.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--card-blue-deep);
  opacity: 0.85;
}

.card__face h2,
.card__title {
  margin: 0 0 24px;
  font-family: var(--serif);
  font-size: clamp(1.5rem, 3.2vw, 2.7rem);
  line-height: 1.1;
  color: var(--card-ink);
}

.card__face h2 em,
.card__title em { font-style: italic; }

.card__unfold {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  padding: 12px 22px;
  background: none;
  border: 1px solid var(--card-blue);
  border-radius: 999px;
  color: var(--card-blue-deep);
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 240ms var(--ease), color 240ms var(--ease);
}

.card__unfold:hover,
.card__unfold:focus-visible { background: var(--card-blue); color: #f2efe7; }

.card__unfold-mark {
  width: 10px;
  height: 10px;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
}

.card__scroll {
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 6px;
  text-align: left;
  scrollbar-width: thin;
}

.card__crown { text-align: center; }

.aside__list {
  margin: 0 auto 28px;
  padding-left: 1.1em;
  max-width: 60ch;
  display: grid;
  gap: 13px;
  color: var(--card-ink);
}

.aside__list li { line-height: 1.58; }
.aside__list li::marker { color: var(--card-gold); }

.card__leaves {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  justify-content: center;
}

.card__leaves .leaf {
  margin: 0;
  overflow: hidden;
  border-radius: 2px;
  height: clamp(70px, 9vh, 106px);
}

.card__leaves .leaf img {
  display: block;
  width: auto;
  height: 100%;
  filter: grayscale(1) sepia(1) hue-rotate(178deg) saturate(1.9) contrast(1.02) brightness(1.02);
  transition: filter 320ms var(--ease), transform 320ms var(--ease);
}

.card__leaves .leaf:hover img { filter: none; transform: scale(1.03); }

.card__close {
  position: absolute;
  top: 8px;
  right: 12px;
  z-index: 3;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  background: none;
  border: 1px solid rgba(var(--fg-rgb), 0.28);
  border-radius: 50%;
  color: rgba(var(--fg-rgb), 0.8);
  font-size: 14px;
  cursor: pointer;
}

.card__close:hover,
.card__close:focus-visible { border-color: rgba(var(--fg-rgb), 0.6); color: var(--fg); }

@media (max-width: 860px) {
  .card { width: 98vw; height: 96svh; }
  .card__face--closed { padding: 44% 19% 16%; }
  .card__face--open { padding: 22% 15% 18%; }
  .card__leaves .leaf { height: 62px; }
}

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

/* ==================================================================
   The band under the hero: a round portrait, then the reading.
   ================================================================== */

.intro-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(32px, 5vw, 76px);
  align-items: start;
}

@media (min-width: 900px) {
  .intro-grid { grid-template-columns: clamp(200px, 22vw, 300px) minmax(0, 1fr); }
}

.intro-portrait {
  margin: 0;
  aspect-ratio: 1;
  width: clamp(180px, 22vw, 300px);
  overflow: hidden;
  border-radius: 50%;
  border: 1px solid var(--line);
}

.intro-portrait img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The measure was the crowding, not the word count: the copy was running
   the full width of the page and the capability chips were solid blocks of
   colour immediately under it. Both now have room. */
.intro-copy > p,
.intro-skills > p { max-width: 64ch; }

.intro-lede {
  margin: 0 0 16px;
  font-size: clamp(1.02rem, 1.5vw, 1.16rem);
  line-height: 1.62;
}

.intro-skills { margin-top: clamp(44px, 6vh, 80px); }
.intro-skills h2 { margin: 0 0 18px; }
.intro-skills .tag-row { margin-top: clamp(28px, 4vh, 44px); gap: 8px; }

/* Outlined rather than filled. Seven saturated blocks were reading as the
   loudest thing in the section; they are a list of capabilities, not a
   colour field. The fill returns on hover and while a note is open. */
.intro-skills .tag.has-note {
  background: none;
  color: var(--tag-accent, rgba(var(--fg-rgb), 0.78));
  border: 1px solid currentColor;
  transition: background 220ms var(--ease), color 220ms var(--ease);
}

.intro-skills .tag.reveal { --tag-accent: var(--reveal); }
.intro-skills .tag.mediate { --tag-accent: var(--mediate); }
.intro-skills .tag.translate { --tag-accent: var(--translate); }
.intro-skills .tag.synthesis { --tag-accent: var(--coral); }

.intro-skills .tag.has-note:hover,
.intro-skills .tag.has-note[aria-expanded="true"] {
  background: var(--tag-accent);
  color: var(--ground);
}

/* The contact details close the section without asking for attention. */
.intro-outro {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  margin-top: clamp(64px, 10vh, 120px);
  padding-top: clamp(24px, 3.4vh, 38px);
  border-top: 1px solid var(--line);
}

.outro-link {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.02em;
  color: rgba(var(--fg-rgb), 0.5);
  transition: color 200ms var(--ease);
}

.outro-link:hover,
.outro-link:focus-visible { color: rgba(var(--fg-rgb), 0.92); }
.outro-link--plain { cursor: default; }

/* ==================================================================
   The name card. Printed matter, not a footer: labelled cells on a
   ruled field, a vertical tab down the edge, and one block of flat
   colour, after the event-card and shipping-label references.
   ================================================================== */

.card-band { padding-block: clamp(64px, 10vh, 130px); }

.namecard {
  position: relative;
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  border: 1px solid rgba(var(--fg-rgb), 0.55);
  background: rgba(var(--fg-rgb), 0.02);
}

.namecard__edge {
  display: grid;
  align-content: space-between;
  justify-items: center;
  gap: 12px;
  padding: 14px 0;
  border-right: 1px solid rgba(var(--fg-rgb), 0.55);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), 0.6);
}

.namecard__edge span {
  writing-mode: vertical-rl;
  text-orientation: mixed;
}

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

.namecard__cell {
  padding: 18px 20px 20px;
  border-bottom: 1px solid rgba(var(--fg-rgb), 0.22);
  border-right: 1px solid rgba(var(--fg-rgb), 0.22);
  min-width: 0;
}

.namecard__cell:nth-child(3n) { border-right: 0; }

.namecard__label {
  margin: 0 0 10px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), 0.45);
}

.namecard__value {
  margin: 0;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.7;
  color: rgba(var(--fg-rgb), 0.88);
  overflow-wrap: anywhere;
}

.namecard__value a { border-bottom: 1px solid rgba(var(--fg-rgb), 0.22); }
.namecard__value a:hover { border-bottom-color: currentColor; }
.namecard__alt { opacity: 0.55; }

.namecard__line {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(1.05rem, 1.9vw, 1.45rem);
  line-height: 1.32;
  text-wrap: balance;
}

.namecard__cell--wide { grid-column: span 2; }

/* One block of flat colour, the way the event cards carry exactly one. */
.namecard__cell--accent {
  background: var(--mediate);
  color: var(--ground);
}

.namecard__cell--accent .namecard__label { color: rgba(0, 0, 0, 0.55); }

.namecard__cell--mail { grid-column: span 1; }

.namecard__cell--cv {
  grid-column: span 3;
  border-right: 0;
  border-bottom: 0;
  display: flex;
  justify-content: flex-end;
  align-items: center;
}

.namecard__cv {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 12px 4px;
}

.namecard__cv:hover { color: var(--mediate); }

@media (max-width: 780px) {
  .namecard { grid-template-columns: 26px minmax(0, 1fr); }
  .namecard__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .namecard__cell:nth-child(3n) { border-right: 1px solid rgba(var(--fg-rgb), 0.22); }
  .namecard__cell:nth-child(2n) { border-right: 0; }
  .namecard__cell--wide,
  .namecard__cell--accent,
  .namecard__cell--cv { grid-column: span 2; }
}

/* ==================================================================
   Page ground: the gradient that used to be the opening screen, with
   the node field drifting over it. Fixed, so the page moves across it
   rather than dragging it along.
   ================================================================== */

.pagefield {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.pagefield__grain {
  position: absolute;
  inset: 0;
  opacity: 0.34;
}

.pagefield__grain canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Everything the reader actually reads sits above the ground. */
main { position: relative; z-index: 1; }
.line-nav { z-index: 40; }

/* ==================================================================
   Scatter: the photographs live in the dark around the card and arrive
   as the writing is read, rather than sitting in a block underneath it.
   ================================================================== */

.card__scatter {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.card__scatter .leaf {
  position: absolute;
  margin: 0;
  width: clamp(74px, 9vw, 132px);
  overflow: hidden;
  border-radius: 2px;
  opacity: 0;
  transform: translate3d(0, 14px, 0) scale(0.94);
  transition: opacity 620ms var(--ease), transform 620ms var(--ease);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
}

.card__scatter .leaf.is-out {
  opacity: 0.95;
  transform: translate3d(0, 0, 0) scale(1);
}

.card__scatter .leaf img {
  display: block;
  width: 100%;
  height: auto;
  filter: contrast(1.04) saturate(1.05) brightness(1.06);
}

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

/* ==================================================================
   Pull to open, and the taper the writing follows.
   ================================================================== */

.card.is-dragging { cursor: grabbing; }
.card.is-open { cursor: default; }

.card__paper {
  transform: translateY(var(--pull, 0px));
  transition: transform 420ms var(--ease);
}

.card.is-dragging .card__paper { transition: none; }

/* Her drawing sets the writing in a trapezoid that follows the canopy: short
   lines under the crown, longer lines as the tree widens. Two floats carry
   the shape and the text wraps to it. */
.taper {
  width: 116px;
  height: 260px;
  shape-margin: 14px;
}

.taper--left {
  float: left;
  shape-outside: polygon(0 0, 100% 0, 0 100%);
}

.taper--right {
  float: right;
  shape-outside: polygon(100% 0, 0 0, 100% 100%);
}

@media (max-width: 860px) {
  .taper { width: 62px; height: 190px; }
}

/* ==================================================================
   The photographs read as a pinned collage rather than a ring: mixed
   sizes, small rotations, allowed to overlap, arriving and leaving again
   so the dark keeps changing while the writing is read.
   ================================================================== */

.card__scatter .leaf {
  width: auto;
  transform: translate3d(0, 18px, 0) scale(0.92) rotate(var(--tilt, 0deg));
  transition: opacity 700ms var(--ease), transform 700ms var(--ease);
}

.card__scatter .leaf img {
  width: var(--w, 120px);
  height: auto;
}

.card__scatter .leaf.is-out {
  opacity: var(--peak, 0.9);
  transform: translate3d(0, 0, 0) scale(1) rotate(var(--tilt, 0deg));
}

/* ------------------------------------------------------------------
   Two projects close the grid rather than compete in it. They are real
   work and they stay legible, but they sit back: less contrast, a
   quieter cover, and they only come fully forward on hover.
   ------------------------------------------------------------------ */

.work-grid .project-card.quiet,
.has-reveal .work-grid .project-card.quiet[data-risen] { opacity: 0.74; }

.project-card.quiet .project-media img,
.project-card.quiet .project-media video {
  filter: grayscale(0.72) contrast(0.96) brightness(0.86);
  transition: filter 340ms var(--ease);
}

.project-card.quiet h3 {
  font-size: clamp(15px, 1.3vw, 18px);
  opacity: 0.86;
}

.project-card.quiet p { opacity: 0.68; }

.work-grid .project-card.quiet:hover,
.has-reveal .work-grid .project-card.quiet[data-risen]:hover,
.work-grid .project-card.quiet:focus-visible { opacity: 1; }

.project-card.quiet:hover .project-media img,
.project-card.quiet:hover .project-media video { filter: none; }

@media (prefers-reduced-motion: reduce) {
  .project-card.quiet .project-media img,
  .project-card.quiet .project-media video { transition: none; }
}

/* ==================================================================
   V2 case pages: share the homepage's typographic and navigation rules.
   ================================================================== */

:root {
  --display: "Barlow Condensed", "Hanken Grotesk", sans-serif;
  --script: "Allura", cursive;
}

body[data-project] {
  --mono: system-ui, sans-serif;
  --project-black: #000;
  background: var(--project-black);
  font-family: var(--sans);
  font-size: 16px;
  letter-spacing: 0;
}

body[data-project] .line-nav.case-nav {
  position: sticky;
  top: 0;
  z-index: 80;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  min-height: 76px;
  gap: 0;
  padding: 0 clamp(30px, 5.2vw, 106px);
  background: rgba(0, 0, 0, 0.86);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(15px);
  font-family: system-ui, sans-serif;
  font-size: 13px;
  text-transform: uppercase;
  transform: translateY(0);
  transition: transform 420ms var(--ease), opacity 280ms ease;
}

body[data-project] .line-nav__brand {
  display: inline-flex;
  width: 42px;
  height: 42px;
  align-items: center;
  padding: 0;
}

body[data-project] .line-nav__name {
  display: none;
}

body[data-project] .line-nav__brand .brand-mark {
  width: 36px;
  height: 36px;
  border: 0;
  color: inherit;
}

body[data-project] .line-nav__brand .hub-mark {
  width: 36px;
  height: 36px;
  overflow: visible;
}

body[data-project] .line-nav__brand .hub-mark line {
  stroke-width: 11;
  stroke-linecap: round;
}

body[data-project] .line-nav__links {
  justify-self: center;
  display: flex;
  align-items: center;
  gap: clamp(22px, 3vw, 48px);
  overflow: visible;
}

body[data-project] .case-switch__meta {
  padding: 0;
  color: var(--fg);
  font-family: system-ui, sans-serif;
  font-size: 13px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0;
}

body[data-project] .case-switch__meta:hover {
  opacity: 0.56;
  color: var(--fg);
}

body[data-project] .case-hero h1,
body[data-project] .section-title,
body[data-project] .case-copy h2,
body[data-project] .about h2 {
  font-family: var(--display);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: uppercase;
}

body[data-project] .case-hero h1 {
  font-family: var(--sans);
  font-size: clamp(42px, 5vw, 52px);
  line-height: 0.98;
  font-weight: 800;
}

body[data-project] .callout {
  font-family: var(--sans);
  font-size: clamp(22px, 3.1vw, 42px);
  line-height: 1.12;
}

body[data-project] p,
body[data-project] li {
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.62;
}

body[data-project] .eyebrow,
body[data-project] .section-number,
body[data-project] .meta-grid,
body[data-project] .button,
body[data-project] .footer,
body[data-project] figcaption {
  font-family: var(--mono);
  letter-spacing: 0;
}

.project-menu {
  position: relative;
}

body[data-project] .project-menu__trigger {
  position: relative;
  display: inline-grid;
  min-width: 88px;
  min-height: 32px;
  place-items: center;
  border: 0;
  background: transparent;
  cursor: pointer;
}

body[data-project] .project-menu.is-open .project-menu__trigger {
  color: var(--fg);
  opacity: 1;
}

.project-menu__label {
  grid-area: 1 / 1;
  transition: opacity 180ms ease, transform 220ms var(--ease), filter 220ms ease;
}

.project-menu.is-open .project-menu__label {
  opacity: 0;
  transform: translateY(-4px);
  filter: blur(3px);
  pointer-events: none;
}

.project-menu__folder {
  --folder-color: #003153;
  position: relative;
  grid-area: 1 / 1;
  width: 32px;
  height: 24px;
  opacity: 0;
  transform: translateY(4px) scale(0.9);
  transition: opacity 180ms ease, transform 260ms var(--ease);
}

.project-menu.is-open .project-menu__folder {
  opacity: 1;
  transform: translateY(0) scale(1);
}

.project-menu__folder span {
  position: absolute;
  display: block;
  border: 1px solid rgba(255, 255, 255, 0.44);
  background: var(--folder-color);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 0 18px rgba(0, 49, 83, 0.5);
}

.project-menu__folder .folder-back {
  left: 2px;
  right: 2px;
  bottom: 2px;
  height: 16px;
  border-radius: 3px;
  opacity: 0.72;
}

.project-menu__folder .folder-tab {
  left: 4px;
  top: 2px;
  width: 14px;
  height: 8px;
  border-bottom: 0;
  border-radius: 3px 3px 0 0;
  transform-origin: 0 100%;
  transition: transform 280ms var(--ease);
}

.project-menu__folder .folder-front {
  left: 0;
  right: 0;
  bottom: 0;
  height: 15px;
  border-radius: 3px;
  transform-origin: 50% 100%;
  transition: transform 340ms var(--ease);
}

.project-menu.is-open .folder-tab {
  transform: skewX(-9deg) translateY(-2px);
}

.project-menu.is-open .folder-front {
  transform: perspective(80px) rotateX(-18deg) translateY(2px);
}

body[data-project] .project-menu__panel {
  position: absolute;
  top: calc(100% + 14px);
  left: 50%;
  z-index: 120;
  width: min(420px, calc(100vw - 38px));
  padding: 8px;
  background: rgba(12, 12, 12, 0.9);
  -webkit-backdrop-filter: blur(18px) saturate(1.1);
  backdrop-filter: blur(18px) saturate(1.1);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  color: var(--fg);
  text-transform: none;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.44);
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, -6px);
  transition: opacity 200ms ease, transform 240ms var(--ease), visibility 200ms ease;
}

body[data-project] .project-menu.is-open .project-menu__panel {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}

.project-menu__item {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  min-height: 34px;
  padding: 0 10px;
  border: 0;
  border-radius: 6px;
  color: rgba(255, 255, 255, 0.52);
  font-family: system-ui, sans-serif;
  font-size: 14px;
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0;
  text-transform: none;
  transition: color 160ms ease, background 160ms ease;
}

.project-menu__item span {
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  color: rgba(255, 255, 255, 0.3);
  transition: color 160ms ease;
}

.project-menu__item strong {
  overflow: hidden;
  font: inherit;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.project-menu__item.is-here,
.project-menu__item:hover,
.project-menu__item:focus-visible {
  color: var(--fg);
}

.project-menu__item:hover,
.project-menu__item:focus-visible {
  background: rgba(255, 255, 255, 0.08);
  outline: none;
}

.project-menu__item.is-here {
  background: rgba(255, 255, 255, 0.05);
}

.project-menu__item.is-here span,
.project-menu__item:hover span,
.project-menu__item:focus-visible span {
  color: rgba(255, 255, 255, 0.82);
}

.case-gallery.case-accordion-gallery {
  display: flex;
  gap: 12px;
  overflow: hidden;
  min-height: 560px;
  scroll-snap-type: none;
}

.case-gallery.case-accordion-gallery figure {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.04);
  transition: flex 520ms var(--ease), filter 360ms ease;
}

.case-gallery.case-accordion-gallery figure:hover,
.case-gallery.case-accordion-gallery figure:focus-within {
  flex: 4.6 1 0;
}

.case-gallery.case-accordion-gallery img,
.case-gallery.case-accordion-gallery video {
  width: 100%;
  height: 560px;
  object-fit: cover;
  border: 0;
  background: transparent;
  filter: grayscale(0.25) contrast(1.08);
}

.case-gallery.case-accordion-gallery figcaption {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 14px;
  margin: 0;
  padding: 10px 12px;
  background: rgba(15, 16, 17, 0.74);
  color: var(--fg);
  font-size: 12px;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 260ms ease, transform 260ms ease;
}

.case-gallery.case-accordion-gallery figure:hover figcaption,
.case-gallery.case-accordion-gallery figure:focus-within figcaption {
  opacity: 1;
  transform: translateY(0);
}

@media (max-width: 860px) {
  body[data-project] .line-nav.case-nav {
    grid-template-columns: auto 1fr;
    padding-inline: 18px;
  }

  body[data-project] .line-nav__links {
    justify-self: end;
    gap: 16px;
    flex-wrap: wrap;
  }

  body[data-project] .case-switch__meta {
    font-size: 11px;
  }

  .case-gallery.case-accordion-gallery {
    min-height: 0;
    overflow-x: auto;
  }

  .case-gallery.case-accordion-gallery figure,
  .case-gallery.case-accordion-gallery figure:hover {
    flex: 0 0 82vw;
  }

  .case-gallery.case-accordion-gallery img,
  .case-gallery.case-accordion-gallery video {
    height: 360px;
  }
}

/* ---------------------------------------------------------------------------
   Case hero: two levels of intro.
   The claim carries the page and stays large; the paragraph under it explains
   the claim at body size, so a reader can take the argument at a glance and
   the detail only if they want it.
--------------------------------------------------------------------------- */
.callout-sub {
  max-width: 62ch;
  margin: 14px 0 0;
  padding-left: 26px;
  font-size: 16px;
  line-height: 1.62;
  opacity: 0.78;
}

body[data-project] .callout-sub {
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.62;
}

/* A lead film that introduces itself on the way past, rather than sitting
   there already finished before the reader arrives. */
.lead-reveal .case-video {
  opacity: 0;
  transform: translateY(46px) scale(0.985);
  transition:
    opacity 900ms cubic-bezier(0.2, 0.8, 0.2, 1),
    transform 1100ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.lead-reveal .case-video figcaption {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 700ms ease 380ms, transform 700ms ease 380ms;
}

.lead-reveal.is-revealed .case-video {
  opacity: 1;
  transform: none;
}

.lead-reveal.is-revealed .case-video figcaption {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .lead-reveal .case-video,
  .lead-reveal .case-video figcaption {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ---------------------------------------------------------------------------
   Closing television, on case pages
   The same set that closes the home page now closes every project page, so a
   reader who arrives on a case from anywhere still lands on contact details
   and a way back up. The reveal follows Motion's screenshot-scroll-reveal:
   tilted back and dim at the top of the section, upright and lit by the time
   it settles. All values arrive as --tv-* from contact-stage.js.
--------------------------------------------------------------------------- */
body[data-project] .contact-tv {
  position: relative;
  z-index: 2;
  min-height: 90vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(28px, 4.5vh, 52px);
  padding: clamp(30px, 5vw, 76px) clamp(28px, 4.6vw, 94px);
  overflow: hidden;
  background: #080808;
  color: var(--fg);
  perspective: 1500px;
  perspective-origin: 50% 60%;
}

body[data-project] .contact-tv::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    linear-gradient(rgba(255, 255, 255, 0.035) 50%, rgba(0, 0, 0, 0.2) 50%),
    radial-gradient(circle at 45% 35%, transparent 36%, rgba(0, 0, 0, 0.5));
  background-size: 100% 5px, auto;
  opacity: calc(var(--tv-progress, 0) * 0.42);
}

body[data-project] .contact-tv__set {
  position: relative;
  z-index: 1;
  width: min(1200px, 86vw);
  aspect-ratio: 2048 / 1080;
  transform-origin: 50% 100%;
  transform:
    translateY(var(--tv-y, 60px))
    rotateX(var(--tv-rot, 34deg))
    scale(var(--tv-scale, 0.8));
  opacity: var(--tv-opacity, 0.3);
  filter: saturate(0.92) contrast(1.12) brightness(var(--tv-bright, 0.5));
}

body[data-project] .contact-tv__frame {
  position: absolute;
  inset: 0;
  z-index: 4;
  width: 100%;
  height: 100%;
  object-fit: contain;
  border: 0;
  pointer-events: none;
}

body[data-project] .contact-tv__screen {
  position: absolute;
  left: 0.6%;
  top: 13.4%;
  z-index: 2;
  width: 67.2%;
  height: 82.4%;
  overflow: hidden;
  border-radius: 0 0 7% 7% / 0 0 7% 7%;
  background: #030303;
}

body[data-project] .contact-tv__screen video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
  filter: saturate(0.72) contrast(1.18) brightness(0.54);
}

body[data-project] .contact-tv__screen::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(rgba(255, 255, 255, 0.08) 50%, rgba(0, 0, 0, 0.28) 50%),
    radial-gradient(circle at 47% 38%, transparent 46%, rgba(0, 0, 0, 0.58));
  background-size: 100% 4px, auto;
  opacity: 0.62;
  mix-blend-mode: screen;
  animation: caseContactScan 5.6s linear infinite;
}

/* Nested inside .contact-tv__set rather than trailing below the cabinet as
   a separate block, so it reads as a lower third printed on the screen. */
/* Centred on the screen's own bounds (left: 0.6%, width: 67.2% above),
   not the whole cabinet: at 50% of the full image the band was centred
   past the screen's right edge, onto the wood speaker grille. */
body[data-project] .contact-tv__links {
  position: absolute;
  left: 34.2%;
  bottom: 6%;
  z-index: 9;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 22px;
  width: 58%;
  padding: 10px 14px;
  border-radius: 3px;
  background: rgba(3, 3, 3, 0.5);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  border-top: 1px solid rgba(255, 255, 255, 0.34);
  border-bottom: 1px solid rgba(255, 255, 255, 0.34);
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.3;
  text-transform: uppercase;
  opacity: var(--tv-progress, 0);
  transform: translate(-50%, calc((1 - var(--tv-progress, 0)) * 16px));
}

body[data-project] .contact-tv__links span,
body[data-project] .contact-tv__links a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: rgba(255, 255, 255, 0.72);
  font-size: 11px;
  transition: color 180ms ease;
}

body[data-project] .contact-tv__links a::before { content: "+"; }

body[data-project] .contact-tv__links a:hover,
body[data-project] .contact-tv__links a:focus-visible {
  color: var(--fg);
}

@keyframes caseContactScan {
  0% { background-position: 0 0, 0 0; }
  100% { background-position: 0 24px, 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  body[data-project] .contact-tv__set {
    transform: none;
    opacity: 1;
    filter: saturate(0.92) contrast(1.12) brightness(0.9);
  }

  body[data-project] .contact-tv__links {
    opacity: 1;
    transform: none;
  }

  body[data-project] .contact-tv__screen::after { animation: none; }
}

@media (max-width: 860px) {
  body[data-project] .contact-tv {
    min-height: 84vh;
    perspective: 900px;
    gap: 24px;
  }

  body[data-project] .contact-tv__set {
    width: min(94vw, 480px);
  }

  body[data-project] .contact-tv__links {
    bottom: 8%;
    width: 60%;
    padding: 8px 10px;
    flex-wrap: wrap;
    justify-content: flex-start;
    gap: 10px 16px;
  }
}

/* ---------------------------------------------------------------------------
   Hosted video facade
   The long film lives on YouTube rather than in the repository. Until someone
   presses play this is one poster image and a button: no third-party script,
   no cookie, no request off-site for a reader who never watches it.
--------------------------------------------------------------------------- */
.case-embed {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  overflow: hidden;
  background: #101010;
  border: 1px solid currentColor;
}

.case-embed__poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
  transition: transform 620ms var(--ease), filter 420ms ease;
}

.case-embed:hover .case-embed__poster {
  transform: scale(1.02);
  filter: brightness(1.06);
}

.case-embed__play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0.34), rgba(0, 0, 0, 0.12) 46%, transparent 70%);
  cursor: pointer;
}

.case-embed__play::before {
  content: "";
  position: absolute;
  width: clamp(64px, 7vw, 92px);
  aspect-ratio: 1;
  border: 1px solid rgba(255, 255, 255, 0.72);
  border-radius: 50%;
  background: rgba(15, 16, 17, 0.42);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: transform 320ms var(--ease), background 320ms ease, border-color 320ms ease;
}

.case-embed__triangle {
  position: relative;
  width: 0;
  height: 0;
  margin-left: 0.34em;
  border-top: clamp(10px, 1.1vw, 14px) solid transparent;
  border-bottom: clamp(10px, 1.1vw, 14px) solid transparent;
  border-left: clamp(17px, 1.9vw, 23px) solid var(--fg);
  transition: transform 320ms var(--ease);
}

.case-embed__play:hover::before,
.case-embed__play:focus-visible::before {
  transform: scale(1.08);
  background: rgba(254, 113, 65, 0.28);
  border-color: var(--fg);
}

.case-embed__play:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: -4px;
}

.case-embed iframe {
  width: 100%;
  height: 100%;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .case-embed__poster,
  .case-embed__play::before,
  .case-embed__triangle {
    transition: none;
  }

  .case-embed:hover .case-embed__poster {
    transform: none;
  }
}

/* ---------------------------------------------------------------------------
   At a glance: the first thing under a case hero. Three answers, then the
   role list and metadata, so a reader can stop here and still know what the
   project was and what I did on it.
   --------------------------------------------------------------------------- */
.glance .section-inner {
  display: grid;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
}

.glance-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
}

.glance-cell,
.glance-role,
.glance-meta > div {
  background: var(--ground);
  padding: 22px 24px 26px;
}

.glance-label,
.glance-meta strong {
  display: block;
  margin-bottom: 10px;
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), 0.55);
}

.glance-cell p {
  margin: 0;
  font-size: 1.02rem;
  line-height: 1.5;
}

.glance-cell--result .glance-label {
  color: var(--glance-accent, var(--coral));
}

.glance-foot {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 1px;
  background: var(--line);
}

.glance-role ul {
  margin: 0;
  padding-left: 1.1em;
  font-size: 0.94rem;
  line-height: 1.5;
}

.glance-role li + li { margin-top: 8px; }

.glance-meta {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  font-size: 0.86rem;
  line-height: 1.45;
}

.glance-meta > div { padding: 16px 18px; }
.glance-meta strong { margin-bottom: 6px; }
.glance-meta .glance-meta-wide,
.glance-meta > div:nth-child(odd):is(:last-child, :has(+ .glance-meta-wide)) { grid-column: 1 / -1; }

/* Collapsed blocks: one labelled line, full block on demand. */
.case-more { padding-top: 0; padding-bottom: 0; }
.case-more .disclose { margin: 0; }
.case-more .disclose-summary { padding: 22px 0; color: rgba(var(--fg-rgb), 0.78); }
.case-more .disclose-title { font-size: 0.78rem; }
.case-more .more-body > .section-inner,
.case-more .more-body > .case-two { padding-left: 0; padding-right: 0; }
.case-more .more-body { padding-bottom: 32px; }

@media (max-width: 860px) {
  .glance-grid,
  .glance-foot { grid-template-columns: 1fr; }
  .glance-cell,
  .glance-role { padding: 18px 16px 20px; }
  .glance-meta > div { padding: 14px 16px; }
}
