@font-face {
  font-family: 'ITC Avant Garde Gothic';
  src: url('assets/fonts/ITCAvantGardeGothic-Medium.ttf') format('truetype');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'ITC Avant Garde Gothic';
  src: url('assets/fonts/ITCAvantGardeGothic-Demi.ttf') format('truetype');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'ITC Avant Garde Gothic';
  src: url('assets/fonts/ITCAvantGardeGothic-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --spacing: 22px;
  --reveal-x: -9999px;
  --reveal-y: -9999px;
  --reveal-r: 26px;
}

@media (min-width: 480px) {
  :root {
    --spacing: 28px;
  }
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  cursor: none;
  /* Images/videos are natively draggable, and that drag affordance shows
     Chrome's own cursor at the OS/compositor level — outside the page's
     control, so `cursor: none` above can't suppress it. Turning off the
     native drag behavior removes that affordance entirely, which is what
     actually stops the real cursor from showing through the custom one. */
  -webkit-user-drag: none;
  user-drag: none;
}

html {
  /* Stops the whole page's own elastic/rubber-band overscroll (macOS
     trackpad). Without this, a fast swipe can bounce the page itself past
     its scroll boundary — the projects sidebar (normal document flow)
     shifts with that bounce while the fixed logo doesn't move, which is
     what makes list items appear to rise above it. */
  overscroll-behavior-y: none;
  /* Navigation is handled by scroll-snap, the projects list's own custom
     scrollbar thumb, and the home/project-page slide transitions — a native
     scrollbar on top of all that is just visual noise. Scrolling itself
     (wheel, trackpad, keyboard, touch) still works exactly as before; this
     only hides the drawn track/thumb. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

html::-webkit-scrollbar {
  display: none;
}

/* Mobile/tablet (<1200px) on the home/projects page (index.html) has no
   real page scrolling at all — #home is hidden and #projects is
   swipe-only — so the page itself should never be natively scrollable
   there, belt-and-suspenders on top of the dvh fix on .projects-section.
   Scoped to .home-page (only index.html's <html> has this class) since
   my-dear.html (and any future case-study page) is a long, genuinely-
   scrolling page — this rule would otherwise disable its scroll too,
   since it's the same shared stylesheet. Same specificity as the desktop
   reset below (a plain class each), so normal source-order cascade keeps
   desktop scrolling correct. */
html.home-page {
  overflow: hidden;
}

#cursorCanvas {
  position: fixed;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  z-index: 9999;
}

/* ---------- Spotlight reveal ----------
   Cursor dot itself always stays orange; only the exact patch of each element
   under the circle turns white, with a soft blurred falloff (not a hard edge).
   A white-colored duplicate of each element's content is stacked on top and
   masked to a small blurred circle that JS keeps pinned to the live cursor
   position (see mousemove handler). Pseudo-elements inherit --reveal-x/y/r
   from their own element, which JS sets directly. Griffin/Lottie is
   intentionally excluded. */
.headline::before,
.description::before {
  -webkit-mask-image: radial-gradient(circle var(--reveal-r) at var(--reveal-x) var(--reveal-y), #000 0%, transparent 100%);
  mask-image: radial-gradient(circle var(--reveal-r) at var(--reveal-x) var(--reveal-y), #000 0%, transparent 100%);
  pointer-events: none;
  content: attr(data-text);
  position: absolute;
  inset: 0;
  color: #ffffff;
  white-space: inherit;
}

/* ---------- Hero entrance animation ---------- */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Held at opacity 0 until html.hero-ready is added — see the inline script
   right after #loadingScreen in index.html. That keeps this entrance from
   playing out invisibly underneath the loading screen; it now starts only
   once the loading screen actually clears (or immediately on a repeat visit
   within the same tab, when there's no loading screen to wait for). */
.griffin-badge,
.headline,
.description,
.feature-media {
  opacity: 0;
}

html.hero-ready .griffin-badge,
html.hero-ready .headline,
html.hero-ready .description,
html.hero-ready .feature-media {
  animation: fadeIn ease-out forwards;
}

html.hero-ready .griffin-badge {
  animation-duration: 0.8s;
  animation-delay: 0s;
}

html.hero-ready .headline {
  animation-duration: 0.8s;
  animation-delay: 0s;
}

html.hero-ready .description {
  animation-duration: 0.8s;
  animation-delay: 0.8s;
}

html.hero-ready .feature-media {
  animation-duration: 1s;
  animation-delay: 0.3s;
}

body {
  background: #000000;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
}

/* See the html:has(...) rule above — same reasoning, same scoping. */
/* See html.home-page above — same reasoning, same scoping/specificity
   approach (matches the desktop reset below exactly). */
body.home-page {
  overflow: hidden;
}

/* ---------- Home section (hero) ----------
   Mobile/tablet (< 1200px): one simple stacked column. Logo and nav are
   pinned to opposite top corners at every breakpoint; everything else is a
   centered flex column, fluidly sized. Desktop turns this into a full
   viewport-height scroll-snap section (see media query below). */
/* Mobile/tablet has no hero at all — the Projects view is the landing
   screen there. Desktop (>=1200px) restores it via the ID override below,
   as the first of the two scroll-snap sections. */
#home {
  display: none;
}

.home-section {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 56px 24px 48px;
  gap: var(--spacing);
}

.projects-pill,
.nav-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-style: normal;
  line-height: 1;
  text-transform: capitalize;
  text-decoration: none;
  color: #000000;
  white-space: nowrap;
  transition: background 0.2s ease;
}

.projects-pill.is-hidden {
  opacity: 0;
  pointer-events: none;
}

.projects-pill:hover {
  background: #ffba86;
}

.nav-pill:hover {
  background: #d9d9d9;
}

/* Orange variant for pages that have no #about to link to (the About page
   itself) — swaps in a "Projects" pill in the same slot/size as the usual
   gray About pill, matching .projects-pill's own color/hover instead. */
.nav-pill.is-projects {
  background: #ff6d00;
  font-weight: 700;
}

.nav-pill.is-projects:hover {
  background: #ffba86;
}

.projects-pill span,
.nav-pill span {
  position: relative;
  top: 3px;
}

/* Optical nudge for this specific label only — "Contact" reads centered
   as-is, but "About" sat a hair off in the same pill shape. */
a.nav-pill[href="#about"] span {
  left: -1px;
}

.projects-pill {
  position: fixed;
  top: 16px;
  left: 16px;
  z-index: 20;
  height: 30px;
  padding: 0 14px;
  background: #ff6d00;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: -0.02em;
}

.nav {
  /* About/Contact only exist as part of the desktop hero — mobile/tablet
     has no hero section to link to (see #home below), so the pills are
     hidden here and restored at the desktop breakpoint. */
  display: none;
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 20;
  align-items: center;
  gap: 10px;
}

.nav-pill {
  height: 30px;
  padding: 0 12px;
  background: #acacac;
  font-weight: 600;
  font-size: 12px;
  letter-spacing: -0.02em;
}

.frame {
  width: 100%;
  max-width: 560px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing);
}

.griffin-badge {
  position: relative;
  width: min(32vw, 130px);
  aspect-ratio: 292.398 / 191.591;
  background: #acacac;
  border-radius: 9999px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.griffin-lottie {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.6s ease-out;
}

.griffin-lottie.is-ready {
  opacity: 1;
}

.griffin-lottie svg {
  display: block;
  width: 100%;
  height: 100%;
}

.headline {
  position: relative;
  width: 100%;
  text-align: center;
  font-weight: 600;
  font-size: 24px;
  letter-spacing: -0.03em;
  line-height: 1.2;
  color: #acacac;
  text-transform: capitalize;
}

.description {
  position: relative;
  width: 100%;
  max-width: 360px;
  text-align: center;
  font-weight: 500;
  font-size: 14px;
  line-height: 1.5;
  color: #acacac;
}

/* ---------- Feature media (full-bleed photo below the hero copy) ----------
   Escapes .frame's centered max-width via the 100vw/-50vw bleed trick so it
   spans the full viewport edge-to-edge on mobile/tablet. On desktop it's a
   fixed, viewport-width block sitting outside the letterboxed .frame — its
   `top` is set by scaleFrame() in JS so it always lines up with the scaled
   hero content above it, and it stretches edge-to-edge at any window size
   instead of gutter­ing along with the frame (see index.html). */
.feature-media {
  position: relative;
  left: 50%;
  width: 100vw;
  margin-left: -50vw;
  aspect-ratio: 3 / 4;
  overflow: hidden;
}

.feature-media-track {
  position: absolute;
  inset: 0;
  display: flex;
  height: 100%;
  /* JS drives this transform (see index.html) — transitions are toggled on
     per-step so the one silent reset-to-start jump (see the cloned final
     slide) can happen instantly, with no visible snap-back. */
  transition: transform 0.7s ease;
}

.feature-media-slide {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
  display: block;
}

.feature-media-fade {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent 45%, rgba(0, 0, 0, 0.85) 85%, #000000 100%);
  pointer-events: none;
}

.feature-media-arrow {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  display: flex;
}

/* ---------- Projects section ----------
   Mobile/tablet: a full-screen, one-project-at-a-time view — top bar
   (griffin badge + menu), full-bleed photo, name/category + counter
   overlaid at the bottom. Vertical swipe moves between projects (see JS).
   Desktop (>=1200px) replaces this with the Figma sidebar layout: a fixed
   black sidebar with the project list + custom scrollbar, and a full-bleed
   image pane that swaps on hover and stays until a different project is
   hovered — see the media query below. */
.projects-section {
  position: relative;
  /* No lower AND no upper bound on purpose: this view has no scrolling
     (swipe-only), so it always needs to match the real viewport exactly —
     capping it (916px, matching the Figma reference's tallest frame) left
     a black gap at the bottom on taller real phones (e.g. iPhone 17 Pro
     Max), and a floor above a short window would push the title/category/
     counter below the fold with no way to reach them.
     100vh is a fallback for browsers without dvh support — on mobile
     Safari, 100vh is measured against the viewport with the address bar
     hidden, taller than what's actually visible when it's showing. That
     leftover sliver is just enough to make the whole page natively
     scrollable, which is what surfaced as a scrollbar you could drag a
     little before a swipe registered. 100dvh tracks the real visible
     height instead. */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  background: #000000;
}

.projects-menu {
  display: none;
  position: relative;
  padding: 0 24px 24px;
}

/* A solid black mask covering the whole top strip of the sidebar (not just
   a tight box around the logo) — matching the reference rectangle from the
   Figma file (400x207 at the 1440x900 frame, bottom edge landing exactly
   where "Kuja" naturally starts). position:fixed + opaque means the list
   can never visually show through it, regardless of any scroll-rendering
   quirk, because it physically covers that screen region outside of and
   independent from the list's own layout. JS toggles .is-visible based on
   whether #projects is in view (see the IntersectionObserver below), since
   position:fixed would otherwise keep it on screen over the home section. */
.projects-mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 130px;
  background: linear-gradient(to bottom, #000000 0%, #000000 80%, transparent 100%);
  z-index: 25;
  opacity: 0;
  pointer-events: none;
  /* Forces its own GPU compositing layer. Without this, some Chrome/mobile
     GPU combinations briefly paint scrolled content "through" a fixed
     element during the scroll gesture itself — a compositor timing bug,
     not a stacking/z-index problem (which is why it looks fine when
     inspected statically but breaks during a real touch-scroll). */
  transform: translateZ(0);
  backface-visibility: hidden;
}

.projects-mask.is-visible {
  opacity: 1;
}

/* Only the logo image itself is clickable — not the whole black strip
   behind it — so moving the mouse around the sidebar can't trigger an
   accidental "back to home" navigation. Positioned/sized here (instead of
   on .projects-logo) since this is now the actual hit target. */
.projects-logo-link {
  position: absolute;
  top: 24px;
  left: 12px;
  width: 130px;
  display: block;
  pointer-events: none;
}

.projects-mask.is-visible .projects-logo-link {
  /* Only clickable once actually visible — otherwise it would sit over the
     home section, invisibly blocking clicks/hover on the hero underneath. */
  pointer-events: auto;
}

.projects-logo {
  display: block;
  width: 100%;
  height: auto;
  /* Matches .projects-pill/.nav-pill's own hover timing so the logo reads
     as behaving "like the buttons" — brightening is the equivalent of
     their background lightening, since this is a flat image on a solid
     background rather than a text pill. */
  transition: filter 0.2s ease;
}

.projects-logo-link:hover .projects-logo {
  filter: brightness(1.6);
}

/* No native scrolling here at all (overflow:hidden, not auto) — the list
   is moved with a JS-driven transform instead (see the wheel handler in
   index.html). That means the browser's own scroll mechanism, and any
   bounce/momentum physics that come with it, never run in the first
   place — the position is just a clamped number we set directly, so it's
   structurally impossible for it to go past its start (where "Kuja"
   first sits) regardless of scroll speed or input device. */
.projects-list-wrap {
  position: relative;
  max-height: 55vh;
  overflow: hidden;
  padding-top: 150px;
}

.projects-list {
  will-change: transform;
}

.projects-list {
  list-style: none;
}

.projects-list a {
  display: block;
  padding: 14px 0;
  border-bottom: 1px solid rgba(172, 172, 172, 0.25);
  color: #acacac;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 600;
  font-size: 20px;
  letter-spacing: -0.02em;
  text-transform: capitalize;
  text-decoration: none;
  transition: color 0.2s ease;
}

.projects-list li:last-child a {
  border-bottom: none;
}

.projects-list a.is-active {
  color: #ffffff;
}

.projects-scrollbar {
  display: none;
}

.projects-image {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.projects-image img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 0.25s ease;
}

.projects-image img.is-swapping {
  opacity: 0;
}

.projects-mobile-fade {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent 50%, rgba(0, 0, 0, 0.85) 100%);
  pointer-events: none;
}

/* Desktop-only hover reveal (see the JS timer in index.html): a gradient +
   one-line summary that fades in over the photo after hovering a project's
   name for a beat, so you get a preview before actually navigating there.
   display:none until the 1200px desktop layout kicks in — the mobile/tablet
   list has no hover input, so this never applies there. */
.projects-hover-summary {
  display: none;
}

@media (min-width: 1200px) {
  .projects-hover-summary {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: flex-end;
    padding: 40px;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.88) 0%, rgba(0, 0, 0, 0.5) 45%, transparent 80%);
    opacity: 0;
    transition: opacity 0.4s ease;
    pointer-events: none;
  }

  .projects-hover-summary.is-visible {
    opacity: 1;
  }

  .projects-hover-summary p {
    max-width: 480px;
    margin: 0;
    font-family: 'ITC Avant Garde Gothic', sans-serif;
    font-weight: 500;
    font-size: 18px;
    line-height: 1.4;
    color: #ffffff;
  }
}

.projects-mobile-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 53px;
  background: #000000;
  /* Above .mobile-menu-overlay (z-index: 10) so the bar — and the
     hamburger/X button that controls the overlay — stays on top and
     usable while the menu is open. */
  z-index: 15;
}

.projects-mobile-badge {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 36px;
  border-radius: 9999px;
  background: #acacac;
}

.projects-mobile-badge img {
  width: 40px;
  height: auto;
}

.projects-mobile-menu {
  position: absolute;
  right: 20px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  gap: 10px;
  width: 44px;
  height: 44px;
  background: none;
  border: none;
  padding: 0;
}

.projects-mobile-menu span {
  display: block;
  width: 26px;
  height: 2.6px;
  background: #acacac;
  transition: transform 0.25s ease, opacity 0.25s ease;
}

/* Morphs the two bars into an X in place — no separate close icon. Distance
   is half the center-to-center gap between the two spans (10px gap +
   2.6px height = 12.6px), so they meet exactly in the middle. */
.projects-mobile-menu.is-open span:first-child {
  transform: translateY(6.3px) rotate(45deg);
}

.projects-mobile-menu.is-open span:last-child {
  transform: translateY(-6.3px) rotate(-45deg);
}

/* Full-screen black overlay (About/Contact) toggled by the hamburger/X
   button above — see scripts/mobile-menu.js. Sits above regular page
   content but below the fixed griffin/menu bar itself, so that bar stays
   put and visually "hosts" the open/close control throughout. */
.mobile-menu-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 10;
  background: #000000;
  opacity: 0;
  transition: opacity 0.25s ease;
}

.mobile-menu-overlay.is-open {
  display: block;
  opacity: 1;
}

.mobile-menu-links {
  padding: 100px 24px 0;
}

.mobile-menu-links a {
  display: block;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 600;
  font-size: 40px;
  letter-spacing: -2px;
  text-transform: capitalize;
  color: #acacac;
  text-decoration: none;
  margin-bottom: 20px;
  transition: color 0.2s ease;
}

.mobile-menu-links a:last-child {
  margin-bottom: 0;
}

.mobile-menu-links a:hover,
.mobile-menu-links a:active {
  color: #ff6d00;
}

@media (min-width: 1200px) {
  .mobile-menu-overlay {
    display: none !important;
  }
}

.projects-mobile-overlay {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  padding: 0 20px 24px;
  /* Purely informational (no interactive children) — without this, tapping
     directly on the name/category/counter text would hit this overlay
     instead of .projects-image underneath, missing the swipe/tap handlers
     bound there. */
  pointer-events: none;
}

.projects-mobile-name {
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 600;
  font-size: 26px;
  letter-spacing: -0.03em;
  color: #ffffff;
  text-transform: capitalize;
}

.projects-mobile-category {
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 500;
  font-size: 15px;
  color: #acacac;
  margin-top: 4px;
}

.projects-mobile-counter {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 500;
  font-size: 15px;
  color: #ffffff;
}

.projects-mobile-counter-line {
  width: 1px;
  height: 60px;
  background: #ffffff;
  opacity: 0.6;
}

/* ---------- Tablet refinement (480px - 1199px) ---------- */
@media (min-width: 480px) {
  .home-section {
    padding-top: 72px;
  }

  .projects-pill {
    top: 20px;
    left: 20px;
    height: 34px;
    padding: 0 16px;
    font-size: 14px;
  }

  .nav {
    top: 20px;
    right: 20px;
    gap: 12px;
  }

  .nav-pill {
    height: 34px;
    padding: 0 14px;
    font-size: 14px;
  }

  .frame {
    max-width: 680px;
  }

  .griffin-badge {
    width: min(24.5vw, 202px);
  }

  .headline {
    font-size: 32px;
  }

  .description {
    font-size: 16px;
    max-width: 460px;
  }

  .feature-media {
    aspect-ratio: 16 / 10;
  }

  .projects-menu {
    padding: 0 40px 32px;
  }

  .projects-mask {
    height: 150px;
  }

  .projects-logo-link {
    top: 28px;
    left: 28px;
    width: 150px;
  }

  .projects-list a {
    font-size: 24px;
    padding: 18px 0;
  }

  .projects-list-wrap {
    padding-top: 180px;
  }
}

/* ---------- Desktop: exact pixel layout (>= 1200px) ----------
   #home and #projects are each a full viewport-height scroll-snap section;
   scrolling, the down arrow, and the Projects pill all move between them. */
@media (min-width: 1200px) {
  html.home-page {
    overflow-y: auto;
    scroll-snap-type: y mandatory;
    scroll-behavior: smooth;
  }

  /* Landing here with a #projects (or other) hash already in the URL —
     e.g. clicking a project page's logo/back link — should jump straight
     there. Without this override, the browser's own scroll-to-fragment
     honors the smooth scroll-behavior above, so it visibly flashes #home
     first and animates down. JS (see index.html's head script) adds this
     class only while that initial jump happens, then removes it so every
     later in-page scroll (wheel, pill, logo click) stays smooth. */
  html.no-anchor-smooth-scroll {
    scroll-behavior: auto !important;
  }

  /* Precomputed initial state for landing directly on #projects (see the
     head script in index.html) — hides the pill and shows the logo mask
     synchronously on first paint, before the real scroll-driven
     IntersectionObserver has had a chance to run and do it itself. */
  html.starts-on-projects .projects-pill {
    opacity: 0;
    pointer-events: none;
  }

  html.starts-on-projects .projects-mask {
    opacity: 1;
  }

  body.home-page {
    overflow: visible;
  }

  #home {
    display: flex;
  }

  .home-section {
    height: 100vh;
    scroll-snap-align: start;
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: flex-start;
    padding: 0;
    gap: 0;
    overflow: hidden;
  }

  .projects-pill {
    position: fixed;
    top: 39px;
    left: 41px;
    width: 124px;
    height: 40px;
    font-size: 21px;
    letter-spacing: -1.05px;
    padding: 0;
  }

  .nav {
    display: flex;
    position: fixed;
    top: 39px;
    right: 41px;
    gap: 15px;
  }

  .nav-pill {
    width: 100px;
    height: 40px;
    font-size: 21px;
    letter-spacing: -1.05px;
    padding: 0;
  }

  /* Matches .projects-pill's own desktop width exactly (124px) — this is
     the same "Projects" pill in spirit, just inline in the nav row instead
     of fixed to the corner, so it should read as the identical button. */
  .nav-pill.is-projects {
    width: 124px;
  }

  .frame {
    position: relative;
    width: 1440px;
    height: 900px;
    max-width: none;
    display: block;
    gap: 0;
    flex-shrink: 0;
    transform-origin: top center;
  }

  .griffin-badge {
    position: absolute;
    left: 50%;
    top: 39px;
    transform: translateX(-50%);
    width: 292.398px;
    height: 191.591px;
  }

  .headline {
    position: absolute;
    left: 50%;
    top: 246px;
    transform: translateX(-50%);
    width: 700px;
    font-size: 45.48px;
    letter-spacing: -2.274px;
    line-height: 1.1;
  }

  .description {
    position: absolute;
    left: 50%;
    top: 318px;
    transform: translateX(-50%);
    width: 396px;
    max-width: none;
    text-align: center;
    font-size: 18px;
    line-height: 1.5;
  }

  .feature-media {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    /* top is set in px by scaleFrame() in index.html */
    margin-left: 0;
    aspect-ratio: auto;
  }

  .feature-media-arrow {
    bottom: 15px;
  }

  .projects-section {
    display: flex;
    height: 100vh;
    scroll-snap-align: start;
    flex-direction: row;
    overflow: visible;
  }

  .projects-mobile-bar,
  .projects-mobile-overlay,
  .projects-mobile-fade {
    display: none;
  }

  .projects-menu {
    display: flex;
    width: 403px;
    flex-shrink: 0;
    height: 100%;
    padding: 0;
  }

  /* Matches the reference rectangle from Figma exactly: 400x207 at the
     1440x900 frame, sized to the sidebar's own 403px width. */
  .projects-mask {
    right: auto;
    width: 403px;
    height: 207px;
  }

  .projects-logo-link {
    top: 41px;
    left: 43px;
    width: 190px;
  }

  .projects-list-wrap {
    flex: 1;
    max-height: none;
    height: 100%;
    overflow: hidden;
    /* 206px matches the Figma reference exactly — "Kuja" sits at y:206 in
       the 1440x900 frame, right at the mask's own 207px bottom edge. */
    padding: 206px 43px 40px;
  }

  .projects-list a {
    font-size: 30px;
    letter-spacing: -0.02em;
    padding: 20px 0;
  }

  .projects-scrollbar {
    display: block;
    position: absolute;
    top: 140px;
    bottom: 40px;
    right: 8px;
    width: 8px;
  }

  .projects-scrollbar-thumb {
    position: absolute;
    top: 0;
    left: 0;
    width: 8px;
    border-radius: 9999px;
    background: #acacac;
  }

  .projects-image {
    position: relative;
    inset: auto;
    flex: 1;
    height: 100%;
  }
}

/* ---------- Project case-study page (e.g. my-dear.html) ----------
   A single long-scroll page per project: logo, title + copy, then a run of
   full-width photos with one 2-column pair partway through. Mobile-first
   fluid sizing, matching the rest of the site's breakpoint pattern. */
.project-page {
  background: #000000;
}

/* Mobile/tablet (<1200px): the desktop wordmark header and About/Contact
   are hidden — the reused .projects-mobile-bar (griffin badge + menu icon)
   takes over instead, same as the projects page itself. */
.project-header {
  display: none;
}

.project-logo-link {
  display: inline-flex;
}

.project-logo {
  display: block;
  width: 130px;
  height: auto;
  /* Same hover treatment as the projects sidebar's own logo (.projects-logo)
     so the mark behaves consistently wherever it appears as a nav link. */
  transition: filter 0.2s ease;
}

.project-logo-link:hover .project-logo {
  filter: brightness(1.6);
}

.project-shell {
  max-width: 1376px;
  margin: 0 auto;
  /* Top padding clears the fixed .projects-mobile-bar (53px) plus the
     usual breathing room, since that bar sits outside document flow. */
  padding: 77px 24px 24px;
}

.project-intro {
  padding: 24px 0 40px;
}

.project-title {
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 600;
  font-size: 40px;
  letter-spacing: -0.03em;
  line-height: 1.1;
  color: #ffffff;
  text-transform: capitalize;
  margin-bottom: 20px;
}

.project-copy {
  display: flex;
  flex-direction: column;
  gap: 1em;
  max-width: 620px;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 500;
  font-size: 15px;
  line-height: 1.6;
  color: #acacac;
}

.project-media {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

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

.project-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}

.project-grid img,
.project-grid video {
  display: block;
  width: 100%;
  height: auto;
}

.project-grid img.is-mirrored {
  transform: scaleX(-1);
}

.project-rounded-top {
  border-radius: 16px 16px 0 0;
}

/* Full-width looping video, sized like .project-image but with a fixed
   aspect-ratio (matching the design's own placeholder box) so the layout
   doesn't jump while the video loads. */
/* Full-width looping video, sized like .project-image. Each instance sets
   its own `style="aspect-ratio: W / H"` matching its own footage (they're
   all different shapes) so the layout doesn't jump while it loads. */
.project-video {
  display: block;
  width: 100%;
  object-fit: cover;
  background: #000000;
}

.project-grid video {
  object-fit: cover;
}

/* Caps a tall/near-square video (e.g. SID's) at a viewport-relative height
   instead of stretching full shell width, so the whole thing fits on screen
   at once rather than needing a scroll to see top-to-bottom. Width follows
   from the height via the instance's own aspect-ratio, centered since it no
   longer spans the full row. */
.project-video.is-compact {
  width: auto;
  max-width: 100%;
  height: 80vh;
  max-height: 700px;
  align-self: center;
}

/* Wraps a .project-image that has a small logo watermark overlaid on top
   (e.g. Biom's hero photo) — percentage-based position/size so the
   watermark stays put relative to the photo at any width, same as the
   object-fit:cover crops used elsewhere. */
.project-image-wrap {
  position: relative;
}

/* A colored full-bleed block that holds a run of grids/images (e.g.
   FamuFest's pink section) — the color shows through the existing grid
   gaps and a bit of padding, instead of a plain black background. */
.project-color-block {
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: 24px;
}

.project-image-wrap .project-image {
  position: relative;
}

.project-watermark {
  position: absolute;
  top: 45.34%;
  left: 50%;
  width: 22.87%;
  opacity: 0.44;
  transform: translateX(-50%);
  pointer-events: none;
}

/* A centered pull-quote-style note that can sit inline in the .project-media
   flow (e.g. between image groups) — same font/color as .project-copy, and
   spaced by the surrounding flex gap rather than its own margin so it lines
   up with the rest of the page's distances. */
.project-caption {
  align-self: center;
  max-width: 620px;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 500;
  font-size: 15px;
  line-height: 1.5;
  text-align: center;
  color: #acacac;
}

@media (min-width: 480px) {
  .project-shell {
    padding: 77px 40px 40px;
  }

  .project-title {
    font-size: 56px;
  }

  .project-copy {
    font-size: 16px;
  }

  .project-caption {
    font-size: 16px;
  }

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

  /* A row of exactly 3 matched items (e.g. Klub Famu's signage videos) —
     stays side-by-side instead of the default 2-up wrap. */
  .project-grid.is-triple {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

@media (min-width: 1200px) {
  .project-header {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 20;
    /* Matches the projects sidebar's own logo position exactly (.projects-logo:
       top 41px, left 43px at this breakpoint) so it doesn't shift between the
       homepage and a project's case-study page. */
    padding: 41px 41px 41px 43px;
  }

  .project-logo {
    width: 190px;
  }

  .project-shell {
    /* Top padding clears the now-fixed .project-header (logo no longer
       takes up space in document flow) so the title doesn't sit under it. */
    padding: 140px 64px 64px;
  }

  .project-intro {
    padding: 0 0 56px;
  }

  .project-title {
    font-size: 72px;
  }

  .project-copy {
    font-size: 18px;
    line-height: 1.5;
  }

  .project-caption {
    font-size: 18px;
    line-height: 1.31;
  }

  .project-media {
    gap: 32px;
  }

  .project-grid {
    gap: 32px;
  }

  .project-color-block {
    gap: 32px;
    padding: 32px;
  }
}


/* ---------- About page (about.html) ----------
   Mobile/tablet (<1200px): a normal scrolling document — full-bleed photo,
   then a single short column of bio text below it. Desktop (>=1200px):
   a fixed full-height split instead (bio text on black at left, portrait
   photo filling the right edge) with no scrolling at all — see the
   overflow lock further down, scoped to that breakpoint only. */
.about-mobile-photo {
  display: block;
  width: 100%;
  height: auto;
}

.about-mobile-text {
  padding: 32px 24px 60px;
  color: #acacac;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-size: 16px;
  line-height: 1.31;
}

.about-mobile-text p {
  margin: 0 0 20px;
}

.about-mobile-text p:last-child {
  margin-bottom: 0;
}

.about-text {
  display: none;
}

.about-photo-col {
  display: none;
}

@media (min-width: 1200px) {
  /* This page is meant to read as one fixed screen at desktop, not a
     scrolling document, so both html and body are locked — mobile/tablet
     above needs normal scrolling instead, hence scoping this here rather
     than applying it unconditionally. */
  html.about-page,
  body.about-page {
    height: 100vh;
    overflow: hidden;
  }

  .about-mobile {
    display: none;
  }

  .about-shell {
    display: flex;
    height: 100vh;
  }

  .about-text {
    display: flex;
    flex: 0 0 57%;
    gap: 27px;
    box-sizing: border-box;
    padding: 171px 40px 60px 39px;
  }

  .about-text-col {
    width: 345px;
    flex-shrink: 0;
    color: #acacac;
    font-family: 'ITC Avant Garde Gothic', sans-serif;
    font-size: 18px;
    line-height: 1.31;
  }

  .about-text-col p {
    margin: 0 0 26px;
  }

  .about-text-col p:last-child {
    margin-bottom: 0;
  }

  .about-photo-col {
    display: block;
    flex: 1 1 auto;
  }

  .about-photo-col img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

/* ---------- Contact page (contact.html) ----------
   Mobile/tablet (<1200px): a normal scrolling document — heading, contact
   info, photo, socials, then the form, all stacked in document flow, same
   padding/type conventions as the About page's own mobile treatment.
   Desktop (>=1200px): a fixed, no-scroll single screen, absolutely
   positioned to the Figma spec's overall arrangement (social icons beside
   the photo's lower edge, form in a 2-column block) but sized against the
   rest of the site's own type scale (.project-title's 72px heading,
   .project-copy/.about-text-col's 18px body text) rather than the much
   larger raw Figma numbers, which read as oversized next to every other
   page and didn't fit one screen. */
.contact-shell {
  /* Top padding clears the fixed mobile bar (53px) plus breathing room,
     matching .project-shell's own convention — needed here since the
     heading is the very first thing on the page, unlike About where the
     photo (no padding needed) comes first instead. */
  padding: 77px 24px 60px;
}

.contact-heading {
  margin: 0 0 20px;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 600;
  font-size: 40px;
  letter-spacing: -0.03em;
  color: #ffffff;
}

.contact-email,
.contact-phone {
  display: block;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 500;
  font-size: 16px;
  color: #ffffff;
}

.contact-email {
  text-decoration: none;
  transition: color 0.2s ease;
}

.contact-email:hover {
  color: #ff6d00;
}

.contact-phone {
  margin: 4px 0 24px;
}

.contact-photo {
  width: 100%;
  max-width: 280px;
  aspect-ratio: 1 / 1;
  margin-bottom: 20px;
}

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

.contact-socials {
  display: flex;
  gap: 16px;
  margin-bottom: 32px;
}

.contact-socials img {
  display: block;
  width: 28px;
  height: 28px;
  transition: opacity 0.2s ease;
}

.contact-socials a:hover img {
  opacity: 0.7;
}

.contact-form {
  display: flex;
  flex-direction: column;
  gap: 20px;
  max-width: 470px;
}

.contact-field label {
  display: block;
  margin-bottom: 8px;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 500;
  font-size: 16px;
  color: #ffffff;
}

.contact-field input,
.contact-field textarea {
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: 42px;
  padding: 0 16px;
  border: none;
  border-radius: 10px;
  background: rgba(172, 172, 172, 0.58);
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-weight: 500;
  font-size: 16px;
  /* The one explicit styling ask for this page: typed text reads in the
     brand orange rather than the usual white/gray body copy. */
  color: #ff6d00;
}

.contact-field textarea {
  height: 84px;
  padding-top: 10px;
  resize: vertical;
  font-family: inherit;
}

.contact-field input::placeholder,
.contact-field textarea::placeholder {
  color: rgba(255, 109, 0, 0.6);
}

/* Appearance/sizing comes entirely from .nav-pill.is-projects (see the
   markup) — the same orange pill used for "Projects" everywhere else on
   the site, so the CTA reads as the same button, not a one-off. This class
   only resets the <button>-specific defaults that .nav-pill/.projects-pill
   never had to deal with, since they're normally <a> tags. */
.contact-submit {
  align-self: flex-start;
  border: none;
  cursor: pointer;
}

.contact-submit:disabled {
  opacity: 0.6;
  cursor: default;
}

.contact-form-status {
  margin: 0;
  font-family: 'ITC Avant Garde Gothic', sans-serif;
  font-size: 14px;
  color: #acacac;
}

.contact-form-status.is-error {
  color: #ff6d00;
}

@media (min-width: 1200px) {
  html.contact-page,
  body.contact-page {
    height: 100vh;
    overflow: hidden;
  }

  .contact-shell {
    position: relative;
    height: 100vh;
    padding: 0;
  }

  .contact-heading {
    position: absolute;
    left: 41px;
    top: 130px;
    margin: 0;
    font-size: 72px;
  }

  .contact-email,
  .contact-phone {
    position: absolute;
    left: 44px;
  }

  .contact-email {
    top: 228px;
  }

  .contact-phone {
    top: 256px;
    margin: 0;
  }

  .contact-photo {
    position: absolute;
    left: 41px;
    top: 310px;
    width: 280px;
    max-width: none;
    aspect-ratio: auto;
    margin-bottom: 0;
  }

  .contact-socials {
    position: absolute;
    left: 41px;
    top: 610px;
    margin-bottom: 0;
  }

  /* Form fields: each label + bubble pair positioned as a simple 2-column
     block, roughly aligned with the heading/photo column to its left. */
  .contact-form {
    display: contents;
  }

  .contact-field {
    position: absolute;
  }

  .contact-field-name {
    left: 480px;
    top: 130px;
    width: 220px;
  }

  .contact-field-email {
    left: 730px;
    top: 130px;
    width: 220px;
  }

  .contact-field-budget {
    left: 480px;
    top: 214px;
    width: 470px;
  }

  .contact-field-message {
    left: 480px;
    top: 298px;
    width: 470px;
  }

  .contact-submit {
    position: absolute;
    left: 480px;
    top: 440px;
  }

  .contact-form-status {
    position: absolute;
    left: 480px;
    top: 500px;
    width: 400px;
  }

  .contact-form-status.is-error {
    color: #ff6d00;
  }
}

/* ---------- Loading screen ----------
   Shown only on the homepage while it loads, then removed — see
   scripts/loading-screen.js. Sits above absolutely everything (custom
   cursor included) until it's done. */
.loading-screen {
  position: fixed;
  inset: 0;
  z-index: 99999;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 28px;
  background: #ff6d00;
  transition: opacity 0.4s ease, visibility 0.4s ease;
}

.loading-screen-logo {
  width: 220px;
  height: auto;
}

.loading-screen-spinner {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 3px solid rgba(0, 0, 0, 0.2);
  border-top-color: #000000;
  animation: loading-screen-spin 0.8s linear infinite;
}

@keyframes loading-screen-spin {
  to {
    transform: rotate(360deg);
  }
}

.loading-screen.is-hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

@media (min-width: 1200px) {
  .loading-screen-logo {
    width: 320px;
  }
}
