@import url('fonts.css');

/* ==========================================================================
   PALY — design tokens
   Near-black with a violet undertone. One accent only; everything else is
   greyscale so the work supplies the rest of the colour.
   ========================================================================== */

:root {
  --bg: #0a0713;
  --bg-panel: #120e1e;
  --bg-raise: #171226;
  --hairline: rgba(198, 168, 255, 0.11);
  --hairline-soft: rgba(198, 168, 255, 0.06);

  --text: #f2eef8;
  --muted: #928ba3;
  --faint: #5b5470;

  --accent: #b06bff;
  --accent-2: #7d5cff;
  --on-accent: #0a0713;

  --serif: 'Instrument Serif', 'Times New Roman', serif;
  --sans: 'Inter Tight', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', monospace;

  --gutter: clamp(20px, 5vw, 72px);
  --maxw: 1440px;
  --section-y: clamp(88px, 13vh, 168px);

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 0.7s;
}

/* Required by Lenis (assets/js/vendor/lenis.min.js) */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }
.lenis.lenis-smooth iframe { pointer-events: none; }

/* ==========================================================================
   Reset
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* On html, not body, so .ambient (z-index:-1) still has something to sit on. */
  background: var(--bg);
}

body {
  margin: 0;
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

body.is-locked { overflow: hidden; }

img, video { display: block; max-width: 100%; }
h1, h2, h3, h4, p, figure, ul { margin: 0; }
ul { padding: 0; list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

a {
  color: inherit;
  text-decoration: none;
  transition: color 0.35s var(--ease), opacity 0.35s var(--ease);
}

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

::selection { background: var(--accent); color: var(--on-accent); }

/* ==========================================================================
   Layout primitives
   ========================================================================== */

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

.section { padding-block: var(--section-y); }
.section + .section { border-top: 1px solid var(--hairline-soft); }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  padding: 12px 18px;
  background: var(--text);
  color: var(--bg);
  z-index: 200;
}
.skip:focus { left: 12px; top: 12px; }

/* Ambient — two soft violet glows drifting behind everything, so the flat black
   reads as a lit room rather than a void. */
.ambient {
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  overflow: hidden;
}
.ambient::before,
.ambient::after {
  content: '';
  position: absolute;
  width: 70vmax;
  height: 70vmax;
  border-radius: 50%;
  will-change: transform;
}
.ambient::before {
  top: -26vmax;
  left: -18vmax;
  opacity: 0.16;
  background: radial-gradient(circle closest-side, var(--accent), transparent 70%);
  animation: drift-a 38s ease-in-out infinite alternate;
}
.ambient::after {
  bottom: -32vmax;
  right: -20vmax;
  opacity: 0.13;
  background: radial-gradient(circle closest-side, var(--accent-2), transparent 70%);
  animation: drift-b 52s ease-in-out infinite alternate;
}
@keyframes drift-a { to { transform: translate3d(11vw, 7vh, 0) scale(1.16); } }
@keyframes drift-b { to { transform: translate3d(-9vw, -6vh, 0) scale(1.1); } }

/* Stars — two seamlessly tiling fields drawn once into data URLs by initStars()
   in app.js, then drifted with background-position so nothing runs per frame. */
.stars {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.6s ease;
}
.stars.is-on { opacity: 1; }
.stars span {
  position: absolute;
  inset: 0;
  background-repeat: repeat;
  will-change: background-position;
}
/* Drift distances are whole multiples of the tile so the loop never jumps. */
.stars .far {
  background-size: 400px 400px;
  animation: drift-far 300s linear infinite,
             twinkle 11s ease-in-out infinite alternate;
}
.stars .near {
  background-size: 520px 520px;
  animation: drift-near 185s linear infinite,
             twinkle 7s ease-in-out infinite alternate-reverse;
}
@keyframes drift-far { to { background-position: 800px -1200px; } }
@keyframes drift-near { to { background-position: -520px -1040px; } }
@keyframes twinkle { from { opacity: 0.7; } to { opacity: 1; } }

/* Grain — a single fixed SVG-noise layer over everything */
.grain {
  position: fixed;
  inset: -50%;
  z-index: 90;
  pointer-events: none;
  opacity: 0.038;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ==========================================================================
   Typography
   ========================================================================== */

.eyebrow {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

.display {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 0.94;
  letter-spacing: -0.025em;
}

.display em { font-style: italic; }

.lede {
  font-size: clamp(1.05rem, 1.5vw, 1.3rem);
  line-height: 1.5;
  color: var(--muted);
  max-width: 46ch;
}

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  padding-bottom: 22px;
  margin-bottom: 46px;
  border-bottom: 1px solid var(--hairline);
}

.section-head h2 {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.9rem, 4vw, 3.2rem);
  line-height: 1;
  letter-spacing: -0.02em;
}

.section-count {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  color: var(--faint);
  white-space: nowrap;
}

/* ==========================================================================
   Buttons / links
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 13px 22px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text);
  transition: border-color 0.4s var(--ease), background 0.4s var(--ease),
              color 0.4s var(--ease);
}
.btn:hover { border-color: var(--accent); background: var(--accent); color: var(--on-accent); }
.btn--solid { background: var(--text); color: var(--on-accent); border-color: var(--text); }
.btn--solid:hover { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }

.link-u {
  position: relative;
  display: inline-block;
  padding-bottom: 2px;
}
.link-u::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.5s var(--ease);
}
.link-u:hover::after { transform: scaleX(1); transform-origin: left; }

/* ==========================================================================
   Nav
   ========================================================================== */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 80;
  transition: background 0.5s var(--ease), backdrop-filter 0.5s var(--ease),
              border-color 0.5s var(--ease);
  border-bottom: 1px solid transparent;
}
.nav.is-stuck {
  background: rgba(10, 7, 19, 0.72);
  backdrop-filter: blur(14px) saturate(140%);
  border-bottom-color: var(--hairline-soft);
}

.nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 76px;
}

.brand {
  font-family: var(--serif);
  font-size: 1.35rem;
  letter-spacing: 0.08em;
}
.brand:hover { color: var(--accent); }

.nav-links {
  display: flex;
  gap: 34px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.nav-links a:hover { color: var(--accent); }

.nav-toggle { display: none; }

/* ==========================================================================
   Hero
   ========================================================================== */

/* The cut-out is the backdrop; the copy layers on top of it. The figure is
   bottom-anchored and tall enough that the head clears the copy, so the wordmark
   crosses the torso rather than the face. Nothing pads the figure's bottom edge,
   so its fade runs straight into the work section and the page reads as one
   continuous background. */
.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  align-items: end;
  padding-block: clamp(92px, 12vh, 116px) clamp(38px, 6vh, 68px);
}

.hero-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
/* The copy sits over the figure's torso, which is mid-tone — without this the
   eyebrow and bio lose contrast. z-index:-1 stays inside .hero-inner's stacking
   context, so it darkens the photo but still sits under the text. */
.hero-inner::before {
  content: '';
  position: absolute;
  z-index: -1;
  left: 50%;
  top: -8%;
  transform: translateX(-50%);
  width: min(1080px, 96vw);
  height: 116%;
  pointer-events: none;
  background: radial-gradient(58% 50% at 50% 50%,
    rgba(10, 7, 19, 0.74), rgba(10, 7, 19, 0.34) 62%, transparent 82%);
}
/* Brighter than the section eyebrows: this one is over a photo. */
.hero-eyebrow { color: var(--muted); }

.hero-eyebrow {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: clamp(18px, 3vh, 34px);
}
.hero-eyebrow .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 12px var(--accent);
  animation: pulse 2.6s ease-in-out infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }

/* The cut-out. Source is 2:3 with a real alpha channel — the lighting pass below
   is masked by that alpha, so keep any replacement cut out too.
   Taken out of flow and pinned to the bottom so the copy can sit over it.
   Height is what matters: the figure has to be tall enough that its head (the top
   ~31% of the cut-out) clears the top of the copy, or the wordmark lands on the
   face. 76vh satisfies that at every viewport tested. The 132vw bound only bites
   on a narrow phone, where it caps the width at ~88vw instead of overflowing. */
.hero-figure {
  position: absolute;
  z-index: 0;
  left: 50%;
  bottom: 0;
  height: clamp(320px, min(76vh, 132vw), 860px);
  aspect-ratio: 2 / 3;
  /* The bottom third dissolves into the page instead of ending on a hard edge. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 44%, rgba(0, 0, 0, 0.45) 74%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 44%, rgba(0, 0, 0, 0.45) 74%, transparent 100%);
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  opacity: 0;
  /* translateX(-50%) is the centring, so it has to survive the reveal. */
  transform: translate(-50%, 26px);
  transition: opacity 1.2s var(--ease) 0.55s, transform 1.2s var(--ease) 0.55s;
}
.hero.is-in .hero-figure { opacity: 1; transform: translate(-50%, 0); }

.hero-figure img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: saturate(1.06) contrast(1.04);
}
/* Ambient bounce behind the figure — a light source in the room, not an outline.
   A drop-shadow was tried here and reads as a soft rectangle: the silhouette is
   a broad seated mass, so a wide blur around it has no shape to follow.
   z-index:-1 stays inside the figure, which mask-image already makes a stacking
   context; the gradient dies well before the box edges so the mask never clips
   a visible line. */
.hero-figure::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(58% 42% at 50% 34%, rgba(176, 107, 255, 0.2), transparent 72%);
}
/* Lighting pass. A violet key from upper-left plus a cooler bounce from below,
   painted only where the cut-out has pixels — mask-image reuses the same file,
   and mask-size:contain lines it up with the img's object-fit:contain.
   mask-image on .hero-figure makes a stacking context, so this screen blend
   composites against the photo and never touches the page background. */
.hero-figure::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* Alphas stay low: the photo is dark, and a screen blend over dark pixels is
     dominated by whatever you put on top — 0.4 turned the figure into a violet
     silhouette. These are ambient light, not a repaint. */
  background:
    linear-gradient(196deg, rgba(176, 107, 255, 0.15) 0%, rgba(125, 92, 255, 0.06) 42%, transparent 66%),
    linear-gradient(0deg, rgba(125, 92, 255, 0.12) 0%, transparent 34%);
  mix-blend-mode: screen;
  -webkit-mask-image: url(../img/paly.webp);
          mask-image: url(../img/paly.webp);
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}

.hero-word {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(3.6rem, 13.5vw, 10.5rem);
  line-height: 0.78;
  letter-spacing: 0.02em;
}
.hero-word .line { display: block; overflow: hidden; padding-bottom: 0.06em; }
.hero-word .line > span {
  display: block;
  transform: translateY(105%);
  transition: transform 1.2s var(--ease);
}
.hero.is-in .hero-word .line > span { transform: translateY(0); }

.hero-bio {
  margin-top: clamp(14px, 2.4vh, 26px);
  max-width: 52ch;
  font-size: clamp(0.98rem, 1.35vw, 1.15rem);
  color: var(--muted);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.9s var(--ease) 0.35s, transform 0.9s var(--ease) 0.35s;
}
.hero-actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: clamp(22px, 3.4vh, 38px);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.9s var(--ease) 0.48s, transform 0.9s var(--ease) 0.48s;
}
.hero.is-in .hero-bio,
.hero.is-in .hero-actions { opacity: 1; transform: none; }

/* --------------------------------------------------------------------------
   Hero → work hand-off
   -------------------------------------------------------------------------- */
/* Scroll-driven, so the junction tracks the scroll position instead of firing
   once and being done: the hero copy lifts and dissolves as it leaves the top
   of the screen while the work header rises to take its place. All compositor
   work — no scroll listener, nothing per frame in JS.
   Behind @supports because scroll-driven timelines aren't everywhere yet;
   without them the hero simply scrolls away and the work header keeps the
   IntersectionObserver reveal it shares with every other section. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* The `exit` range is the element sliding off the top of the viewport, so
       opacity only reaches 0 once the copy is genuinely gone. Fading it over a
       fixed scroll distance instead would leave the two buttons invisible but
       still on screen and still clickable. */
    .hero-inner {
      animation: hero-copy-exit linear both;
      animation-timeline: view();
      animation-range: exit 0% exit 92%;
      will-change: opacity, transform;
    }
    @keyframes hero-copy-exit {
      to { opacity: 0; transform: translateY(-56px); }
    }

    /* Timeline is the whole section rather than the header itself: the header
       is ~74px tall, so its own `entry` phase is only ~60px of scroll and the
       rise snaps. #work is ~900px, which gives the move room to breathe.
       These two also carry .reveal — the animation's `both` fill wins over the
       transition, so the scroll version supersedes it rather than fighting it. */
    #work {
      view-timeline-name: --work;
      view-timeline-axis: block;
    }
    /* The `animation` shorthand resets animation-timeline and animation-range,
       so both always have to be declared after it — hence the split. */
    #work .section-head,
    #work .filters {
      animation: work-rise linear both;
      animation-timeline: --work;
    }
    #work .section-head { animation-range: entry 10% entry 44%; }
    #work .filters      { animation-range: entry 18% entry 54%; }
    @keyframes work-rise {
      from { opacity: 0; transform: translateY(44px); }
      to   { opacity: 1; transform: none; }
    }
  }
}

/* ==========================================================================
   Reel
   ========================================================================== */

.reel-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--bg-panel);
  border: 1px solid var(--hairline);
  overflow: hidden;
  cursor: pointer;
}
.reel-frame video,
.reel-frame img { width: 100%; height: 100%; object-fit: cover; }

.reel-frame .placeholder-art { position: absolute; inset: 0; }

.reel-play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(10, 7, 19, 0.32);
  transition: opacity 0.5s var(--ease), background 0.5s var(--ease);
}
.reel-frame.is-playing .reel-play { opacity: 0; pointer-events: none; }

.reel-play span {
  display: grid;
  place-items: center;
  width: clamp(76px, 9vw, 116px);
  aspect-ratio: 1;
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 50%;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  backdrop-filter: blur(6px);
  transition: background 0.45s var(--ease), color 0.45s var(--ease),
              border-color 0.45s var(--ease);
}
.reel-frame:hover .reel-play span {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
}

/* ==========================================================================
   Filters
   ========================================================================== */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 40px;
}

.chip {
  padding: 9px 16px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color 0.35s var(--ease), border-color 0.35s var(--ease),
              background 0.35s var(--ease);
}
.chip:hover { color: var(--text); border-color: var(--accent); }
.chip[aria-pressed='true'] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.chip .n { opacity: 0.5; margin-left: 7px; }

/* ==========================================================================
   Work grid
   ========================================================================== */

.grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(28px, 4vw, 56px) clamp(20px, 2.6vw, 40px);
}

.card {
  display: block;
  cursor: pointer;
  text-align: left;
  width: 100%;
}
.card[hidden] { display: none; }

.card-media {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--bg-panel);
  border: 1px solid var(--hairline-soft);
}
.card-media > img,
.card-media > video,
.card-media > .placeholder-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.2s var(--ease), opacity 0.6s var(--ease);
}
.card:hover .card-media > img,
.card:hover .card-media > .placeholder-art { transform: scale(1.035); }

.card-media > video { opacity: 0; }
.card.is-previewing .card-media > video { opacity: 1; }

.card-idx {
  position: absolute;
  top: 14px;
  left: 16px;
  z-index: 2;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  color: rgba(255, 255, 255, 0.55);
  mix-blend-mode: difference;
}

.card-body {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding-top: 18px;
}

.card-title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.35rem, 2.1vw, 1.85rem);
  line-height: 1.1;
  letter-spacing: -0.015em;
  transition: color 0.4s var(--ease);
}
.card:hover .card-title { color: var(--accent); }
.card-client { font-size: 0.9rem; color: var(--muted); margin-top: 3px; }
.card-blurb {
  font-size: 0.9rem;
  color: var(--faint);
  margin-top: 10px;
  max-width: 42ch;
}

.card-meta {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  text-align: right;
  white-space: nowrap;
}

.grid-empty {
  padding: 60px 0;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}

/* Cursor-following hover label over work media */
.hover-tag {
  position: fixed;
  z-index: 95;
  top: 0;
  left: 0;
  padding: 8px 14px;
  border-radius: 999px;
  background: var(--text);
  color: var(--bg);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.85);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.hover-tag.is-on { opacity: 1; transform: translate(-50%, -50%) scale(1); }

/* Typographic fallback used wherever real media is not in yet */
.placeholder-art {
  display: grid;
  place-items: center;
  background:
    radial-gradient(90% 120% at 22% 8%, rgba(176, 107, 255, 0.16), transparent 62%),
    linear-gradient(150deg, #191231 0%, #0c0916 55%, #140f26 100%);
}
.placeholder-art b {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(3rem, 9vw, 7rem);
  line-height: 1;
  letter-spacing: -0.04em;
  color: rgba(255, 255, 255, 0.09);
  user-select: none;
}

/* ==========================================================================
   Clients marquee
   ========================================================================== */

.marquee {
  overflow: hidden;
  padding-block: clamp(44px, 7vh, 84px);
  border-top: 1px solid var(--hairline-soft);
  border-bottom: 1px solid var(--hairline-soft);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.marquee-track {
  display: flex;
  width: max-content;
  gap: 56px;
  animation: slide 52s linear infinite;
}
.marquee:hover .marquee-track { animation-play-state: paused; }
.marquee-track span {
  font-family: var(--serif);
  font-size: clamp(1.4rem, 3vw, 2.4rem);
  color: var(--faint);
  white-space: nowrap;
  transition: color 0.4s var(--ease);
}
.marquee-track span:hover { color: var(--accent); }
@keyframes slide { to { transform: translateX(-50%); } }

/* ==========================================================================
   About
   ========================================================================== */

.about-body p + p { margin-top: 1.1em; }
.about-body p {
  font-size: clamp(1rem, 1.25vw, 1.12rem);
  color: var(--muted);
  max-width: 56ch;
}
.about-body p:first-child {
  color: var(--text);
  font-size: clamp(1.15rem, 1.9vw, 1.5rem);
  font-family: var(--serif);
  line-height: 1.25;
  max-width: 26ch;
}

.facts {
  margin: clamp(28px, 4vh, 40px) 0 0;
  max-width: 56ch;
  border-top: 1px solid var(--hairline);
}
.facts > div {
  display: grid;
  grid-template-columns: 92px 1fr;
  gap: 16px;
  padding: 12px 0;
  border-bottom: 1px solid var(--hairline-soft);
}
.facts dt {
  padding-top: 3px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}
.facts dd { margin: 0; font-size: 0.95rem; }

.stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
  margin-top: clamp(36px, 5vh, 56px);
  padding-top: 30px;
  border-top: 1px solid var(--hairline);
}
.stat b {
  display: block;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.7rem, 3.4vw, 2.9rem);
  line-height: 1;
  letter-spacing: -0.02em;
}
.stat span {
  display: block;
  margin-top: 9px;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--faint);
}

/* ==========================================================================
   Contact
   ========================================================================== */

.contact { text-align: center; }
.contact .eyebrow { margin-bottom: 26px; }

.contact-mail:hover { color: var(--accent); }
.contact-mail {
  display: inline-block;
  font-family: var(--serif);
  font-size: clamp(1.9rem, 7vw, 5.4rem);
  line-height: 1;
  letter-spacing: -0.03em;
  word-break: break-word;
}

.contact-note {
  margin-top: 26px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--faint);
}

.contact-actions {
  display: flex;
  justify-content: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 40px;
}

.foot {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  padding-block: 30px;
  border-top: 1px solid var(--hairline-soft);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--faint);
}

/* ==========================================================================
   Project overlay (hash-routed)
   ========================================================================== */

.pv {
  position: fixed;
  inset: 0;
  z-index: 120;
  background: var(--bg);
  overflow-y: auto;
  overscroll-behavior: contain;
  opacity: 0;
  visibility: hidden;
  transform: translateY(18px);
  transition: opacity 0.5s var(--ease), transform 0.6s var(--ease),
              visibility 0.5s;
}
.pv.is-open { opacity: 1; visibility: visible; transform: none; }

.pv-close {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  justify-content: flex-end;
  padding: 20px var(--gutter);
  background: linear-gradient(to bottom, var(--bg) 55%, transparent);
}
.pv-close button {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.pv-close button:hover { color: var(--text); }

.pv-inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding: 0 var(--gutter) clamp(60px, 12vh, 120px);
}

.pv-title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.2rem, 6.5vw, 5rem);
  line-height: 0.98;
  letter-spacing: -0.03em;
  margin-block: 8px 14px;
}
.pv-client { font-size: 1.05rem; color: var(--muted); }

.pv-media {
  position: relative;
  aspect-ratio: 16 / 9;
  margin-top: clamp(32px, 5vh, 56px);
  background: var(--bg-panel);
  border: 1px solid var(--hairline-soft);
  overflow: hidden;
}
.pv-media video,
.pv-media img,
.pv-media .placeholder-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.pv-cols {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: clamp(28px, 5vw, 72px);
  margin-top: clamp(36px, 6vh, 64px);
  padding-top: 34px;
  border-top: 1px solid var(--hairline);
}
.pv-cols p { font-size: 1.05rem; color: var(--muted); max-width: 60ch; }

.pv-facts { display: grid; gap: 18px; align-content: start; }
.pv-fact dt {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 5px;
}
.pv-fact dd { margin: 0; font-size: 0.95rem; }

.pv-tools { display: flex; flex-wrap: wrap; gap: 7px; }
.pv-tools li {
  padding: 6px 12px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.11em;
  color: var(--muted);
}

/* ==========================================================================
   Scroll reveal
   ========================================================================== */

.reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.reveal.is-in { opacity: 1; transform: none; }

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width: 900px) {
  .grid { grid-template-columns: 1fr; }
  .stats { grid-template-columns: repeat(2, 1fr); }
  .hero-word { font-size: clamp(4rem, 21vw, 11rem); }
  .pv-cols { grid-template-columns: 1fr; }
  .card-body { flex-direction: column; gap: 8px; }
  .card-meta { text-align: left; }
}

@media (max-width: 640px) {
  .nav-links {
    position: fixed;
    inset: 76px 0 auto 0;
    flex-direction: column;
    gap: 0;
    padding: 8px var(--gutter) 22px;
    background: rgba(10, 7, 19, 0.96);
    backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--hairline);
    transform: translateY(-12px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.35s var(--ease), transform 0.35s var(--ease), visibility 0.35s;
  }
  .nav-links a { padding-block: 14px; border-bottom: 1px solid var(--hairline-soft); }
  .nav.is-open .nav-links { opacity: 1; visibility: visible; transform: none; }
  .nav.is-open { background: rgba(10, 7, 19, 0.9); backdrop-filter: blur(14px); }

  .nav-toggle {
    display: inline-flex;
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--muted);
  }

  .hover-tag { display: none; }
  .hero-word { font-size: clamp(3.6rem, 24vw, 8rem); }
}

/* ==========================================================================
   Reduced motion
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  .hero-word .line > span { transform: none; }
  .hero-bio, .hero-actions { opacity: 1; transform: none; }
  .hero-figure { opacity: 1; transform: translate(-50%, 0); }
}
