/* ============================
   Fonts
   ============================ */
/* Neue Montreal (Pangram Pangram) is a licensed font — not on Google Fonts.
   Copy these .otf files into assets/fonts/ (keep the exact names). Until they
   exist the stack falls back to Inter, so nothing breaks.
   .otf works fine; converting to .woff2 later just makes them smaller. */
@font-face {
  font-family: "Neue Montreal";
  src: url("assets/fonts/PPNeueMontreal-Hairline.otf") format("opentype");
  font-weight: 100 200;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("assets/fonts/PPNeueMontreal-Light.otf") format("opentype");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("assets/fonts/PPNeueMontreal-Regular.otf") format("opentype");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("assets/fonts/PPNeueMontreal-Semibold.otf") format("opentype");
  font-weight: 600 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Neue Montreal";
  src: url("assets/fonts/PPNeueMontreal-Extrabold.otf") format("opentype");
  font-weight: 800 900;
  font-style: normal;
  font-display: swap;
}

/* Eiko (Pangram Pangram) — display face, replaces Big Shoulders Display.
   Self-hosted from assets/fonts/. Falls back to a serif until it loads. */
@font-face {
  font-family: "Eiko";
  src: url("assets/fonts/PPEiko-Thin.otf") format("opentype");
  font-weight: 100 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Eiko";
  src: url("assets/fonts/PPEiko-Medium.otf") format("opentype");
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Eiko";
  src: url("assets/fonts/PPEiko-Heavy.otf") format("opentype");
  font-weight: 700 900;
  font-style: normal;
  font-display: swap;
}

/* ============================
   Tokens
   ============================ */
:root {
  --stage: #080706;
  --stage-raised: #121110;
  --tungsten: #E3A34E;
  --rim: #7C9CB3;
  --paper: #ECE7DE;
  --ash: #7A756D;
  --line: #201d1a;

  --display: "Eiko", Georgia, "Times New Roman", serif;
  --body: "Inter", sans-serif;
  --grotesk: "Neue Montreal", "Inter", sans-serif;

  --edge: clamp(1.25rem, 4vw, 4rem);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

body {
  margin: 0;
  background: var(--stage);
  color: var(--paper);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.6;
}

a {
  color: inherit;
}

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 600;
  line-height: 1;
  margin: 0;
}

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

/* ============================
   Header
   ============================ */
.site-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 1.25rem var(--edge);
  background: linear-gradient(to bottom, rgba(18,17,16,0.9), transparent);
}


.site-nav {
  display: flex;
  align-items: center;
  gap: 2rem;
}

.site-nav a:not(.nav-cta) {
  text-decoration: none;
  font-size: 0.9rem;
  color: var(--ash);
  transition: color 0.2s ease;
}

.site-nav a:not(.nav-cta):hover,
.site-nav a:not(.nav-cta):focus-visible {
  color: var(--tungsten);
}

/* Contact-me button: thin white outline by default, tungsten fill on hover
   (text stays white). script.js adds a faint magnetic pull toward the pointer. */
.nav-cta {
  text-decoration: none;
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  color: #fff;
  border: 1px solid #474747;
  border-radius: 5px;
  padding: 0.5rem 1.1rem;
  white-space: nowrap;
  transition: transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1),
              background-color 0.2s ease;
}

.nav-cta:hover,
.nav-cta:focus-visible {
  background-color: var(--tungsten);
  color: #fff;
  text-decoration: none;
}

.nav-toggle {
  display: none;
  flex-direction: column;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.5rem;
}

.nav-toggle span {
  width: 22px;
  height: 2px;
  background: var(--paper);
}

/* ============================
   Hero
   ============================ */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: stretch;
  justify-content: center;
  overflow: hidden;
  padding: clamp(5rem, 12vh, 8rem) var(--edge) 50px;
  background:
    radial-gradient(circle at 30% 20%, rgba(124,156,179,0.04), transparent 45%),
    var(--stage);
}

.hero-video {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--stage);
}

.hero-video #reel-player {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  transform: translate(-50%, -50%);
}

/* Vimeo iframe sized to always cover the hero, cropping rather than letterboxing */
.hero-video iframe {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100vw;
  height: 56.25vw;   /* 16:9 */
  min-height: 100%;
  min-width: 177.78vh; /* 16:9 */
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.hero-overlay {
  position: absolute;
  inset: 0;
  background: rgba(8, 7, 6, 0.5);
}

.hero-overlay::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(236, 231, 222, 0.09) 1px, transparent 1px);
  background-size: 4px 4px;
  mix-blend-mode: overlay;
}

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

.hero-content {
  position: relative;
  width: 100%;
  max-width: none;
  margin: 0;
  text-align: left;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 0;
}

/* Bottom-left meta block — small, justified, uppercase. Sits under the lockup
   and fades in (space reserved) once the lockup has risen. */
.hero-sub {
  align-self: flex-start;
  max-width: 32rem;
  margin: 2rem 0 0;
  color: var(--ash);
  font-size: 0.8rem;
  line-height: 1.5;
  letter-spacing: 0.045em;
  text-align: justify;
  text-transform: uppercase;
  transition: opacity 0.7s ease;
}

.js .hero-sub:not(.is-in) {
  opacity: 0;
}

/* Social links, below the meta block. Fade up once everything has typed in. */
.hero-links {
  align-self: flex-start;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin: 1.75rem 0 0;
  padding: 0;
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.js .hero-links:not(.is-in) {
  opacity: 0;
  transform: translateY(10px);
}

.hero-links a {
  text-decoration: none;
  color: var(--ash);
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: color 0.2s ease;
}

.hero-links a:hover,
.hero-links a:focus-visible {
  color: var(--tungsten);
}

/* Name + title lockup. Starts dropped down over the (invisible) meta block,
   then slides up once it has finished typing. */
.hero-identity {
  align-self: flex-start;
  transition: transform 0.7s cubic-bezier(0.2, 0.7, 0.2, 1);
  will-change: transform;
}

.hero-title {
  display: inline-block;
  position: relative;
  margin: 0;
  text-transform: uppercase;
  line-height: 1;
}

/* Thin, wide-tracked name over a heavy title — same grotesque family. */
.hero-name {
  display: block;
  font-family: var(--grotesk);
  font-weight: 100;
  font-size: clamp(1.5rem, 5.7vw, 4.7rem);
  letter-spacing: 0.08em;
  line-height: 1;
  color: var(--paper);
}

.hero-title strong {
  display: block;
  font-family: var(--grotesk);
  font-weight: 800; /* ~10% thinner than the former 900 */
  font-size: clamp(1.55rem, 6vw, 5rem);
  letter-spacing: -0.01em;
  line-height: 0.92;
  color: #fff;
  margin-top: 0.05em;
}

/* Cursor spotlight over the hero display lockup + meta paragraph. script.js adds
   .spotlight on desktop (motion allowed) and .spot-on the first time the pointer
   crosses the hero — after that it stays on, so the pool just freezes in place
   when the cursor moves away. --spot-x / --spot-y are the pointer's position
   relative to each element's own box; the gradient is painted through the text,
   bright under the cursor, falling off around it. */
.hero.spotlight.spot-on :is(.hero-title, .hero-sub) {
  background-image: radial-gradient(
    circle 260px at var(--spot-x, 50%) var(--spot-y, 50%),
    #ffffff 0%,
    #ffffff 22%,
    rgba(236, 231, 222, 0.34) 82%
  );
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

/* ============================
   Section shell
   ============================ */
section {
  padding: 6rem var(--edge);
  border-top: 1px solid var(--line);
}

.section-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 3rem;
  text-align: center;
}

.section-head h2 {
  font-size: clamp(1.75rem, 4vw, 2.5rem);
  color: var(--tungsten);
}

/* ============================
   Section parallax
   ============================ */
/* .parallax is added by script.js to the content sections. --sy is the
   scroll-driven drift (px); the heading moves the full amount, the body a
   fraction of it, so they separate as you scroll. Children fade in on entry.
   Solid background + z-index keeps this plane BELOW the reel section, so its
   drifting content can never bleed up over the video. */
.parallax {
  --sy: 0px;
  position: relative;
  z-index: 0;
  background: var(--stage);
}

.parallax > * {
  opacity: 0;
  transform: translateY(calc(var(--sy) * 0.4));
  transition: opacity 0.7s ease;
}

.parallax > .section-head {
  transform: translateY(var(--sy));
}

.parallax.is-in > * {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .parallax > * {
    opacity: 1;
    transform: none;
  }
}

/* ---- About: the heading, paragraphs and skills stay pinned (position: sticky)
   while you scroll, so the About block feels static. The filmography is an
   opaque, full-bleed panel stacked above it that slides up over the pinned
   content as you keep scrolling. Paragraphs still scrub in one at a time
   (script.js); skills rows and filmography posters stagger in via .reveal-in. ---- */
.about.parallax {
  padding-bottom: clamp(6rem, 16vh, 12rem);
}

.about-pinned {
  position: sticky;
  top: clamp(4.5rem, 12vh, 7rem);
  z-index: 1;
}

.about.parallax .about-pinned {
  transform: none; /* no scroll drift on a sticky element */
}

.about.parallax .about-body > p {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.about.parallax .about-body > p.reveal-in {
  opacity: 1;
  transform: none;
}

.about-skills {
  margin-top: clamp(3rem, 10vh, 7rem);
}

.about-skills h3 {
  font-size: 1.4rem;
  color: var(--tungsten);
  margin-bottom: 1.5rem;
  text-align: center;
}

.about.parallax .about-skills {
  opacity: 0;
  transform: translateY(44px);
  transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.about.parallax .about-skills.reveal-in {
  opacity: 1;
  transform: none;
}

/* the sliding panel — a full-bleed opaque cover that scrolls up over the pinned
   About content; the grid inside it stays centred. */
.about.parallax .filmography {
  position: relative;
  z-index: 2;
  opacity: 1;
  transform: none;
  max-width: none;
  margin-top: clamp(14rem, 55vh, 40rem); /* stays pinned this long before it slides over */
  margin-left: calc(var(--edge) * -1);
  margin-right: calc(var(--edge) * -1);
  padding: clamp(3.5rem, 10vh, 7rem) var(--edge) 0;
  background: var(--stage);
  box-shadow: 0 -22px 45px -20px rgba(0, 0, 0, 0.7);
}

.about.parallax .film-grid {
  max-width: 68rem;
  margin-left: auto;
  margin-right: auto;
}

.about.parallax .about-skills .tool-list li,
.about.parallax .film-item {
  opacity: 0;
  transition: opacity 0.5s ease;
}

.about.parallax .about-skills.reveal-in .tool-list li,
.about.parallax .filmography.reveal-in .film-item {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .about-pinned { position: static; }
  .about.parallax .about-body > p,
  .about.parallax .about-skills,
  .about.parallax .about-skills .tool-list li,
  .about.parallax .filmography,
  .about.parallax .film-item {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ============================
   Demo reel
   ============================ */
/* Fill the viewport so the next section doesn't peek in below it. */
.work {
  position: relative;
  z-index: 1; /* stay above the parallax sections below it */
  overflow: hidden;
  background: var(--stage);
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  /* 0 = section still below the fold, 1 = settled in view. script.js drives
     this on scroll; defaults to 1 so everything is visible without JS. */
  --reel-progress: 1;
}

.work > * {
  position: relative;
  z-index: 1;
}

/* Spotlight cone from the top, behind the video — rises and fades in with scroll. */
.work::before {
  content: "";
  position: absolute;
  z-index: 0;
  left: 50%;
  top: -12%;
  width: min(150vw, 1700px);
  height: 216%;
  pointer-events: none;
  background: radial-gradient(
    ellipse 70% 50% at 50% 0%,
    rgba(236, 231, 222, 0.33) 0%,
    rgba(202, 175, 124, 0.105) 40%,
    transparent 72%
  );
  opacity: var(--reel-progress);
  transform: translateX(-50%) translateY(calc((1 - var(--reel-progress)) * -90px));
  will-change: opacity, transform;
}

.work .section-head {
  transform: translateY(calc((1 - var(--reel-progress)) * 26px));
  opacity: calc(0.2 + var(--reel-progress) * 0.8);
}

.reel-embed {
  position: relative;
  width: 100%;
  max-width: 72rem;
  aspect-ratio: 16 / 9;
  border-radius: 2px;
  overflow: hidden;
  background: var(--stage-raised);
  border: 1px solid var(--line);
  transform: translateY(calc((1 - var(--reel-progress)) * 54px));
  opacity: calc(0.15 + var(--reel-progress) * 0.85);
  will-change: opacity, transform;
}

.reel-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.reel-breakdown {
  margin: 1.5rem 0 0;
  text-align: center;
  transform: translateY(calc((1 - var(--reel-progress)) * 54px));
  opacity: calc(0.15 + var(--reel-progress) * 0.85);
}

.reel-breakdown a {
  text-decoration: none;
  color: var(--ash);
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: color 0.2s ease;
}

.reel-breakdown a:hover,
.reel-breakdown a:focus-visible {
  color: var(--tungsten);
}

/* ============================
   About
   ============================ */
.about-body {
  max-width: 40rem;
  margin: 0 auto;
  text-align: center;
}

.about-lead {
  font-size: 1.2rem;
  color: var(--paper);
}

.about-body p:not(.about-lead) {
  color: var(--ash);
}

/* ============================
   Filmography
   ============================ */
.filmography {
  margin: 3.5rem auto 0;
  max-width: 68rem;
}

.filmography-headline {
  font-size: 1.4rem;
  color: var(--tungsten);
  margin-bottom: 1.5rem;
  text-align: center;
}

.film-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  border-top: 1px solid var(--line);
  border-left: 1px solid var(--line);
}

.film-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  padding: 1.25rem;
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  text-align: center;
}

.film-poster {
  position: relative;
  aspect-ratio: 2 / 3;
  border-radius: 2px;
  margin-bottom: 0.5rem;
  background: var(--stage-raised);
  overflow: hidden;
}

.film-poster img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.film-poster[data-tone="key"] {
  background: radial-gradient(circle at 30% 25%, rgba(227,163,78,0.18), var(--stage-raised) 70%);
}
.film-poster[data-tone="rim"] {
  background: radial-gradient(circle at 70% 75%, rgba(124,156,179,0.16), var(--stage-raised) 70%);
}
.film-poster[data-tone="mixed"] {
  background:
    radial-gradient(circle at 25% 20%, rgba(227,163,78,0.12), transparent 55%),
    radial-gradient(circle at 75% 80%, rgba(124,156,179,0.1), transparent 55%),
    var(--stage-raised);
}

.film-title {
  font-family: var(--display);
  font-size: 1.05rem;
  color: var(--paper);
}

.film-meta {
  font-size: 0.78rem;
  color: var(--tungsten);
}

.film-role {
  font-size: 0.85rem;
  color: var(--ash);
}

/* ============================
   Tools
   ============================ */
.tool-list {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  max-width: 40rem;
}

.tool-list li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  padding: 1rem 0;
  border-bottom: 1px solid var(--line);
  text-align: center;
}

.tool-name {
  font-family: var(--display);
  font-size: 1.1rem;
}

.tool-use {
  color: var(--ash);
  font-size: 0.9rem;
}

/* ============================
   Gallery (AI section) — full-width drag-to-browse filmstrip
   ============================ */
/* Fill the viewport so the strip owns the whole screen. */
.tools {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.gallery {
  /* Width of one item; neighbours peek in on either side. */
  --gi: clamp(17rem, 58vw, 54rem);
  margin-top: clamp(2rem, 6vh, 4rem);
}

.gallery-strip {
  /* Break out of the section's edge padding to span the whole window. */
  margin-inline: calc(var(--edge) * -1);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;          /* Firefox */
  -ms-overflow-style: none;       /* old Edge */
  cursor: grab;
  overscroll-behavior-x: contain;
  /* JS owns horizontal movement (needed for the seamless loop); the browser
     keeps vertical so the page still scrolls off a touch drag. */
  touch-action: pan-y;
}

.gallery-strip::-webkit-scrollbar { display: none; }
.gallery-strip.is-dragging { cursor: grabbing; }
.gallery-strip:focus-visible {
  outline: 2px solid var(--tungsten);
  outline-offset: -2px;
}

/* Big "Drag" circle that replaces the pointer over the strip. When the pointer
   leaves the strip it collapses — shrinking to the dot's size and fading to the
   dot's colour — so it reads as morphing into the site's regular cursor. */
.gallery-cursor {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 60;
  width: clamp(72px, 8vw, 108px);
  height: clamp(72px, 8vw, 108px);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #fff;
  color: var(--stage);
  font-family: var(--display);
  font-size: 0.9rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  pointer-events: none;
  opacity: 0;
  /* ~8px — the size of .cursor-dot — so it lands exactly where the dot picks up. */
  transform: translate(-50%, -50%) scale(0.074);
  transition: opacity 0.28s ease, transform 0.32s cubic-bezier(0.2, 0.7, 0.2, 1),
    background-color 0.28s ease;
}

/* Direction hints flanking the circle, just outside it. */
.gallery-cursor-arrow {
  position: absolute;
  top: 50%;
  width: 1em;
  height: 1em;
  transform: translateY(-50%);
  color: var(--tungsten);
}

.gallery-cursor-arrow--l { right: calc(100% + 0.5em); }
.gallery-cursor-arrow--r { left: calc(100% + 0.5em); }

.gallery-cursor span,
.gallery-cursor-arrow {
  transition: opacity 0.14s ease;
}

.gallery-cursor:not(.is-visible) span,
.gallery-cursor:not(.is-visible) .gallery-cursor-arrow {
  opacity: 0;
}

.gallery-cursor.is-visible {
  opacity: 1;
  background: var(--tungsten);
  transform: translate(-50%, -50%) scale(1);
}

.gallery-cursor.is-grabbing {
  transform: translate(-50%, -50%) scale(0.88);
}

/* Hide the OS pointer over the strip on devices that get the big cursor. */
@media (hover: hover) and (pointer: fine) {
  .gallery-strip,
  .gallery-strip.is-dragging { cursor: none; }
}

@media (hover: none), (pointer: coarse) {
  .gallery-cursor { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .gallery-cursor { transition: opacity 0.2s ease; }
}

.gallery-track {
  display: flex;
  gap: clamp(0.9rem, 3vw, 2.5rem);
  width: max-content;
  /* Pad so the first and last items can sit dead centre. */
  padding-inline: calc(50% - var(--gi) / 2);
}

.gallery-item {
  flex: 0 0 var(--gi);
  margin: 0;
  opacity: 0.3;
  transform: scale(0.9);
  transition: opacity 0.45s ease, transform 0.45s cubic-bezier(0.2, 0.7, 0.2, 1);
  -webkit-user-select: none;
  user-select: none;
}

.gallery-item.is-focused {
  opacity: 1;
  transform: none;
}

.gallery-media {
  aspect-ratio: 16 / 9;
  border: 1px solid var(--line);
  border-radius: 2px;
  overflow: hidden;
  /* Dimmed retro-TV "please stand by" test card. */
  background-color: #0b0a09;
  background-image: url("assets/work/standby.svg");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.gallery-info {
  margin-top: clamp(0.9rem, 2vw, 1.25rem);
  text-align: left;
  /* Hidden + dropped below by default; the focused item's heading rises up
     into place, and any heading leaving focus sinks back down and fades out. */
  opacity: 0;
  transform: translateY(2rem);
  transition: opacity 0.8s ease-in-out, transform 0.7s ease-in-out;
}

.gallery-item.is-focused .gallery-info {
  opacity: 1;
  transform: none;
}

.gallery-info h3 {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(1.15rem, 2.4vw, 1.6rem);
  color: var(--paper);
}

@media (max-width: 700px) {
  .gallery { --gi: min(82vw, 26rem); }
}

@media (prefers-reduced-motion: reduce) {
  .gallery-item { transform: none; transition: opacity 0.2s ease; }
  .gallery-item:not(.is-focused) { transform: none; }
  .gallery-info { transform: none; transition: opacity 0.2s ease; }
  .gallery-item.is-focused .gallery-info { transform: none; }
}

/* ============================
   Footer
   ============================ */
.site-footer {
  padding: clamp(4rem, 12vh, 7rem) var(--edge) clamp(2rem, 5vh, 3.5rem);
  background: var(--stage);
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: clamp(1.75rem, 4vh, 3rem);
}

.footer-cta {
  font-family: var(--display);
  font-size: clamp(1.15rem, 2.4vw, 1.9rem);
  letter-spacing: 0.01em;
  text-decoration: none;
  color: var(--paper);
  transition: color 0.2s ease;
}

.footer-cta:hover,
.footer-cta:focus-visible {
  color: var(--tungsten);
}

.footer-meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  color: var(--ash);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.footer-time time {
  font-variant-numeric: tabular-nums;
  color: var(--paper);
}

/* Toronto skyline line-art. The SVG fill is #201d1a — the same colour as the
   .footer-bar divider (--line) — so the skyline reads as a faint continuation
   of that hairline. The negative bottom margin cancels the footer's row gap so
   the skyline's ground line sits right on the divider. */
.footer-skyline {
  display: block;
  width: min(420px, 86%);
  height: auto;
  /* -1 * (footer row gap) closes the gap; the extra -2px overlaps the skyline's
     ground line onto the divider so there's no sub-pixel seam. */
  margin: 0.5rem auto calc(clamp(1.75rem, 4vh, 3rem) * -1 - 2px);
}

.footer-bar {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.25rem;
  width: 100%;
  max-width: 34rem;
  padding-top: clamp(1.5rem, 4vh, 2.5rem);
  border-top: 1px solid var(--line);
}

.footer-links {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1.75rem;
  margin: 0;
  padding: 0;
}

.footer-links a {
  text-decoration: none;
  color: var(--ash);
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  transition: color 0.2s ease;
}

.footer-links a:hover,
.footer-links a:focus-visible {
  color: var(--paper);
}

.footer-copy {
  color: var(--ash);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
}

/* Transient "address copied" confirmation for mailto: buttons */
.mail-hint {
  position: fixed;
  z-index: 60;
  transform: translate(-50%, -100%) translateY(6px);
  padding: 0.4rem 0.7rem;
  background: var(--stage-raised);
  border: 1px solid #474747;
  border-radius: 5px;
  color: var(--paper);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.mail-hint.is-below {
  transform: translate(-50%, 0) translateY(-6px);
}

.mail-hint.is-shown {
  opacity: 1;
  transform: translate(-50%, -100%) translateY(0);
}

.mail-hint.is-below.is-shown {
  transform: translate(-50%, 0) translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .mail-hint { transition: opacity 0.2s ease; }
}

/* ============================
   Animated hover underline
   ============================ */
/* One hairline that draws in from the left over 0.44s on hover/focus. */
.site-nav a:not(.nav-cta),
.hero-links a,
.reel-breakdown a,
.footer-cta,
.footer-links a {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  padding-bottom: 0.12em;
  transition: background-size 0.44s ease, color 0.2s ease;
}

.site-nav a:not(.nav-cta):hover,
.site-nav a:not(.nav-cta):focus-visible,
.hero-links a:hover,
.hero-links a:focus-visible,
.reel-breakdown a:hover,
.reel-breakdown a:focus-visible,
.footer-cta:hover,
.footer-cta:focus-visible,
.footer-links a:hover,
.footer-links a:focus-visible {
  background-size: 100% 1px;
}

@media (prefers-reduced-motion: reduce) {
  .site-nav a:not(.nav-cta),
  .hero-links a,
  .reel-breakdown a,
  .footer-cta,
  .footer-links a {
    transition: background-size 0.15s ease, color 0.2s ease;
  }
}

/* ============================
   Cursor invert-circle on links
   ============================ */
/* On hover the pointer becomes a tungsten disc and, where it overlaps the link,
   the text reads black — a spotlight that inverts just the circle's worth of the
   link. script.js feeds --inv-x / --inv-y (pointer position within the link). */
@media (hover: hover) and (pointer: fine) {
  .site-nav a:not(.nav-cta),
  .hero-links a,
  .reel-breakdown a,
  .footer-cta,
  .footer-links a {
    position: relative;
    /* Predictable box so the ::after copy lands on the same baseline as the
       real text (inline <a>s misplace an absolutely-positioned pseudo). */
    display: inline-block;
    vertical-align: top;
  }

  .site-nav a:not(.nav-cta)::after,
  .hero-links a::after,
  .reel-breakdown a::after,
  .footer-cta::after,
  .footer-links a::after {
    content: attr(data-invert-text);
    position: absolute;
    /* Roomy box so the 44px circle is never cut into a rounded rectangle by
       the pseudo's own edges — the matching padding keeps the text aligned. */
    inset: -2.4em -2.8em;
    padding: 2.4em 2.8em;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    white-space: nowrap;
    color: #000;
    /* Tungsten fill + a black underline that draws in with the link's own,
       so inside the circle the underline reads inverted too. */
    background-image:
      linear-gradient(#000, #000),
      linear-gradient(var(--tungsten), var(--tungsten));
    background-repeat: no-repeat;
    background-origin: content-box, padding-box;
    background-position: left bottom, left top;
    background-size: 0% 1px, 100% 100%;
    pointer-events: none;
    z-index: 4;
    clip-path: circle(0px at calc(var(--inv-x, 50%) + 2.8em) calc(var(--inv-y, 50%) + 2.4em));
    transition: background-size 0.44s ease;
  }

  .site-nav a:not(.nav-cta):hover::after,
  .site-nav a:not(.nav-cta):focus-visible::after,
  .hero-links a:hover::after,
  .hero-links a:focus-visible::after,
  .reel-breakdown a:hover::after,
  .reel-breakdown a:focus-visible::after,
  .footer-cta:hover::after,
  .footer-cta:focus-visible::after,
  .footer-links a:hover::after,
  .footer-links a:focus-visible::after {
    background-size: 100% 1px, 100% 100%;
    clip-path: circle(22px at calc(var(--inv-x, 50%) + 2.8em) calc(var(--inv-y, 50%) + 2.4em));
  }

  /* Contact-me pill: same invert circle. Its own padding is folded into the
     ::after padding so the black "Contact me" copy sits over the real text. */
  .nav-cta { position: relative; }

  .nav-cta::after {
    content: attr(data-invert-text);
    position: absolute;
    inset: -2.4em -2.8em;
    padding: calc(2.4em + 0.5rem) calc(2.8em + 1.1rem);
    font: inherit;
    letter-spacing: inherit;
    white-space: nowrap;
    color: #000;
    background: var(--tungsten);
    pointer-events: none;
    z-index: 4;
    clip-path: circle(0px at calc(var(--inv-x, 50%) + 2.8em) calc(var(--inv-y, 50%) + 2.4em));
  }

  .nav-cta:hover::after,
  .nav-cta:focus-visible::after {
    clip-path: circle(22px at calc(var(--inv-x, 50%) + 2.8em) calc(var(--inv-y, 50%) + 2.4em));
  }

  /* Black border shown only through the cursor circle. */
  .nav-cta::before {
    content: "";
    position: absolute;
    inset: -1px;
    border: 1px solid #000;
    border-radius: 5px;
    pointer-events: none;
    z-index: 5;
    clip-path: circle(0px at calc(var(--inv-x, 50%) + 1px) calc(var(--inv-y, 50%) + 1px));
  }

  .nav-cta:hover::before,
  .nav-cta:focus-visible::before {
    clip-path: circle(22px at calc(var(--inv-x, 50%) + 1px) calc(var(--inv-y, 50%) + 1px));
  }
}

@media (prefers-reduced-motion: reduce) {
  .site-nav a:not(.nav-cta)::after,
  .hero-links a::after,
  .reel-breakdown a::after,
  .footer-cta::after,
  .footer-links a::after {
    transition: background-size 0.15s ease;
  }
}

/* ============================
   Back-to-top
   ============================ */
.to-top {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 45;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  color: var(--paper);
  background: rgba(18, 17, 16, 0.72);
  border: 1px solid #474747;
  border-radius: 5px;
  text-decoration: none;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.to-top svg {
  width: 18px;
  height: 18px;
  display: block;
}

.to-top:hover,
.to-top:focus-visible {
  background-color: var(--tungsten);
  border-color: var(--tungsten);
  color: #fff;
}

/* ============================
   Hover-sound toggle
   ============================ */
/* Sits just left of .to-top in the bottom-right corner. Added by script.js on
   fine pointers. */
.sound-toggle {
  position: fixed;
  right: calc(1.25rem + 42px + 0.6rem); /* edge + .to-top width + gap */
  bottom: 1.25rem;
  z-index: 45;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  color: var(--paper);
  background: rgba(18, 17, 16, 0.72);
  border: 1px solid #474747;
  border-radius: 5px;
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.sound-toggle svg {
  width: 18px;
  height: 18px;
  display: block;
}

.sound-toggle[aria-pressed="false"] {
  color: var(--ash);
}

.sound-toggle:hover,
.sound-toggle:focus-visible {
  background-color: var(--tungsten);
  border-color: var(--tungsten);
  color: #fff;
}

/* Icon buttons get the same invert circle on fine pointers: a tungsten disc
   slides in behind the icon (which flips black) instead of the flat fill. */
@media (hover: hover) and (pointer: fine) {
  .to-top:hover,
  .to-top:focus-visible,
  .sound-toggle:hover,
  .sound-toggle:focus-visible {
    background-color: rgba(18, 17, 16, 0.72);
    border-color: #474747; /* stays default; the circle-clipped ring below does the black */
    color: #000;
  }

  /* Big box so the disc is a full circle wherever the pointer sits in the
     button; z-index 2 lifts it clear of the button box, under the icon (3). */
  .to-top::before,
  .sound-toggle::before {
    content: "";
    position: absolute;
    inset: -32px;
    background: var(--tungsten);
    pointer-events: none;
    z-index: 2;
    clip-path: circle(0px at calc(var(--inv-x, 50%) + 32px) calc(var(--inv-y, 50%) + 32px));
  }

  .to-top:hover::before,
  .to-top:focus-visible::before,
  .sound-toggle:hover::before,
  .sound-toggle:focus-visible::before {
    clip-path: circle(24px at calc(var(--inv-x, 50%) + 32px) calc(var(--inv-y, 50%) + 32px));
  }

  /* Black border, shown only through the cursor circle — default elsewhere. */
  .to-top::after,
  .sound-toggle::after {
    content: "";
    position: absolute;
    inset: -1px;
    border: 1px solid #000;
    border-radius: 5px;
    pointer-events: none;
    z-index: 4;
    clip-path: circle(0px at calc(var(--inv-x, 50%) + 1px) calc(var(--inv-y, 50%) + 1px));
  }

  .to-top:hover::after,
  .to-top:focus-visible::after,
  .sound-toggle:hover::after,
  .sound-toggle:focus-visible::after {
    clip-path: circle(24px at calc(var(--inv-x, 50%) + 1px) calc(var(--inv-y, 50%) + 1px));
  }

  .to-top svg,
  .sound-toggle svg {
    position: relative;
    z-index: 3;
  }
}

/* ============================
   Custom cursor
   ============================ */
@media (hover: hover) and (pointer: fine) {
  body.has-custom-cursor {
    cursor: none;
  }

  body.has-custom-cursor a,
  body.has-custom-cursor button {
    cursor: none;
  }
}

.cursor-dot {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  border-radius: 50%;
  z-index: 999;
  transform: translate(-50%, -50%);
  opacity: 0;
  width: 8px;
  height: 8px;
  background: #fff;
  transition: width 0.25s ease, height 0.25s ease, background 0.25s ease, opacity 0.2s ease;
}

body.has-custom-cursor .cursor-dot {
  opacity: 1;
}

body.cursor-is-active .cursor-dot {
  width: 44px;
  height: 44px;
  background: var(--tungsten);
}

/* The gallery shows its own big "Drag" cursor — hide the dot there.
   Over an invert-link the link's own tungsten circle stands in for it. */
body.gallery-cursor-on .cursor-dot,
body.cursor-invert .cursor-dot {
  opacity: 0;
}

/* ============================
   Hero type-on animation
   ============================ */
/* Only hide up front when JS is available to type it back in. */
.js .hero [data-type] {
  visibility: hidden;
}

.js .hero [data-type].is-typing,
.js .hero [data-type].type-done {
  visibility: visible;
}

/* Blinking block caret that trails the text while it types. */
.type-caret {
  display: inline-block;
  width: 0.56em;
  height: 1.05em;
  margin-left: 0.08em;
  transform: translateY(0.18em);
  background: currentColor;
  opacity: 0.9;
  animation: type-caret-blink 0.75s steps(1) infinite;
}

/* Short enough to stay within the text line box — a taller caret would grow
   the lockup and shove it around while typing. */
.hero-title .type-caret {
  height: 0.72em;
  transform: none;
}

@keyframes type-caret-blink {
  0%, 49%   { opacity: 0.9; }
  50%, 100% { opacity: 0; }
}

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

/* ============================
   Responsive
   ============================ */
@media (max-width: 900px) {
  .film-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (max-width: 800px) {
  .site-nav { display: none; }
  .nav-toggle { display: flex; }

  .site-nav.is-open {
    display: flex;
    position: fixed;
    top: 4rem;
    right: var(--edge);
    left: var(--edge);
    flex-direction: column;
    align-items: stretch;
    gap: 1.25rem;
    background: var(--stage-raised);
    padding: 1.5rem;
    border: 1px solid var(--line);
    border-radius: 4px;
  }

  .nav-cta {
    text-align: center;
  }

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

  .hero-sub {
    align-self: stretch;
    max-width: none;
  }
}

@media (max-width: 560px) {
  .hero-name { font-size: clamp(1.5rem, 8vw, 2.6rem); }
  .hero-title strong { font-size: clamp(1.4rem, 8.5vw, 2.8rem); }
}
