/* ==========================================================================
   Will Holman — Portfolio
   Two-tone. Helvetica only. Dark by default.
   ========================================================================== */

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img { display: block; max-width: 100%; }
button, input, textarea { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }

/* ---------- Tokens ---------- */
:root {
  --sans: "Helvetica Neue", Helvetica, Arial, "Liberation Sans", sans-serif;

  --bg:    #000000;
  --fg:    #ffffff;
  --muted: #8c8c8c;
  --line:  rgba(255, 255, 255, 0.16);
  --card:  #0b0b0b;
  --veil:  rgba(0, 0, 0, 0.72);

  --gutter: clamp(20px, 4vw, 56px);
  --maxw: 1520px;
  --header-h: 76px;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
}

[data-theme="light"] {
  --bg:    #ffffff;
  --fg:    #000000;
  --muted: #6e6e6e;
  --line:  rgba(0, 0, 0, 0.14);
  --card:  #f3f3f3;
  --veil:  rgba(255, 255, 255, 0.72);
}

/* ---------- Base ---------- */
html { -webkit-text-size-adjust: 100%; }

body {
  font-family: var(--sans);
  background: var(--bg);
  color: var(--fg);
  font-size: 16px;
  line-height: 1.5;
  letter-spacing: -0.01em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-height: 100vh;
  transition: background-color 0.45s var(--ease), color 0.45s var(--ease);

  /* The arrows (U+2197) and the copyright sign both have emoji presentation
     forms, which iOS and Android will happily render as colour glyphs. Every
     such character in the markup is followed by U+FE0E to force text
     presentation; this reinforces it for browsers that support the property.
     The site uses no intentional emoji, so applying it globally is safe. */
  font-variant-emoji: text;
}

/* Deliberately NO overflow-x on <body>: a non-visible overflow makes body its
   own scroll container, which stops position: sticky working on the landing
   masthead. Nothing overflows horizontally anyway — the intro clips its own
   flying artwork, and the pile scatter is mathematically bounded to its stage. */
html { overflow-x: clip; }

/* Theme switch shouldn't animate on first paint */
body.no-transition, body.no-transition * { transition: none !important; }

::selection { background: var(--fg); color: var(--bg); }

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

.shell {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

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

.skip-link {
  position: absolute; top: 8px; left: 8px;
  padding: 10px 16px; z-index: 200;
  background: var(--fg); color: var(--bg);
  font-size: 13px; font-weight: 700;
  transform: translateY(-200%);
}
.skip-link:focus { transform: translateY(0); }

/* ==========================================================================
   Header
   ========================================================================== */
.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 90;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  transition: background-color 0.45s var(--ease), transform 0.4s var(--ease);
}
@supports not (backdrop-filter: blur(1px)) {
  .header { background: var(--bg); }
}

.header__inner {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.header__brand {
  display: flex;
  align-items: center;
  gap: clamp(12px, 1.8vw, 20px);
  min-width: 0;
}

/* Live Eastern-time clock. tabular-nums keeps the digits a fixed width so
   the header doesn't twitch as the minute ticks over. Empty until JS fills
   it, so it simply isn't there if scripts fail. */
.clock {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  transition: color 0.35s var(--ease);
}
.clock:empty { display: none; }

/* The header is already tight on phones with three links and the toggle. */
@media (max-width: 640px) {
  .clock { display: none; }
}

/* Monogram */
.logo {
  display: inline-flex;
  align-items: center;
  font-weight: 700;
  font-size: 21px;
  letter-spacing: -0.06em;
  line-height: 1;
  padding: 6px 2px;
}
/* The W is set light to echo "Will" in the masthead. Same caveat as
   .name-light: a genuine Light cut only exists where Helvetica Neue does. */
.logo span:first-child { font-weight: 300; letter-spacing: -0.03em; }
.logo span { display: inline-block; transition: transform 0.5s var(--ease); }
.logo:hover span:first-child { transform: translateX(-2px) translateY(-1px); }
.logo:hover span:last-child  { transform: translateX(2px)  translateY(1px); }

.nav {
  display: flex;
  align-items: center;
  gap: clamp(18px, 2.4vw, 40px);
}

.nav__link {
  position: relative;
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: -0.005em;
  padding: 6px 0;
  white-space: nowrap;
  color: var(--fg);
}
.nav__link::after {
  content: "";
  position: absolute;
  left: 0; bottom: 2px;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.42s var(--ease);
}
.nav__link:hover::after,
.nav__link[aria-current="page"]::after { transform: scaleX(1); transform-origin: left; }
.nav__link[aria-current="page"] { font-weight: 700; }

.nav__arrow {
  display: inline-block;
  font-size: 0.82em;
  margin-left: 3px;
  transition: transform 0.42s var(--ease);
}
.nav__link:hover .nav__arrow { transform: translate(2px, -2px); }

/* Theme toggle — pill with sun + moon */
.toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  cursor: pointer;
  flex: none;
  -webkit-tap-highlight-color: transparent;
}
.toggle__knob {
  position: absolute;
  top: 3px; left: 3px;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--fg) 14%, transparent);
  transition: transform 0.5s var(--ease);
}
[data-theme="dark"] .toggle__knob { transform: translateX(28px); }
.toggle__icon {
  position: relative;
  z-index: 1;
  width: 26px; height: 26px;
  display: grid;
  place-items: center;
  opacity: 0.4;
  transition: opacity 0.4s var(--ease);
}
.toggle__icon svg { width: 15px; height: 15px; display: block; }
[data-theme="light"] .toggle__icon--sun,
[data-theme="dark"]  .toggle__icon--moon { opacity: 1; }

/* ==========================================================================
   Page frame
   ========================================================================== */
.main { padding-top: var(--header-h); }

.page-head { padding: clamp(56px, 10vh, 120px) 0 clamp(32px, 5vh, 56px); }

/* Project heads that carry client logos: title block left, logos right,
   centred against each other. */
.page-head--split {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(24px, 5vw, 72px);
}
.page-head__main { min-width: 0; }

/* Fixed box so swapping logos of different proportions never shifts the
   layout; each one is absolutely positioned and letterboxed inside it. */
.logo-loop {
  position: relative;
  flex: none;
  width: clamp(140px, 19vw, 250px);
  height: clamp(66px, 8.5vw, 108px);
}

.logo-loop__item {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center right;
  opacity: 0;
  transition: opacity 0.85s var(--ease);
}
.logo-loop__item.is-active { opacity: 1; }

/* The source art is monochrome black on transparency, so it would vanish
   against the dark background. Inverting turns it white. */
[data-theme="dark"] .logo-loop__item { filter: invert(1); }

/* Stack under the title on narrow screens rather than squeezing both. */
@media (max-width: 720px) {
  .page-head--split {
    display: block;
  }
  .logo-loop {
    width: clamp(130px, 40vw, 200px);
    height: clamp(60px, 18vw, 88px);
    margin-top: clamp(22px, 4vh, 34px);
  }
  .logo-loop__item { object-position: center left; }
}

.page-head__label {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 18px;
}

/* Matches .hero__name on the landing page — one heading scale across the site.
   Change both together. */
.page-head__title {
  font-weight: 700;
  font-size: clamp(2.2rem, 6.6vw, 5.4rem);
  line-height: 0.88;
  letter-spacing: -0.05em;
}

.page-head__label a { transition: opacity 0.3s var(--ease); }
.page-head__label a:hover { opacity: 0.62; }

/* Back control on project pages */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin-bottom: clamp(22px, 4vh, 38px);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--muted);
  transition: color 0.35s var(--ease);
}
.back-link:hover { color: var(--fg); }

.back-link__icon {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--line);
  border-radius: 50%;
  flex: none;
  transition: background-color 0.35s var(--ease), color 0.35s var(--ease),
              border-color 0.35s var(--ease), transform 0.35s var(--ease);
}
.back-link__icon svg { width: 15px; height: 15px; display: block; }
.back-link:hover .back-link__icon {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
  transform: translateX(-3px);
}

.page-head__client {
  margin-top: 14px;
  font-size: clamp(1.05rem, 2vw, 1.5rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  color: var(--muted);
}

/* Matches .hero__tagline on the landing page. */
.page-head__sub {
  margin-top: clamp(12px, 1.4vw, 18px);
  max-width: 62ch;
  font-size: clamp(0.95rem, 1.55vw, 1.3rem);
  line-height: 1.35;
  letter-spacing: -0.02em;
  color: var(--muted);
}

/* ==========================================================================
   Home
   ========================================================================== */
/* ---------- Landing masthead --------------------------------------------
   The masthead pins directly beneath the fixed header and stays put while the
   piles scroll up behind it. Sticky (not fixed) so it keeps its place in flow
   and the piles start below it without any JS measuring heights.
   Note: this only works because <body> is not a scroll container — see the
   comment on body's overflow in the Base section. */
.home-head {
  position: sticky;
  top: var(--header-h);
  z-index: 50;
}

/* The frosted band. Separate layer so it can extend past the masthead and
   fade out, rather than cutting the blur off at a hard edge. */
.home-head__blur {
  position: absolute;
  inset: 0 0 -56px 0;
  pointer-events: none;
  background: color-mix(in srgb, var(--bg) 66%, transparent);
  -webkit-backdrop-filter: blur(24px) saturate(118%);
          backdrop-filter: blur(24px) saturate(118%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 58%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0, #000 58%, transparent 100%);
}
/* Without backdrop-filter there's no blur to see through, so go opaque
   rather than leaving the masthead sitting illegibly over the artwork. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .home-head__blur { background: var(--bg); }
}

.home-head .shell { position: relative; z-index: 1; }

.hero {
  padding: clamp(34px, 5.5vh, 68px) 0 clamp(34px, 6vh, 76px);
}

.hero__name {
  font-weight: 700;
  font-size: clamp(2.2rem, 6.6vw, 5.4rem);
  line-height: 0.88;
  letter-spacing: -0.05em;
}

/* "Will" set light against the bold surname.
   Helvetica Neue carries a real Light cut at 300 on macOS and iOS. Windows has
   no Helvetica and falls back to Arial, which ships only Regular and Bold — and
   browsers never synthesise a lighter face, only a bolder one — so it lands on
   Regular there. Still a clear step down from the 700 surname either way. */
.name-light {
  font-weight: 300;
  letter-spacing: -0.035em; /* light faces need a touch less negative tracking */
}

.hero__tagline {
  margin-top: clamp(12px, 1.4vw, 18px);
  font-size: clamp(0.95rem, 1.55vw, 1.3rem);
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: -0.02em;
}

/* Mask-reveal wrapper for intro copy */
.reveal-line { display: block; overflow: hidden; }
.reveal-line > * { display: block; will-change: transform; }

.hero__cta {
  margin-top: clamp(36px, 6vh, 64px);
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  font-weight: 500;
  color: var(--muted);
  transition: color 0.35s var(--ease);
}
.hero__cta:hover { color: var(--fg); }
.hero__cta svg { width: 14px; height: 14px; }
.hero__cta:hover svg { animation: nudge 0.6s var(--ease); }
@keyframes nudge {
  50% { transform: translate(3px, -3px); }
}

/* ==========================================================================
   Work index — the piles
   ========================================================================== */
/* Tight grid. Safe because the resting scatter below is bounded so no card
   ever leaves its own stage — see the nth-child transforms and REST in pile.js.
   One variable drives both axes, so the column gap always matches the row gap. */
.piles {
  --pile-gap: clamp(14px, 2vw, 30px);
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  row-gap: var(--pile-gap);
  column-gap: var(--pile-gap);
  padding-bottom: clamp(80px, 14vh, 180px);
}
@media (max-width: 760px) {
  .piles { grid-template-columns: 1fr; }
}

.pile {
  display: block;
  position: relative;
  -webkit-tap-highlight-color: transparent;
}

.pile__stage {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  isolation: isolate;
  cursor: pointer;
  container-type: inline-size;
}

/* every card shares the exact same box, so they converge precisely */
.pile__card {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  will-change: transform, opacity;
  transform-origin: 50% 50%;
  backface-visibility: hidden;
  pointer-events: none;
  filter: drop-shadow(0 18px 40px rgba(0, 0, 0, 0.55));
}
[data-theme="light"] .pile__card {
  filter: drop-shadow(0 18px 40px rgba(0, 0, 0, 0.18));
}

/* The solid shape the pile collapses into — white on dark, black on light.
   Masked by the target image so cut-out artwork keeps its true silhouette. */
.pile__shape {
  position: absolute;
  inset: 0;
  background: var(--fg);
  opacity: 0;
  pointer-events: none;
  will-change: opacity, transform;
  transform-origin: 50% 50%;

  /* mask-image is set inline on the element: a url() inside a custom property
     would resolve against this stylesheet's folder, not the document. */
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
}

.pile__label {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 6%;
  text-align: center;
  color: var(--bg);
  font-weight: 700;
  font-size: clamp(1.1rem, 3.6vw, 2.6rem);
  line-height: 0.94;
  letter-spacing: -0.045em;
  text-transform: uppercase;
  text-wrap: balance;
  opacity: 0;
  pointer-events: none;
  will-change: opacity, transform;
}
/* size against the pile, not the viewport, so it fits the shape at any column width */
@supports (container-type: inline-size) {
  .pile__label { font-size: clamp(1.05rem, 8.4cqw, 2.9rem); }
}

.pile__index {
  position: absolute;
  top: 0; left: 0;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--muted);
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.pile:hover .pile__index,
.pile:focus-visible .pile__index { opacity: 1; }

/* Static rest scatter, so the pile reads as a pile before JS runs — and for
   anyone on reduced motion, where pile.js bails out entirely.
   GSAP writes inline transforms that supersede these once it takes over. */
.pile__card:nth-child(1) { transform: translate(-16%,  -9%) rotate(-12deg) scale(0.52); }
.pile__card:nth-child(2) { transform: translate( 14%, -13%) rotate(  8deg) scale(0.50); }
.pile__card:nth-child(3) { transform: translate(-20%,  11%) rotate( -5deg) scale(0.49); }
.pile__card:nth-child(4) { transform: translate( 19%,   9%) rotate( 12deg) scale(0.50); }
.pile__card:nth-child(5) { transform: translate( -4%,  -2%) rotate(  4deg) scale(0.55); }
.pile__card:nth-child(6) { transform: translate(  2%,  13%) rotate( -3deg) scale(0.58); }

/* Reduced motion: collapse to the shape on hover/focus, no choreography */
.reduce-motion .pile__card,
.reduce-motion .pile__shape,
.reduce-motion .pile__label { transition: opacity 0.2s linear, transform 0.2s linear; }

.reduce-motion .pile:hover .pile__card,
.reduce-motion .pile:focus-within .pile__card { opacity: 0; transform: scale(0.82); }

.reduce-motion .pile:hover .pile__shape,
.reduce-motion .pile:focus-within .pile__shape,
.reduce-motion .pile:hover .pile__label,
.reduce-motion .pile:focus-within .pile__label { opacity: 1; }

.reduce-motion .pile__shape { transform: scale(0.82); }

/* ==========================================================================
   Project pages — editorial grid
   ========================================================================== */
.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(14px, 2.2vw, 34px);
  align-items: start;
  padding-bottom: clamp(80px, 14vh, 160px);
}

.grid__item {
  grid-column: span 6;
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 0.8s var(--ease), transform 0.8s var(--ease);
}
.grid__item.is-in { opacity: 1; transform: none; }

/* rhythm: wide / narrow / narrow / wide … */
.grid__item[data-span="4"] { grid-column: span 4; }
.grid__item[data-span="5"] { grid-column: span 5; }
.grid__item[data-span="6"] { grid-column: span 6; }
.grid__item[data-span="7"] { grid-column: span 7; }
.grid__item[data-span="8"] { grid-column: span 8; }
.grid__item[data-span="12"] { grid-column: span 12; }

@media (max-width: 900px) {
  .grid__item,
  .grid__item[data-span="4"],
  .grid__item[data-span="5"],
  .grid__item[data-span="7"],
  .grid__item[data-span="8"] { grid-column: span 6; }
  .grid__item[data-span="12"] { grid-column: span 12; }
}
@media (max-width: 560px) {
  .grid__item,
  .grid__item[data-span="4"],
  .grid__item[data-span="5"],
  .grid__item[data-span="6"],
  .grid__item[data-span="7"],
  .grid__item[data-span="8"] { grid-column: span 12; }
}

.grid__item img {
  width: 100%;
  height: auto;
  background: var(--card);
}
/* merch cut-outs float rather than sit in a box */
.grid--cutout .grid__item img { background: transparent; }

/* Zoom trigger wrapping each grid image */
.grid__zoom {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  -webkit-tap-highlight-color: transparent;
}
.grid__zoom img { transition: opacity 0.4s var(--ease); }
.grid__zoom:hover img { opacity: 0.82; }

/* ==========================================================================
   Lightbox
   ========================================================================== */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  visibility: hidden;
}
.lightbox.is-open { visibility: visible; }

.lightbox__backdrop {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--bg) 74%, transparent);
  -webkit-backdrop-filter: blur(24px) saturate(120%);
          backdrop-filter: blur(24px) saturate(120%);
  opacity: 0;
  border: 0;
  width: 100%;
  cursor: zoom-out;
}
@supports not (backdrop-filter: blur(1px)) {
  .lightbox__backdrop { background: color-mix(in srgb, var(--bg) 94%, transparent); }
}

.lightbox__figure {
  position: absolute;
  inset: 0;
  /* flex, not grid: as a grid item the image's max-height:100% had no
     definite row height to resolve against, so it was dropped entirely and
     tall artwork ran off the bottom of the screen. */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(56px, 8vh, 92px) clamp(48px, 7vw, 110px);
  pointer-events: none;
}

.lightbox__img {
  /* Viewport units rather than percentages: these resolve no matter how the
     container sizes itself, so the artwork is always fully on screen. They
     also leave clearance for the close button, arrows and counter. */
  max-width: 84vw;
  max-height: 76vh;
  max-height: 76svh;
  width: auto;
  height: auto;
  object-fit: contain;
  will-change: transform, opacity;
  transform-origin: 50% 50%;
}

@media (max-width: 620px) {
  .lightbox__img { max-width: 88vw; max-height: 70svh; }
}

.lightbox__btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  color: var(--fg);
  cursor: pointer;
  opacity: 0;
  transition: background-color 0.3s var(--ease), color 0.3s var(--ease),
              border-color 0.3s var(--ease), transform 0.3s var(--ease);
}
.lightbox__btn:hover {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
}
.lightbox__btn svg { width: 17px; height: 17px; display: block; }

.lightbox__close { top: clamp(16px, 3vh, 30px); right: clamp(16px, 3vw, 34px); }
.lightbox__prev  { left: clamp(12px, 2.4vw, 30px);  top: 50%; margin-top: -24px; }
.lightbox__next  { right: clamp(12px, 2.4vw, 30px); top: 50%; margin-top: -24px; }
.lightbox__prev:hover { transform: translateX(-3px); }
.lightbox__next:hover { transform: translateX(3px); }

.lightbox__count {
  position: absolute;
  left: 50%;
  bottom: clamp(18px, 3.4vh, 34px);
  transform: translateX(-50%);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  opacity: 0;
  pointer-events: none;
}

body.lightbox-open { overflow: hidden; }

@media (max-width: 620px) {
  .lightbox__figure { padding: 76px 16px 84px; }
  .lightbox__prev { left: 12px; }
  .lightbox__next { right: 12px; }
}

/* Project footer nav */
.project-nav {
  border-top: 1px solid var(--line);
  padding: clamp(28px, 5vh, 56px) 0 clamp(60px, 10vh, 120px);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 24px;
  flex-wrap: wrap;
}
.project-nav__label {
  font-size: 12px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 10px;
}
.project-nav__link {
  font-weight: 700;
  font-size: clamp(1.4rem, 3.4vw, 2.4rem);
  letter-spacing: -0.04em;
  line-height: 1;
  display: inline-block;
  transition: transform 0.45s var(--ease);
}
.project-nav__link:hover { transform: translateX(6px); }
.project-nav__prev .project-nav__link:hover { transform: translateX(-6px); }

/* ==========================================================================
   About
   ========================================================================== */
.about {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
  gap: clamp(28px, 5vw, 72px);
  align-items: start;
  padding-bottom: clamp(80px, 14vh, 160px);
}
@media (max-width: 860px) { .about { grid-template-columns: 1fr; } }

/* Original uncropped frame, capped so it never blows up. `height: auto` plus
   the intrinsic width/height attributes keep the aspect locked — it cannot
   stretch. The file itself is already black and white. */
.about__portrait {
  display: block;
  width: 100%;
  height: auto;
  max-width: 460px;
}
@media (max-width: 860px) {
  .about__portrait { max-width: 420px; }
}

.about__body p {
  font-size: clamp(1.05rem, 1.7vw, 1.4rem);
  line-height: 1.5;
  letter-spacing: -0.02em;
}
.about__body p + p { margin-top: 1.1em; }

.about__section { margin-top: clamp(36px, 6vh, 64px); }

.about__section-label {
  font-size: 12px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 6px;
}

/* Horizontal social icons (About) */
.socials {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(10px, 1.4vw, 16px);
  margin-top: clamp(28px, 5vh, 44px);
}

.socials__link {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--fg);
  flex: none;
  transition: background-color 0.35s var(--ease), color 0.35s var(--ease),
              border-color 0.35s var(--ease), transform 0.35s var(--ease);
}
.socials__link svg { width: 19px; height: 19px; display: block; }
.socials__link:hover {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
  transform: translateY(-2px);
}

/* Link list — used by Contact */
.links { display: flex; flex-direction: column; }

.links__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding: clamp(14px, 2vh, 20px) 0;
  border-bottom: 1px solid var(--line);
  transition: padding-left 0.45s var(--ease);
}
.links__row:hover { padding-left: 12px; }

.links__name {
  font-weight: 700;
  font-size: clamp(1.15rem, 2.4vw, 1.75rem);
  letter-spacing: -0.035em;
}
.links__value {
  font-size: 13px;
  color: var(--muted);
  text-align: right;
  word-break: break-word;
  transition: color 0.35s var(--ease);
}
.links__row:hover .links__value { color: var(--fg); }

/* ==========================================================================
   Contact
   ========================================================================== */
.contact {
  max-width: 760px;
  padding-bottom: clamp(80px, 14vh, 160px);
}

.contact__copy { margin-top: clamp(24px, 4vh, 36px); }

.copy-btn {
  border: 1px solid var(--line);
  background: transparent;
  border-radius: 999px;
  padding: 7px 15px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: border-color 0.3s var(--ease), background-color 0.3s var(--ease);
}
.copy-btn:hover { border-color: var(--fg); }
.copy-btn.is-done { background: var(--fg); color: var(--bg); border-color: var(--fg); }

/* ==========================================================================
   Footer
   ========================================================================== */
.footer {
  border-top: 1px solid var(--line);
  padding: clamp(26px, 4vh, 40px) 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
  font-size: 12.5px;
  color: var(--muted);
}
.footer a { transition: color 0.3s var(--ease); }
.footer a:hover { color: var(--fg); }
.footer__social { display: flex; gap: 20px; }

/* ==========================================================================
   Intro overlay
   ========================================================================== */
.intro {
  position: fixed;
  inset: 0;
  z-index: 999;
  background: var(--bg);
  overflow: hidden;
  display: grid;
  place-items: center;
}
.intro[hidden] { display: none; }

.intro__stage { position: absolute; inset: 0; pointer-events: none; }

.intro__img {
  position: absolute;
  top: 50%; left: 50%;
  width: clamp(96px, 14vw, 210px);
  height: auto;
  margin: 0;
  opacity: 0;
  will-change: transform, opacity;
  transform-origin: 50% 50%;
  filter: drop-shadow(0 20px 44px rgba(0, 0, 0, 0.6));
}

.intro__mark {
  position: relative;
  z-index: 3;
  font-weight: 700;
  font-size: clamp(3.4rem, 15vw, 12rem);
  line-height: 0.84;
  letter-spacing: -0.06em;
  overflow: hidden;
  display: block;
}
/* Direct child only — the W inside it has to stay inline. */
.intro__mark > span { display: block; will-change: transform; }
/* Light W against a bold H, matching the header monogram and the masthead.
   Keeps the mark's own tighter tracking rather than .name-light's. */
.intro__mark .name-light { letter-spacing: inherit; }

.intro__meta {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: clamp(24px, 5vh, 48px);
  z-index: 3;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 24px;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.intro__ticker {
  height: 1.35em;
  overflow: hidden;
  min-width: 0;
}
.intro__ticker span {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.intro__count { font-variant-numeric: tabular-nums; color: var(--fg); }

.intro__rule {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: calc(clamp(24px, 5vh, 48px) + 2.6em);
  height: 1px;
  background: var(--line);
  z-index: 3;
  transform: scaleX(0);
  transform-origin: left;
}

/* lock scroll while intro runs */
body.intro-lock { overflow: hidden; height: 100vh; }

/* ==========================================================================
   Motion preferences
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .grid__item { opacity: 1; transform: none; }
}
