/* ============================================
   JALITA ASPELIN — PORTFOLIO
   One typeface (Switzer). Arpeggio-structured hero.
   Spector/Wendrich case-study anatomy. Baseform grid
   restraint. van Schneider pacing on project galleries.
   ============================================ */

@import url('https://api.fontshare.com/v2/css?f[]=switzer@400,500,600,700,800&display=swap');

/* Self-hosted — used ONLY for the wordmark (#hero-wordmark). Everything
   else on the site stays on Switzer. All eight are declared so it's a
   one-line swap in --font-wordmark below to go back to an earlier try. */
@font-face {
  font-family: 'Amoria';
  src: url('assets/fonts/AMORIA.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Chelion';
  src: url('assets/fonts/Chelion.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'ChromeSlab';
  src: url('assets/fonts/ChromeSlab-Regular.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'SLTFNightWindfall';
  src: url('assets/fonts/SLTFNightWindfall-Regular.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'CabinetGrotesk-Black';
  src: url('assets/fonts/CabinetGrotesk-Black.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Syne-Bold';
  src: url('assets/fonts/Syne-Bold.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'MeshedDisplay-Black';
  src: url('assets/fonts/MeshedDisplay-Black.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'MeshedDisplay-Regular';
  src: url('assets/fonts/MeshedDisplay-Regular.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'MangoGrotesque-Medium';
  src: url('assets/fonts/MangoGrotesque-Medium.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'HustleBrush-Regular';
  src: url('assets/fonts/HustleBrush-Regular.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg: #FAFAF8;         /* crisper than the previous pass, still not pure white */
  --dark: #0F0F0F;
  --grey: #A8A6A0;
  --line: #DEDCD5;
  --accent: #FF2E88;     /* bold fuchsia — used sparingly. */
  --accent-alt: #E8FF3D; /* neon yellow — used as the mid-point of the wordmark's scroll gradient (see script.js) */

  --font-body: 'Switzer', -apple-system, sans-serif;
  --font-wordmark: 'HustleBrush-Regular', var(--font-body);

  --space-xs: 0.5rem;
  --space-s: 1rem;
  --space-m: 2rem;
  --space-l: 4rem;
  --space-xl: 8rem;
  --space-xxl: 12rem;

  --container: 1280px;
  --edge: 6vw;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  background: var(--bg);
  color: var(--dark);
  font-family: var(--font-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
/* Kills the default translucent grey/white flash mobile browsers put on
   any tappable element (links, images) on touch — this is the "white
   glimmer at the edge of photos" on mobile tap, not an actual style rule
   of ours. Applied globally since it affects every image/link on every
   page equally. */
a, img, button { -webkit-tap-highlight-color: transparent; }
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
.wrap { max-width: var(--container); margin: 0 auto; padding: 0 var(--edge); }

/* ============================================
   NAV — Arpeggio structure: left link row, right menu mark
   ============================================ */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 260;   /* above .menu-overlay (250) so the button stays visible/clickable when open */
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-s) var(--edge);
}
/* Plain white, no mix-blend-mode — used to auto-invert against whatever
   sat underneath, but that broke down over a busy/light hero photo (nav
   went grey and half-disappeared). The .hero__bg::after scrim now
   guarantees a dark strip under the nav, so a flat white works reliably. */
.nav__links {
  display: flex;
  gap: var(--space-m);
  font-size: 0.78rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0,0,0,0.35);
  transition: opacity 0.2s;
}
.nav__links a { opacity: 0.94; transition: opacity 0.2s, color 0.2s; }
.nav__links a:hover { opacity: 1; color: var(--accent); }
.nav--light .nav__links a:hover { color: var(--accent); }

/* Light variant — used on subpages (about, case studies) where the nav
   sits over a solid light background instead of the dark hero */
.nav--light { mix-blend-mode: normal; background: var(--bg); border-bottom: 1px solid var(--line); }
.nav--light .nav__links { color: var(--dark); text-shadow: none; }
.nav--light .nav__menu span { background: var(--dark); }

/* While the overlay is open: hide the link row, force a clean white
   cross regardless of light/dark nav variant, and drop the blend mode
   so it isn't distorted sitting over the dark overlay */
body.menu-open .nav__links { opacity: 0; visibility: hidden; pointer-events: none; }
body.menu-open .nav { mix-blend-mode: normal; background: transparent !important; border-bottom-color: transparent !important; }
body.menu-open .nav__menu span { background: #fff !important; }

.nav__menu {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-end;
  gap: 6px;
  width: 30px;
  height: 30px;
  margin: -8px -6px -8px 0;  /* pulls the tap target visually into the corner without shrinking it */
  cursor: pointer;
  background: none;
  border: none;
  padding: 10px 4px;
  position: relative;
  z-index: 300;
}
.nav__menu span { display: block; width: 26px; height: 2px; background: #fff; transition: transform 0.3s ease, opacity 0.3s ease, background 0.2s ease; }
.nav__menu.is-open span:first-child { transform: translateY(4px) rotate(45deg); }
.nav__menu.is-open span:last-child  { transform: translateY(-3px) rotate(-45deg); }
.nav__menu:hover span { background: var(--accent); }
.nav--light .nav__menu:hover span { background: var(--accent); }
body.menu-open .nav__menu:hover span { background: var(--accent) !important; }

/* ============================================
   FULL-SCREEN MENU — every case study, one click away
   ============================================ */
.menu-overlay {
  position: fixed;
  inset: 0;
  z-index: 250;
  background: var(--dark);
  color: var(--bg);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--space-xl) var(--edge);
  overflow-y: auto;   /* safety net — if content is ever taller than the
                          viewport (small phones, long lists) it scrolls
                          instead of getting silently clipped top/bottom */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-12px);
  transition: opacity 0.4s ease, transform 0.4s ease, visibility 0.4s;
}
.menu-overlay.is-open { opacity: 1; visibility: visible; transform: translateY(0); }

.menu-overlay__cols { display: flex; gap: var(--space-xxl); flex-wrap: wrap; }
.menu-overlay__group span {
  display: block;
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #8a887f;
  margin-bottom: var(--space-s);
}
.menu-overlay__group a {
  display: block;
  font-family: var(--font-body);
  font-size: clamp(1.3rem, 2.6vw, 2rem);
  font-weight: 600;
  padding: 0.25em 0;
  color: var(--bg);
  opacity: 0.85;
  transition: opacity 0.2s, transform 0.2s, color 0.2s;
}
.menu-overlay__group a:hover { opacity: 1; color: var(--accent); transform: translateX(6px); }
.menu-overlay__foot {
  margin-top: var(--space-xl);
  display: flex;
  gap: var(--space-m);
  font-size: 0.8rem;
  color: #9a988f;
}

/* Hover preview — sits under the contact row, fills in with whichever
   case study's photo is currently hovered in the Selected Work list.
   Hidden/collapsed until script.js flags it active. */
.menu-overlay__preview {
  margin-top: var(--space-m);
  width: fit-content;
  height: fit-content;
  max-width: min(340px, 50vw);
  max-height: 260px;
  border-radius: 14px;
  overflow: hidden;
  background: #1c1c19;
  opacity: 0;
  transform: translateY(8px) scale(0.98);
  transition: opacity 0.3s ease, transform 0.3s ease;
  pointer-events: none;
}
.menu-overlay__preview.is-active { opacity: 1; transform: translateY(0) scale(1); }
.menu-overlay__preview img {
  display: block;
  width: auto;
  height: auto;
  max-width: min(340px, 50vw);
  max-height: 260px;
}
@media (max-width: 760px) {
  .menu-overlay__preview { display: none; }  /* no hover on touch — would just sit stuck open */
}

/* ============================================
   HERO — sticky, "slides up" as intro overtakes it.
   Video-ready background; giant bleeding wordmark.
   ============================================ */
.hero {
  position: sticky;
  top: 0;
  height: 100svh;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  overflow: hidden;
  background: var(--dark);
  color: var(--bg);
}

/* Drop a <video> in here later — this gradient is the placeholder.
   On scroll it insets slightly and rounds off, the way the Arpeggio
   hero image pulls away from the edges as the page moves. */
.hero__bg {
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg, #1a1a17 0%, #0F0F0F 60%);
  z-index: 0;
  transform: scale(calc(1 - var(--hero-progress, 0) * 0.06));
  border-radius: calc(var(--hero-progress, 0) * 18px);
  overflow: hidden;
  will-change: transform;
}
.hero__bg video {
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.55;
}
/* Dark scrim across the top of the hero so the nav stays legible
   regardless of what's underneath it (busy/light portrait photo,
   video frame, whatever) — the nav no longer relies on mix-blend-mode
   to auto-contrast, it's just pinned to white on a guaranteed-dark strip. */
.hero__bg::after {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 22vh;
  background: linear-gradient(180deg, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0) 100%);
  pointer-events: none;
}

/* PORTRAIT TEST — sits above the video, below hero__top text (z-index:2)
   and below the fixed wordmark (z-index:240, in its own stacking context
   so it renders on top regardless). The bottom fade is baked into the PNG
   itself, not done with a CSS mask, so it reads the same everywhere. */
.hero__portrait {
  position: absolute;
  z-index: 1;
  bottom: calc(-32px - 5svh);   /* nudged down a little, then another 5% of viewport height per Jalita's request */
  left: 50%;
  transform: translateX(-50%);
  height: 106svh;   /* 88svh + 20% per Jalita's request */
  pointer-events: none;
}
.hero__portrait picture,
.hero__portrait img {
  display: block;
  height: 100%;
  width: auto;
  max-width: none;   /* overrides the global img{max-width:100%} rule, which
                         was fighting height:100%/width:auto on narrow
                         viewports and squashing the photo horizontally */
}
@media (max-width: 760px) {
  .hero__portrait { height: 74svh; bottom: -120px; }
}

.hero__top {
  position: relative;
  z-index: 2;
  padding: 17vh var(--edge) 0;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-l);
  /* fades out as you scroll through the hero — driven by --hero-progress */
  opacity: calc(1 - var(--hero-progress, 0) * 1.6);
  transform: translateY(calc(var(--hero-progress, 0) * -40px));
  will-change: opacity, transform;
}
.hero__intro {
  max-width: 30ch;
  font-size: clamp(1.4rem, 2.6vw, 2.1rem);
  font-weight: 500;
  line-height: 1.25;
}
.hero__bullet {
  margin-top: var(--space-m);
  font-size: 1.1rem;
  font-weight: 400;
  white-space: nowrap;
  color: #cfcdc7;
  max-width: 28ch;
}
.hero__role {
  text-align: right;
  font-size: clamp(1.2rem, 2vw, 1.7rem);
  font-weight: 500;
  color: #cfcdc7;
  max-width: 20ch;
}

/* Availability line — top-right. Kept in the top block, not along the
   bottom, so nothing competes with the wordmark. */
.hero__avail {
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #8f8d86;
  text-align: right;
}
.hero__avail strong { color: var(--accent); font-weight: 500; }

.hero__deco {
  position: absolute;
  color: #fff;
  opacity: 0.28;
  font-size: 1.1rem;
  font-weight: 300;
  z-index: 1;
  pointer-events: none;
}

/* ============================================
   HERO WORDMARK — Arpeggio move.
   The name is set to span the FULL viewport width (JS measures the
   glyphs and solves for the exact font-size — see fitWordmark() in
   script.js, which is more reliable than guessing a vw value).
   It sits flush to the fold, then on scroll it rises and scales up
   slightly while the hero content above it fades — scroll-LINKED,
   not a one-shot entry animation.
   ============================================ */
/* FIXED, not absolute — the mark has to be able to leave the hero and
   fly up into the nav bar, so it can't be trapped inside it. */
.hero__mark {
  position: fixed;
  left: 0;
  right: 0;             /* full width — required so fitWordmark() has a
                            real viewport measurement to fit against, not
                            a box that's shrink-wrapping its own content */
  top: 0;                /* actual value set by script.js every frame */
  z-index: 240;     /* above page content, below the menu overlay (250) */
  pointer-events: none;
  overflow: visible;
  padding: 0 1.6vw;
  text-align: center;   /* centers the h1 horizontally — no transform
                            needed for this element at all, ever */
  /* NOTE: deliberately NOT using mix-blend-mode here (composites
     unreliably on a huge fixed element — see script.js). Color is
     switched explicitly instead, once the mark docks over a light
     section. */
}
.hero__mark-link {
  pointer-events: auto;
  display: inline-block;
  text-decoration: none;
}
.hero__mark h1 {
  /* inline-block is REQUIRED: it makes the element shrink-wrap the glyphs
     so fitWordmark() can measure the actual text width. As a block it
     reports the container width and the fit silently does nothing. */
  display: inline-block;
  /* REQUIRED: top/left/right/bottom (set per-frame by script.js) only ever
     affect positioned elements. Without this, the h1 stays position:static
     and script.js's wordmark.style.top assignments are silently no-ops —
     the browser just drops them. The h1 then falls back to wherever it
     lands in normal flow inside its fixed parent, i.e. pinned to the very
     top of the viewport. This was the actual bug behind the "wordmark at
     the top instead of the bottom" symptom — not caching. */
  position: relative;
  font-family: var(--font-wordmark);   /* HustleBrush-Regular — the one place on the site that isn't Switzer */
  font-weight: 400;
  text-transform: capitalize;   /* first letter of each word only, not full caps */
  font-size: 13vw;                 /* fallback until fitWordmark() runs */
  letter-spacing: -0.01em;
  line-height: 0.86;               /* tight — sits low, cropped at the fold */
  white-space: nowrap;
  color: #fff;   /* fallback until script.js's per-frame gradient takes over — gradient now starts white */
  opacity: 0;
  /* Size and position travel via real font-size + top changes each scroll
     frame (see script.js), NOT CSS transform:scale(). Scaling a text
     layer with transform rasterizes it once at the start size and stretches
     that bitmap — which is exactly what caused the pixelation when it
     shrank down into the nav. Changing the actual font-size instead makes
     the browser re-render real vector glyphs at every size, so it stays
     crisp all the way down. transform here is ONLY ever the constant
     translateX(-50%) on the parent — never touched per-frame. */
  animation: heroFadeIn 0.9s ease 0.15s both;
  transition: color 0.35s ease;
}
@keyframes heroFadeIn {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .hero__mark h1 { animation: none; opacity: 1; }
  .hero__top { opacity: 1; transform: none; }
}

/* ============================================
   INTRO — short section that slides up over the hero
   ============================================ */
.intro {
  position: relative;
  z-index: 2;
  background: var(--bg);
  padding: var(--space-xxl) var(--edge);
}
.intro__text {
  max-width: 48ch;   /* narrowed so the line wraps to 3 lines on desktop
                        instead of 2 — was 62ch */
  font-size: clamp(1.5rem, 3.2vw, 2.5rem);
  font-weight: 500;
  line-height: 1.35;
}
.intro__text strong { color: var(--accent); font-weight: 600; }
.intro .eyebrow { margin-bottom: var(--space-m); }

/* ============================================
   REVEAL — generic scroll-triggered text animation
   ============================================ */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal.is-visible { opacity: 1; transform: translateY(0); }

/* ============================================
   SELECTED WORK — Baseform-style alternating gallery.
   Dark ground, image-forward, small caption sits ABOVE
   each frame (not overlaid). Rhythm: pair / full / pair / full.
   ============================================ */
.work {
  position: relative;
  z-index: 2;
  background: var(--dark);
  padding: var(--space-xl) 0 var(--space-xxl);
}
.section-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  margin: 0 var(--edge) var(--space-l);
  border-top: 1px solid #2a2a26;
  padding-top: var(--space-m);
  color: var(--bg);
}
.section-head h2 {
  font-size: clamp(1.8rem, 3.5vw, 2.6rem);
  font-weight: 700;
}
.section-head span {
  font-size: 0.8rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #7a7873;
}

.work__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: start;   /* items keep their own height — no forced equal-size pairing */
  gap: var(--space-xl) var(--space-m);
  padding: 0 var(--edge);
}
.work__item { display: block; }
.work__item--full { grid-column: 1 / -1; }

.work__label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 0.85rem;
}
.work__label .work__title { font-size: 1rem; font-weight: 600; color: var(--bg); }
.work__label .work__cat { font-size: 0.85rem; color: #8a887f; }

.work__thumb {
  position: relative;
  aspect-ratio: 4 / 3.3;         /* default — overridden per item below */
  background: linear-gradient(155deg, #1d1d1a 0%, #121210 100%);
  border: 1px solid #2a2a26;
  display: flex;
  align-items: flex-end;
  padding: var(--space-m);
  overflow: hidden;
  transition: border-color 0.35s ease;
}
.work__thumb img {
  width: 100%; height: 100%; object-fit: cover; position: absolute; inset: 0;
  transform-origin: center;
  transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
}
/* Mailbird's hero thumbnail: the box now matches the source photo's own
   ratio (8:5 ≈ 1.6, same as the laptop mockup) instead of the wide 21:11
   banner shape it used to be — so cover-fit needs no letterboxing and no
   side padding at all, the frame is just the size of the actual image. */
.work__thumb--hero img {
  object-fit: cover;
  object-position: right center;
  inset: 0;
}
/* Subtle hover — plain scale, no tilt/perspective. Reference: client sent
   a recording of Taylor Designer's site, where the work-grid images just
   ease up slightly on hover, nothing 3D. */
.work__item:hover .work__thumb img { transform: scale(1.035); }
/* Scrim so the index number + tag stay legible once a real photo sits
   behind them instead of the plain dark placeholder gradient. */
.work__thumb::after {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,0) 55%, rgba(0,0,0,0.55) 100%);
  pointer-events: none;
}
.work__thumb:has(img) .work__thumb__tag { color: #d8d6d0; }

/* size variants — this is what breaks the "two boxes the same" look.
   Paired items sit at deliberately different heights; full-width items
   run tall, edge to edge, like a hero frame rather than a strip. */
@media (min-width: 761px) {
  .work__thumb--tall  { aspect-ratio: 3 / 3.4; }
  .work__thumb--short { aspect-ratio: 16 / 9.5; }
  /* Shrunk ~30% from full grid width so the whole tile — title above it
     included — sits comfortably in the fold on a laptop, instead of
     running edge-to-edge and taller than the viewport. Ratio (8:5) is
     unchanged; the extra room just becomes dark negative space on
     either side, matching the .work section's own background. */
  .work__thumb--hero  { aspect-ratio: 8 / 5; max-width: 77%; margin: 0 auto; }
  /* Align "Selected Work" + the 01—08 counter to the hero tile's own
     inset edges rather than the full grid padding, so the heading sits
     directly above the (now narrower) hero image instead of overhanging
     it on both sides. Same 11.5%-of-content-width inset the hero tile
     gets from its own max-width: 77%; margin: auto. */
  .work .section-head {
    margin-left: calc(var(--edge) + (100% - 2 * var(--edge)) * 0.115);
    margin-right: calc(var(--edge) + (100% - 2 * var(--edge)) * 0.115);
  }
  /* Same inset for the title + category label sitting above each hero
     tile (Mailbird, Servaali) — it was still anchored to the old full
     grid edge, so it no longer lined up with the tile's own left edge
     once the tile got narrower. */
  .work__item--full .work__label {
    margin-left: 11.5%;
  }
  /* Intro paragraph gets the same inward shift so its left edge lines up
     with "Selected Work" and the hero tile below it, instead of sitting
     out at the old full-width edge. */
  .intro {
    padding-left: calc(var(--edge) + (100% - 2 * var(--edge)) * 0.115);
  }
}

.work__thumb__index {
  position: absolute;
  top: -0.08em;
  right: 0.1em;
  font-size: 7.2rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
  color: transparent;
  -webkit-text-stroke: 1px #302f2a;
  transition: -webkit-text-stroke-color 0.35s ease;
  user-select: none;
  pointer-events: none;
}
.work__thumb__tag {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #706f68;
  max-width: 26ch;
}
.work__thumb__tag::before {
  content: '';
  width: 20px; height: 1px;
  background: var(--accent);
  flex-shrink: 0;
}
.work__item:hover .work__thumb { border-color: var(--accent); }
.work__item:hover .work__thumb__index { -webkit-text-stroke-color: var(--accent); }

/* Hide the system pointer over case-study images — the custom cursor
   below replaces it. */
.work__thumb, .work__item:hover .work__thumb { cursor: none; }
.has-fine-cursor, .has-fine-cursor a, .has-fine-cursor button { cursor: none; }

/* ============================================
   CUSTOM CURSOR — ONE element, three states. Small dot everywhere by
   default (the Arpeggio touch), a medium ring over general links/buttons,
   and the original full circle + arrow specifically over case-study
   images. Position is driven from script.js; size/color states are CSS
   so they can transition smoothly instead of popping.
   ============================================ */
.cursor {
  position: fixed;
  top: 0; left: 0;
  width: 14px; height: 14px;
  margin: -7px 0 0 -7px;
  border-radius: 50%;
  border: none;
  background: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
  z-index: 400;
  pointer-events: none;
  opacity: 0;
  /* transform (position + scale) is owned by script.js every frame —
     only size/color/opacity are transitioned here, or the two fight. */
  transition: opacity 0.28s ease, width 0.25s ease, height 0.25s ease,
              margin 0.25s ease, background 0.25s ease, border-color 0.25s ease;
}
.cursor.is-visible { opacity: 1; }
.cursor.is-hovering {
  width: 40px; height: 40px; margin: -20px 0 0 -20px;
  background: transparent;
  border: 1.5px solid var(--accent);
}
.cursor.is-active {
  width: 84px; height: 84px; margin: -42px 0 0 -42px;
  background: rgba(255, 46, 136, 0.08);
  border: 1px solid var(--accent);
  backdrop-filter: blur(2px);
}
.cursor svg { width: 26px; height: 26px; stroke: var(--accent); fill: none; stroke-width: 1.5; opacity: 0; transition: opacity 0.2s; }
.cursor.is-active svg { opacity: 1; animation: cursorNudge 1.4s ease-in-out infinite; }
@keyframes cursorNudge {
  0%, 100% { transform: translate(0, 0); }
  50%      { transform: translate(3px, -3px); }
}
@media (max-width: 900px), (pointer: coarse) {
  .cursor { display: none; }
  .has-fine-cursor, .has-fine-cursor a, .has-fine-cursor button { cursor: auto; }
  .work__thumb, .work__item:hover .work__thumb { cursor: pointer; }
}

/* ============================================
   OTHER WORK — continuous drifting scroll of holding visuals for
   engagements without a case study page yet. Deliberately not
   clickable: plain gray cards in mixed sizes, no captions, no hover
   affordance. Drop an <img> into any .otherwork__card once you have
   real artwork — the sizing stays put either way.
   ============================================ */
/* position + z-index are REQUIRED, not decorative. The hero is
   position:sticky z-index:1 and stays stuck for the whole page scroll,
   so any section left unpositioned paints UNDERNEATH it and reads as a
   blank black panel. Every other section on this page sets these two
   properties for the same reason — this one was missing them. */
.otherwork {
  position: relative;
  z-index: 2;
  background: var(--dark);
  padding: var(--space-xl) 0 var(--space-xxl);
  overflow: hidden;
}
.otherwork .section-head { border-top: none; color: var(--bg); }
.otherwork .section-head span { color: #8a8880; }

.otherwork__row {
  overflow: hidden;
}
.otherwork__track {
  display: flex;
  align-items: center;
  gap: var(--space-m);
  width: max-content;
  padding: var(--space-m) 0;
  animation: scrollLeft 110s linear infinite;
}
/* pauses only when the pointer is actually over a card/image — not the
   title or the gaps between cards — per :has(), which has full support
   across current browsers */
.otherwork__track:has(.otherwork__card:hover) { animation-play-state: paused; }

.otherwork__card {
  flex-shrink: 0;
  height: 300px;
  border-radius: 20px;
  background: #1c1c1a;
  cursor: default;
  overflow: hidden;
  position: relative;
}
.otherwork__card img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* three shapes only — square, landscape (4:3), tall (3:4) — sized off the
   real photos' native ratios, all sharing the row's fixed height */
.otherwork__card--square { width: 300px; }
.otherwork__card--landscape { width: 400px; }
.otherwork__card--tall { width: 225px; }

@media (max-width: 760px) {
  .otherwork__card { height: 200px; }
  .otherwork__card--square { width: 200px; }
  .otherwork__card--landscape { width: 267px; }
  .otherwork__card--tall { width: 150px; }
}

/* ============================================
   PHONE SHOWCASE — centered floating phone-frame video loop,
   used for live-site walkthroughs (e.g. Matsmart mobile UX)
   ============================================ */
.phone-showcase {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: center;
  padding: var(--space-l) var(--edge);
}
.phone-showcase__frame {
  width: min(300px, 62vw);
}
.phone-showcase__frame video {
  width: 100%;
  height: auto;
  display: block;
}

/* ============================================
   QUOTE SECTION — one big statement, dark, links to About
   ============================================ */
.quote {
  position: relative;
  z-index: 2;
  background: var(--dark);
  color: var(--bg);
  padding: var(--space-xl) var(--edge);
  overflow: hidden;
}
.quote__text {
  font-size: clamp(1.6rem, 4vw, 3rem);
  font-weight: 500;
  line-height: 1.3;
  max-width: 24ch;
  position: relative;
  z-index: 1;
}
.quote__link { position: relative; z-index: 1; }
/* PORTRAIT TEST — right side headshot, plain (no melt fade — that's the
   hero's treatment). Hidden below 1000px so it never collides with the
   text column, which is the only thing that matters on mobile. */
.quote__portrait {
  position: absolute;
  right: 0;
  top: calc(50% + 32px);
  transform: translateY(-50%);
  height: 552px; /* 460px + 20% per Jalita's request, plus shifted down */
  pointer-events: none;
}
.quote__portrait img { display: block; height: 100%; width: auto; }
@media (max-width: 1000px) {
  .quote__portrait { display: none; }
}
.quote__link {
  display: inline-block;
  margin-top: var(--space-l);
  font-size: 0.85rem;
  letter-spacing: 0.03em;
  border-bottom: 1px solid var(--bg);
  padding-bottom: 2px;
}

/* ============================================
   TESTIMONIALS — real quotes, single-row carousel, 2 cards visible.
   Black cards: circular photo, quote in quotation marks, name in
   Switzer, role in warm grey. Arrow nav + dot progress up top.
   ============================================ */
.testimonials {
  position: relative;
  z-index: 2;
  background: var(--bg);
  padding: var(--space-xl) var(--edge) var(--space-xxl);
}
.testimonials__nav {
  display: flex;
  gap: var(--space-xs);
  flex-shrink: 0;
}
.testimonials__btn {
  width: 44px;
  height: 44px;
  border: 1px solid var(--line);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  background: transparent;
  transition: background 0.2s, border-color 0.2s, opacity 0.2s;
  flex-shrink: 0;
}
.testimonials__btn svg { width: 16px; height: 16px; stroke: var(--dark); stroke-width: 1.6; fill: none; }
.testimonials__btn:hover:not(:disabled) { background: var(--dark); border-color: var(--dark); }
.testimonials__btn:hover:not(:disabled) svg { stroke: var(--bg); }
.testimonials__btn:disabled { opacity: 0.3; cursor: default; }

.testimonials__viewport {
  overflow: hidden;
  margin-top: var(--space-l);
}
.testimonials__grid {
  display: flex;
  align-items: flex-start;   /* was stretch (the flex default) — every card in
                                 the whole scrollable row was being forced to
                                 match the height of the single longest quote
                                 anywhere in the deck, not just its visible
                                 neighbour. That's the "weird spacing" — short
                                 quotes got a huge empty gap before the name.
                                 flex-start lets each card size to its own
                                 content. */
  gap: var(--space-m);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  touch-action: pan-y;
}
.testimonials__grid::-webkit-scrollbar { display: none; }

.testimonial {
  flex: 0 0 calc((100% - var(--space-m)) / 2);
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: var(--space-l) var(--space-l) var(--space-m);
  min-height: 0;   /* card hugs its own content instead of forcing a fixed
                       height — a short quote makes a shorter card */
}
.testimonial__avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  overflow: hidden;
  margin-bottom: var(--space-m);
  flex-shrink: 0;
}
.testimonial__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

.testimonial__quote {
  font-size: 1.15rem;
  font-weight: 500;
  line-height: 1.5;
  color: var(--dark);
  flex: 1;
}
.testimonial__quote::before { content: "\201C"; }
.testimonial__quote::after { content: "\201D"; }
.testimonial__who {
  margin-top: var(--space-m);
  padding-top: var(--space-s);
}
.testimonial__who strong {
  display: block;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 1rem;
  line-height: 1.2;
  color: var(--dark);
  margin-bottom: 2px;
}
.testimonial__who span {
  display: block;
  font-size: 0.8rem;
  color: #7a7873;
}

/* Dark-card variant kept available for reuse elsewhere, opt-in only —
   the default .testimonial is light again per Jalita's latest call. */
.testimonial--dark {
  background: var(--dark);
  border: none;
}
.testimonial--dark .testimonial__quote,
.testimonial--dark .testimonial__who strong { color: var(--bg); }
.testimonial--dark .testimonial__who span { color: #9a988f; }

.testimonials__dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: var(--space-l);
}
.testimonials__dot {
  width: 7px;
  height: 7px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--line);
  cursor: pointer;
  transition: background 0.2s, transform 0.2s;
}
.testimonials__dot.is-active { background: var(--accent); transform: scale(1.3); }

@media (max-width: 760px) {
  .testimonial { flex: 0 0 100%; min-height: 0; }
  .section-head { flex-direction: column; align-items: flex-start; gap: var(--space-m); }
}

/* ABOUT-PAGE TESTIMONIALS — pull-quote slider, dark section bg, 3
   cards visible. Cards get a lighter shade than the section itself
   so they read as distinct objects, with a bigger/bolder headline
   quote instead of the full paragraph. Nav arrows + dots flip to a
   light variant since the section background is dark. Uses CSS grid
   (not flex-basis) for the 3-up track — more reliable across browsers
   than the flex calc() approach used on the homepage carousel. */
.testimonials--pull .section-head { border-top: none; }
.testimonials--pull .testimonials__grid { display: grid; grid-auto-flow: column; grid-auto-columns: calc((100% - 2 * var(--space-m)) / 3); }

.testimonials--pull .testimonials__btn { border-color: rgba(255,255,255,0.35); background: transparent; }
.testimonials--pull .testimonials__btn svg { stroke: var(--bg); }
.testimonials--pull .testimonials__btn:hover:not(:disabled) { background: rgba(255,46,136,0.15); border-color: var(--accent); }
.testimonials--pull .testimonials__btn:hover:not(:disabled) svg { stroke: var(--bg); }

.testimonials--pull .testimonials__dot { background: rgba(255,255,255,0.25); }
.testimonials--pull .testimonials__dot.is-active { background: var(--accent); }

.testimonial--pull {
  background: #1c1c1a;
  border: 1px solid rgba(255,255,255,0.08);
  min-height: 0;   /* hugs content — short pull-quotes get a shorter card
                       instead of every card matching the longest one */
}
.testimonial--pull .testimonial__quote {
  font-size: clamp(1.15rem, 1.6vw, 1.35rem);
  font-weight: 600;
  line-height: 1.3;
}

@media (max-width: 900px) {
  .testimonials--pull .testimonials__grid { grid-auto-columns: calc((100% - var(--space-m)) / 2); }
}
@media (max-width: 760px) {
  .testimonials--pull .testimonials__grid { grid-auto-columns: 100%; }
  .testimonial--pull { min-height: 0; }
}

/* ============================================
   STATS — dark, animated count-up
   ============================================ */
.stats {
  position: relative;
  z-index: 2;
  background: var(--dark);
  color: var(--bg);
  padding: var(--space-xl) var(--edge);
}
.stats .eyebrow { text-align: center; color: var(--accent); margin-bottom: var(--space-l); }
.stats__row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0;
}
.stat {
  flex: 1 1 180px;
  text-align: center;
  padding: var(--space-m) var(--space-m);
  border-left: 1px solid #2a2a26;
}
.stats__row .stat:first-child { border-left: none; }
.stat__label {
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #9a988f;
  min-height: 2.4em;
}
.stat__number {
  margin-top: var(--space-s);
  font-size: clamp(2.2rem, 4.5vw, 3.4rem);
  font-weight: 800;
}
@media (max-width: 720px) {
  .stat { border-left: none; border-top: 1px solid #2a2a26; flex: 1 1 45%; }
}

/* ============================================
   BRAND MARQUEE — continuous horizontal scroll, no spin
   ============================================ */
.marquee {
  position: relative;
  z-index: 2;
  background: var(--bg);
  border-top: 1px solid var(--line);
  padding: var(--space-l) 0;
  overflow: hidden;
}
.marquee .eyebrow { text-align: center; padding: 0 var(--edge); margin-bottom: var(--space-m); }
.marquee__row {
  overflow: hidden;
  mask-image: linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
}
.marquee__row + .marquee__row { margin-top: var(--space-l); }
.marquee__track {
  display: flex;
  width: max-content;
  animation: scrollLeft 70s linear infinite;
}
.marquee__track--right { animation-name: scrollRight; animation-duration: 85s; }
.marquee:hover .marquee__track { animation-play-state: paused; }
.marquee__item {
  padding: 0 var(--space-l);
  display: flex;
  align-items: center;
  justify-content: center;
  height: 96px;
  white-space: nowrap;
}
/* Logo files are trimmed to tight bounding boxes, so a single height would
   make wide wordmarks look huge and square marks look tiny. These three
   classes normalise by optical mass instead: the wider the logo, the
   shorter it sits. Result reads as "all the same size" to the eye. */
.marquee__item img {
  width: auto;
  max-width: 210px;
  object-fit: contain;
  filter: grayscale(1) brightness(0);
  opacity: 0.75;
  transition: opacity 0.2s;
}
.marquee__item img.logo--xs     { height: 30px; }
.marquee__item img.logo--wide   { height: 38px; }
.marquee__item img.logo--mid    { height: 50px; }
.marquee__item img.logo--square { height: 66px; }
.marquee__item:hover img { opacity: 1; }
@media (max-width: 760px) {
  .marquee__item { height: 68px; padding: 0 var(--space-m); }
  .marquee__item img { max-width: 140px; }
  .marquee__item img.logo--xs     { height: 20px; }
  .marquee__item img.logo--wide   { height: 26px; }
  .marquee__item img.logo--mid    { height: 34px; }
  .marquee__item img.logo--square { height: 46px; }
}
@keyframes scrollLeft {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}
@keyframes scrollRight {
  0%   { transform: translateX(-50%); }
  100% { transform: translateX(0); }
}

/* ============================================
   BRING — "what I bring to the table" section on About. Illustration
   + centered headline; adjectives scatter out from dead-center on
   scroll-in (desktop only — below 900px they just fade/scale in the
   flow, since there's no room to scatter them).
   ============================================ */
.bring {
  padding: var(--space-xl) var(--edge);
  text-align: center;
}
.bring__field { position: relative; max-width: 1300px; height: clamp(480px, 46vw, 680px); margin: 0 auto; }
.bring__center {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: min(90%, 640px);
  z-index: 2;
}
.bring__illustration { width: clamp(84px, 9vw, 130px); height: auto; margin: 0 auto var(--space-s); display: block; }
.bring__title { font-size: clamp(2rem, 5vw, 3.4rem); font-weight: 800; letter-spacing: -0.01em; }
.bring__subtitle { max-width: 42ch; margin: 1rem auto 0; color: #4a4a47; font-size: 1.05rem; }
.bring-word {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%) scale(0.4);
  opacity: 0;
  display: inline-flex;
  padding: 0.65em 1.3em;
  border-radius: 999px;
  background: rgba(255, 46, 136, 0.09);
  font-family: var(--font-body);
  font-weight: 500;
  letter-spacing: 0;
  font-size: clamp(0.85rem, 1.3vw, 1.05rem);
  color: var(--dark);
  white-space: nowrap;
  transition: transform 0.9s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.7s ease-out, color 0.25s ease;
}
.bring-word:hover { color: var(--accent); }
.bring-word.is-visible {
  opacity: 1;
  transform: translate(calc(-50% + var(--tx)), calc(-50% + var(--ty))) rotate(var(--rot)) scale(1);
}
@media (max-width: 900px) {
  .bring__field {
    height: auto; position: static;
    display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
    gap: 0.6rem 1.1rem;
  }
  .bring__center { position: static; transform: none; width: 100%; flex-basis: 100%; margin-bottom: var(--space-s); }
  .bring-word { position: static; transform: scale(0.4); }
  .bring-word.is-visible { transform: scale(1) !important; }
}

/* ============================================
   EYEBROW — shared small label
   ============================================ */
.eyebrow {
  font-size: 0.78rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}

/* ============================================
   FOOTER — Mike Bennet style: big prompt, quiet meta row
   ============================================ */
.footer {
  position: relative;
  z-index: 2;
  background: var(--dark);
  color: var(--bg);
  padding: var(--space-xxl) var(--edge) var(--space-m);
}
.footer__top {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--space-l);
  padding-bottom: var(--space-xl);
}
.footer__prompt {
  font-size: clamp(2rem, 5vw, 4rem);
  font-weight: 800;
  max-width: 14ch;
  line-height: 1.05;
}
.footer__email {
  font-size: clamp(1rem, 1.6vw, 1.25rem);
  border-bottom: 1px solid var(--bg);
  padding-bottom: 3px;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.footer__email:hover { color: var(--accent); border-color: var(--accent); }
.footer__cols {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-m);
  padding-top: var(--space-m);
  font-size: 0.82rem;
  color: #9a988f;
}
.footer__cols a { opacity: 0.85; }
.footer__cols a:hover { opacity: 1; }

.footer__social {
  display: flex;
  gap: var(--space-s);
}
.footer__social a {
  width: 38px; height: 38px;
  border: 1px solid #33332f;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.2s, border-color 0.2s;
}
.footer__social a:hover { background: var(--bg); border-color: var(--bg); }
.footer__social a:hover svg { stroke: var(--dark); }
.footer__social svg { width: 16px; height: 16px; stroke: var(--bg); transition: stroke 0.2s; }

/* ============================================
   FOOTER GRID — Arpeggio-style three-column footer, kept dark to match
   the rest of the site (Arpeggio's own version runs light).
   ============================================ */
.footer__grid {
  display: grid;
  grid-template-columns: 1fr 1.2fr 1fr;
  gap: var(--space-l);
  margin-top: var(--space-xl);
  padding-top: var(--space-l);
  border-top: 1px solid #2a2a26;
}
.footer__col { display: flex; flex-direction: column; gap: 0.65rem; }
.footer__col a { opacity: 0.85; font-size: 0.95rem; transition: opacity 0.2s, color 0.2s; }
.footer__col a:not(.footer__row):hover { opacity: 1; color: var(--accent); }
.footer__col a.footer__logo {
  font-family: var(--font-wordmark);
  font-size: 1.3rem;
  color: var(--bg);
  margin-bottom: 0.5rem;
  opacity: 1;
  transition: color 0.2s;
}
.footer__col a.footer__logo:hover { color: var(--accent); opacity: 1; }
.footer__colhead {
  font-size: 0.72rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #8f8d86;
  margin-bottom: 0.3rem;
}

/* middle "currently based / local time" block — plain, no card */
.footer__location-text { font-size: 1.05rem; color: var(--bg); max-width: 26ch; }
.footer__location-text--sub { margin-top: 0.3rem; opacity: 0.7; }
.footer__location-sub { font-size: 0.85rem; color: #8f8d86; max-width: 26ch; margin-top: -0.3rem; }
.footer__clockrow { display: flex; align-items: center; gap: var(--space-l); margin-top: 0.4rem; }
.footer__analog { width: 150px; height: 150px; flex-shrink: 0; order: 2; margin-top: -14px; margin-left: 0.6rem; }
.footer__clockinfo { order: 1; }
.clock-face { fill: none; stroke: #3a3a35; stroke-width: 1.5; }
.clock-tick { stroke: #55554e; stroke-width: 1.5; }
.clock-hand { stroke-linecap: round; }
.clock-hand--hour { stroke: var(--bg); stroke-width: 3.5; }
.clock-hand--minute { stroke: var(--bg); stroke-width: 2.5; }
.clock-hand--second { stroke: var(--accent); stroke-width: 1.2; }
.clock-pin { fill: var(--accent); }
.footer__clock {
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  font-weight: 600;
  color: var(--bg);
  letter-spacing: 0.01em;
}
.footer__tz { font-size: 0.75rem; color: #8f8d86; }

/* social list — arrow rows, Arpeggio-style. Hover fills the whole strip
   pink with black text/icon, not just a color swap. */
.footer__col--social { gap: 0; }
.footer__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.7rem 0.6rem;
  margin: 0 -0.6rem;
  border-bottom: 1px solid #2a2a26;
  opacity: 0.9;
  color: var(--bg);
  transition: opacity 0.2s, background 0.2s, color 0.2s;
}
.footer__row-left { display: flex; align-items: center; gap: 0.7rem; }
.footer__row-left svg { width: 18px; height: 18px; stroke: currentColor; flex-shrink: 0; }
.footer__row-arrow {
  transform: translateX(-4px);
  opacity: 0;
  transition: transform 0.2s, opacity 0.2s;
}
.footer__row:hover {
  opacity: 1;
  background: var(--accent);
  color: var(--dark);
  border-bottom-color: var(--accent);
}
.footer__row:hover .footer__row-arrow { transform: translateX(0); opacity: 1; }

.footer__fine {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-s);
  margin-top: var(--space-l);
  padding-top: var(--space-m);
  border-top: 1px solid #2a2a26;
  font-size: 0.8rem;
  color: #8f8d86;
}
@media (max-width: 760px) {
  .footer__grid { grid-template-columns: 1fr; }
  .footer__analog { width: 120px; height: 120px; margin-top: 0; margin-left: 0; }
}

/* ============================================
   RESPONSIVE — pass 2, mobile-first fixes across pages
   ============================================ */
@media (max-width: 760px) {
  :root { --space-xxl: 6rem; --space-xl: 4rem; --edge: 7vw; }

  .nav__links { display: none; }               /* mobile relies on the overlay menu only */
  .nav { padding: var(--space-s) var(--edge); }
  /* With .nav__links hidden, .nav__menu was the only flex child left —
     justify-content:space-between has nothing to space it FROM, so it
     collapsed to flex-start (left edge) instead of staying on the right. */
  .nav__menu { margin-left: auto; }

  .hero__top { padding-top: 10vh; gap: var(--space-m); }
  .hero__intro { font-size: 1.5rem; max-width: 100%; }
  .hero__bullet { white-space: normal; }
  .hero__bullet-break { display: block; }   /* "beauty that isn't" onto its own line, mobile only */
  .hero__role { text-align: left; margin-top: var(--space-s); }
  .hero__mark h1 { font-size: 15vw; }
  /* "Available now" moves out of the top text block entirely and sits
     centered near the bottom of the hero, just under where the giant
     "Jalita Aspelin" wordmark rests at scroll position 0 (REST_BOTTOM_PAD
     in script.js reserves 120px above the hero's bottom edge for it) —
     desktop keeps it pinned top-right per the original layout. .hero
     is position:sticky, which is a positioning context, so this absolute
     box is relative to the hero itself regardless of .hero__top's own
     (unpositioned) box. */
  .hero__top > div:last-child {
    position: absolute;
    left: 0; right: 0;
    bottom: 64px;
  }
  .hero__avail { text-align: center; }

  /* Overlay content was overflowing the top of small phone screens
     (iPhone 16 Pro etc.) because it was vertically centered with full
     desktop spacing — the top of the "Selected Work" list got clipped
     above the fold. Switched to flex-start with real top clearance for
     the close button, and tightened spacing so the full menu (9 case
     studies + Site column + foot row) fits on one screen without
     scrolling on most devices. */
  .menu-overlay { justify-content: flex-start; padding-top: 5.5rem; padding-bottom: 2.5rem; }
  .menu-overlay__cols { gap: var(--space-m); flex-direction: column; }
  .menu-overlay__group span { margin-bottom: 0.5rem; }
  .menu-overlay__group a { font-size: 1.15rem; padding: 0.16em 0; }
  .menu-overlay__foot { margin-top: var(--space-m); flex-wrap: wrap; gap: var(--space-s); }

  .work__grid { grid-template-columns: 1fr; gap: var(--space-l); padding: 0 var(--edge); }
  .work__thumb { aspect-ratio: 4 / 3.2; }
  /* Mailbird + Servaali's hero tiles were falling back to the generic
     4:3.2 mobile ratio above instead of their own 8:5 (the ratio their
     actual source photos are cropped to on desktop) — cover-fit was
     slicing the sides off both, cropping out card copy/UI. Restoring
     their own ratio on mobile too fixes it the same way it's already
     fixed on desktop. */
  .work__thumb--hero { aspect-ratio: 8 / 5; }
  .work__thumb__index { font-size: 4.6rem; }

  .testimonials__grid { grid-template-columns: 1fr; gap: var(--space-l); }

  .about-hero__title { max-width: 100%; }
  .facts__grid { grid-template-columns: 1fr; }

  .stats__row { flex-direction: column; }
  .stat { border-left: none !important; border-top: 1px solid #2a2a26; }
  .stat:first-child { border-top: none; }

  .footer__top { flex-direction: column; align-items: flex-start; }
  .footer__cols { flex-direction: column; align-items: flex-start; gap: var(--space-s); }
}

/* ============================================
   CASE STUDY PAGE — meta table, sliders, statement blocks
   (used on project-*.html pages)
   ============================================ */
.case-hero {
  padding: var(--space-xxl) var(--edge) var(--space-l);
  border-bottom: 1px solid var(--line);
}
.case-hero__eyebrow {
  font-size: 0.85rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #7a7873;
  margin-bottom: var(--space-s);
}
.case-hero__title {
  font-size: clamp(2.5rem, 6vw, 5rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1;
  max-width: 16ch;
}
.case-hero__subtitle {
  margin-top: var(--space-s);
  font-size: clamp(1rem, 1.4vw, 1.25rem);
  color: #4a4a47;
  max-width: 42ch;
}

.slider { position: relative; margin: var(--space-l) 0; }
.slider__track {
  display: flex; gap: var(--space-xs);
  overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; padding: 0 var(--edge);
}
.slider__track::-webkit-scrollbar { display: none; }
.slider__frame {
  position: relative;
  flex: 0 0 auto; width: min(84vw, 900px); aspect-ratio: 16 / 10;
  scroll-snap-align: start;
  background: linear-gradient(135deg, #d8d5cb, #c4c1b6);
  display: flex; align-items: center; justify-content: center;
  color: #7a7873; font-size: 0.85rem; text-align: center; padding: var(--space-m);
  overflow: hidden;
}
.slider__frame img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.slider__nav { display: flex; gap: var(--space-xs); padding: var(--space-s) var(--edge) 0; justify-content: flex-end; }
.slider__btn {
  width: 40px; height: 40px; border: 1px solid var(--dark); border-radius: 50%;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  background: transparent; transition: background 0.2s, color 0.2s;
}
.slider__btn:hover { background: var(--dark); color: var(--bg); }

.meta-table { padding: 0 var(--edge); margin: var(--space-l) 0; }
.meta-row {
  display: grid; grid-template-columns: 1fr 2fr; gap: var(--space-m);
  padding: var(--space-s) 0; border-top: 1px solid var(--line); font-size: 0.85rem;
}
.meta-row:first-child { border-top: none; }
.meta-row:last-child { border-bottom: 1px solid var(--line); }
.meta-row dt { text-transform: uppercase; letter-spacing: 0.2em; color: #7a7873; font-weight: 500; }
.meta-row dd { text-align: right; color: var(--dark); }
.meta-row dd a { color: var(--dark); text-decoration: underline; text-underline-offset: 2px; }
.meta-row dd a:hover { color: var(--accent); }

.statement { padding: var(--space-xl) var(--edge); }
.statement__head {
  font-size: clamp(1.8rem, 5vw, 3.5rem); font-weight: 700; line-height: 1.05;
  letter-spacing: -0.01em; margin-bottom: var(--space-l);
}
.statement__head .grey { color: var(--grey); display: block; }
.statement__head .dark { color: var(--dark); display: block; }
.statement__body { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-l); }
/* Lead paragraph in the left column. 22ch was far too tight — it broke
   into a narrow ribbon of 3-4 word lines. 34ch keeps it clearly shorter
   than the body column on the right while still reading as a paragraph. */
.statement__lead { font-size: clamp(1.1rem, 1.6vw, 1.4rem); font-weight: 600; max-width: 34ch; }
.statement__text { font-size: 1rem; color: #4a4a47; max-width: 46ch; }
/* Right-hand column when it holds more than one paragraph (Challenge,
   Mandate) — a single grid item wrapping both .statement__text
   paragraphs so the 2-column layout above still only sees 2 children. */
.statement__text-group .statement__text + .statement__text { margin-top: 1.1em; }

/* ============================================
   THE WORK — simple numbered list section, added between the Mandate
   and the outcome. Deliberately plain (no dividers) — the client asked
   to reduce hairlines elsewhere on this page.
   ============================================ */
.work-list { padding: var(--space-xl) var(--edge); }
.work-list .eyebrow { margin-bottom: var(--space-l); }
.work-list__item + .work-list__item { margin-top: var(--space-l); }
.work-list__title { font-size: clamp(1.2rem, 2.2vw, 1.6rem); font-weight: 700; color: var(--dark); }
.work-list__desc { margin-top: 0.35em; font-size: 1rem; color: #4a4a47; max-width: 50ch; }

/* ============================================
   CASE STUDY STAT ROW — outcome numbers, same visual language as the
   homepage .stats block (flex row, vertical dividers, big number under
   a label) but adapted for light theme instead of dark.
   ============================================ */
.case-stats-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0;
  padding: var(--space-l) var(--edge) var(--space-xl);
  border-top: 1px solid var(--line);
}
.case-stat {
  flex: 1 1 180px;
  text-align: center;
  padding: var(--space-m);
  border-left: 1px solid var(--line);
}
.case-stats-row .case-stat:first-child { border-left: none; }
.case-stat__number { font-size: clamp(1.6rem, 3vw, 2.4rem); font-weight: 800; color: var(--dark); }
.case-stat__label { margin-top: 0.5rem; font-size: 0.82rem; color: #6a6864; max-width: 20ch; margin-left: auto; margin-right: auto; }
@media (max-width: 720px) {
  .case-stats-row { flex-direction: column; }
  .case-stat { border-left: none; border-top: 1px solid var(--line); }
  .case-stats-row .case-stat:first-child { border-top: none; }
}

@media (max-width: 720px) {
  .statement__body { grid-template-columns: 1fr; }
  .meta-row { grid-template-columns: 1fr; gap: 0.25rem; }
  .meta-row dd { text-align: left; }
  .hero__top { flex-direction: column; }
  .hero__role { text-align: left; }
}

/* ============================================
   ABOUT PAGE
   ============================================ */
.about-hero {
  padding: var(--space-xxl) var(--edge) var(--space-l);
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--space-l);
  overflow: hidden;
}
.about-hero__text { position: relative; z-index: 2; flex: 1 1 auto; }
.about-hero__portrait {
  flex: 0 0 auto;
  width: clamp(286px, 35vw, 506px);   /* +10% per Jalita's request */
  align-self: flex-end;
  margin-bottom: -3.5rem;
}
.about-hero__portrait picture,
.about-hero__portrait img { display: block; width: 100%; height: auto; }
@media (max-width: 800px) {
  /* Title + role used to sit below the portrait (order:-1 on mobile),
     which pushed them well below the fold on a phone. Text now comes
     first — matches DOM order — with less top padding and a smaller
     photo so the whole hero is more compact. */
  .about-hero { flex-direction: column; align-items: center; text-align: center; padding-top: var(--space-l); }
  .about-hero__portrait { width: 64vw; margin: var(--space-m) auto 0; }
}
.about-hero__title {
  font-size: clamp(2.5rem, 6vw, 5rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.04;
  max-width: 18ch;
}
.about-hero__role {
  margin-top: var(--space-s);
  font-size: clamp(1.1rem, 1.8vw, 1.4rem);
  color: #4a4a47;
}
.about-body {
  padding: var(--space-l) var(--edge) var(--space-xxl);
  display: grid;
  grid-template-columns: 1fr 1.6fr;
  gap: var(--space-xl);
  border-top: 1px solid var(--line);
}
.about-body__label {
  font-size: 0.78rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #7a7873;
}
.about-body__text {
  font-size: clamp(1.1rem, 1.6vw, 1.35rem);
  line-height: 1.6;
  max-width: 62ch;
}
.about-body__text p + p { margin-top: var(--space-m); }
@media (max-width: 800px) {
  .about-body { grid-template-columns: 1fr; }
}

.facts { padding: 0 var(--edge) var(--space-xxl); }
.facts__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-m) var(--space-l);
  border-top: 1px solid var(--line);
  padding-top: var(--space-l);
}
.fact dt { font-size: 0.78rem; letter-spacing: 0.2em; text-transform: uppercase; color: #7a7873; }
.fact dd { margin-top: 0.4rem; font-size: 1.05rem; font-weight: 500; }
.fact__places span:not(:last-child)::after { content: " · "; }
@media (max-width: 700px) {
  .facts__grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 500px) {
  /* Places list gets cramped/wrappy at phone width — one place per line,
     no dot separator, desktop (and tablet) keep the inline "·" list. */
  .fact__places { display: flex; flex-direction: column; gap: 0.15rem; }
  .fact__places span:not(:last-child)::after { content: ''; }
}

.artifacts__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem; }
.artifacts__tile {
  aspect-ratio: 1;
  background: #232320;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #6a6864;
  font-size: 0.75rem;
  text-align: center;
  padding: 1rem;
}
@media (max-width: 700px) {
  /* Three-across got cramped on phones — stack them so each preview
     tile has real room to breathe once photos replace these
     placeholders. */
  .artifacts__grid { grid-template-columns: 1fr; gap: 0.75rem; }
  .artifacts__tile { aspect-ratio: 16 / 10; }
}

/* ============================================
   CASE STUDY — SCROLL REVEAL MEDIA
   Used ONLY on project-test.html — an experiment mirroring the
   Reebok-style case study layout (full-bleed hero image, two-column
   overview + meta list, full-bleed video, large image, two-up portraits)
   with a scroll-linked reveal: each block fades/scales in once as it
   enters the viewport, then its image drifts with a subtle parallax
   offset (slower than the page scroll) while it's on screen. See the
   "SCROLL REVEAL MEDIA" block in script.js for the JS half of this.
   Nothing here is referenced by any other page, so it can't affect them.
   ============================================ */
.case-overview {
  padding: var(--space-xl) var(--edge);
  border-top: 1px solid var(--line);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-xl);
}
.case-overview__label {
  font-size: 0.78rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #7a7873;
  margin-bottom: var(--space-s);
}
.case-overview__text {
  font-size: clamp(1.05rem, 1.5vw, 1.3rem);
  line-height: 1.6;
  color: #2a2a26;
  max-width: 52ch;
}
.case-overview__text p + p { margin-top: var(--space-m); }
.case-overview__meta { border-top: 1px solid var(--line); padding-top: var(--space-m); }
.case-overview__meta .meta-row:first-child { border-top: none; }

.reveal-media {
  position: relative;
  overflow: hidden;
  border-radius: 0;   /* was 4px — that tiny radius was showing a grey
                         sliver of the dark background at each corner
                         wherever the image didn't land pixel-perfect
                         over the rounded clip. Sharp corners, no gap. */
  background: #1c1c19;
  opacity: 0;
  transform: scale(0.9) translateY(48px);   /* more dramatic entrance —
                                                was scale(0.96)/24px */
  /* Wave-to-flat top edge, straight from the reference clip: as the block
     scrolls up, its top edge starts as a soft wave and settles flat by
     the time it's fully in view. Both polygons have the same point count
     (10 across the top + 2 bottom corners) so the browser can interpolate
     between them directly — no JS needed, it rides the same is-visible
     class swap as the fade/scale. No corner rounding here on purpose —
     sharp rectangle once flat. */
  clip-path: polygon(
    0% 14%, 11% 4%, 22% 16%, 33% 3%, 44% 15%,
    56% 2%, 67% 14%, 78% 4%, 89% 16%, 100% 5%,
    100% 100%, 0% 100%
  );
  transition: opacity 1s cubic-bezier(0.16, 1, 0.3, 1),
              transform 1.1s cubic-bezier(0.16, 1, 0.3, 1),
              clip-path 1.1s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal-media.is-visible {
  opacity: 1;
  transform: scale(1) translateY(0);
  clip-path: polygon(
    0% 0%, 11% 0%, 22% 0%, 33% 0%, 44% 0%,
    56% 0%, 67% 0%, 78% 0%, 89% 0%, 100% 0%,
    100% 100%, 0% 100%
  );
}
.reveal-media__inner {
  position: absolute;
  inset: 0;   /* exact match to the box — top/bottom overshoot used to skew
                 this away from the image's real aspect ratio, forcing
                 object-fit:cover to zoom in and slice off the sides */
  transform: scale(1.06);   /* baseline overscan, matched by script.js —
                                covers the full parallax travel range so
                                no background edge is ever exposed */
  will-change: transform;
}
/* Opt out of the parallax overscan entirely — used on the Mailbird opening
   shot, where the 6% zoom was cropping the bottom of the laptop out of
   frame. script.js skips these elements in the parallax loop too. */
/* inset:-1px (not 0) — a hairline of grid-column rounding was showing
   the dark placeholder background at the right/bottom edge of the
   tall-left masonry cells; a 1px bleed on every side is invisible but
   guarantees full coverage regardless of subpixel rounding. */
.reveal-media--static .reveal-media__inner { transform: scale(1); inset: -1px; }
.reveal-media__inner img,
.reveal-media__inner video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
/* Hover lives on the <img> itself, not .reveal-media__inner — that element
   already gets a JS-driven transform every scroll frame for the parallax
   drift, so a second transform source here would fight it. */
.reveal-media:hover .reveal-media__inner img,
.reveal-media:hover .reveal-media__inner video { transform: scale(1.045); }

/* Aspect ratios below are set to match the real exported photos exactly
   (not a generic crop ratio) — with object-fit:cover and a matching box
   ratio, cover never actually crops anything since the box shape equals
   the image shape. This keeps the full frame of every screenshot visible
   instead of trimming edges to fit an arbitrary box. */
.case-media--full { margin: 0 var(--edge) var(--space-l); aspect-ratio: 1.6; }
/* Short one-line project descriptor, sits under the opening full-screen
   image, before the case study's narrative kicks in. */
.case-descriptor {
  max-width: 56ch;
  margin: 0 auto var(--space-l);
  padding: 0 var(--edge);
  font-size: 1.05rem;
  line-height: 1.5;
  color: #57554e;
  text-align: center;
}
/* Slider composition at the bottom of a case study — compact variant,
   ~30% smaller than the standard slider used elsewhere. */
.slider--compact .slider__frame { width: min(58vw, 630px); }
.slider--compact .slider__nav { padding-right: calc(var(--edge) + 21vw); }
@media (max-width: 760px) {
  .slider--compact .slider__frame { width: min(74vw, 630px); }
  .slider--compact .slider__nav { padding-right: var(--edge); }
}
.case-media--hero { margin: 0 var(--edge) var(--space-l); aspect-ratio: 1.499; }
.case-media--tall { aspect-ratio: 1.6; }
/* One-off overrides for photos with a different ratio than the rest of the
   set — each keeps its own tile's full frame intact rather than forcing it
   into the 1.6 ratio shared by most of the others. */
.case-media--privacy { aspect-ratio: 1.135; }
.case-media--settings { margin: 0 var(--edge) var(--space-l); aspect-ratio: 1.416; }
.case-media--r1416 { aspect-ratio: 1.416; }
.case-media--r1499 { aspect-ratio: 1.499; }
.case-media--row {
  margin: 0 var(--edge) var(--space-l);
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: start;
  gap: var(--space-m);
}
/* Three-up asymmetric block — one tall image on the left, two smaller
   ones stacked on the right that together match its height. Height is
   locked via aspect-ratio on the whole grid (not the individual tiles)
   so the two stacked images always sum exactly to the tall one's height,
   regardless of what aspect ratio the source photos actually are —
   object-fit:cover on each reveal-media__inner handles the crop. */
.case-media--three {
  /* extra side margin shrinks the whole container ~15% (width, and since
     aspect-ratio is fixed, height shrinks proportionally with it) */
  margin: 0 calc(var(--edge) + 6.6vw) var(--space-l);
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: var(--space-m);
  aspect-ratio: 16 / 9.5;
}
.case-media--three .reveal-media--tall-left { grid-row: 1 / 3; }
@media (max-width: 720px) {
  .case-overview { grid-template-columns: 1fr; }
  .case-media--row { grid-template-columns: 1fr; }
  .case-media--three { grid-template-columns: 1fr; grid-template-rows: auto auto auto; aspect-ratio: auto; }
  .case-media--three .reveal-media--tall-left { grid-row: auto; aspect-ratio: 4 / 3; }
  .case-media--three .reveal-media:not(.reveal-media--tall-left) { aspect-ratio: 4 / 3; }
}

/* Three-up variant for source photos that don't share the usual ~1.6
   ratio (a very wide/short banner + a near-square shot, for instance).
   Flexbox instead of grid: the stacked pair on the right sizes itself
   from each image's own aspect-ratio, and the tall-left tile (which has
   no aspect-ratio of its own) simply stretches to match — align-items:
   stretch on the flex row does that for free, no math needed. */
.case-media--three-auto {
  margin: 0 calc(var(--edge) + 6.6vw) var(--space-l);
  display: flex;
  align-items: stretch;
  gap: var(--space-m);
}
.case-media--three-auto .reveal-media--tall-left { flex: 1.15; }
.case-media--three-auto .reveal-media--stack {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-m);
}
@media (max-width: 720px) {
  .case-media--three-auto { flex-direction: column; }
  .case-media--three-auto .reveal-media--tall-left { flex: none; aspect-ratio: 4 / 3; }
}
/* Four-up row of square images — sits directly under a full-bleed shot as
   a "detail strip". Same side margins as the masonry blocks so the whole
   sequence reads as one column. Collapses 4 -> 2 -> 1 as space runs out. */
/* Three-up row for items with wildly different aspect ratios (a portrait,
   a square and a wide landscape, say). Each child's flex-grow is set inline
   to its own aspect ratio, so widths come out proportional to shape and all
   three land on exactly the same height — no cropping, no hard-coded sizes.
   Stacks vertically once there isn't room to keep them side by side. */
.case-media--trio {
  margin: 0 calc(var(--edge) + 4vw) var(--space-l);
  display: flex;
  align-items: flex-start;
  gap: var(--space-m);
}
@media (max-width: 860px) {
  .case-media--trio { flex-direction: column; }
  .case-media--trio .reveal-media { width: 100%; flex: none !important; }
}

.case-media--four {
  margin: 0 calc(var(--edge) + 6.6vw) var(--space-l);
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-m);
}
.case-media--four .reveal-media { aspect-ratio: 1 / 1; }
@media (max-width: 480px) {
  .case-media--four { grid-template-columns: 1fr; }
}

/* Placeholder label — shown centered inside a reveal-media block until a
   real photo replaces it; harmless to leave the CSS in, it just never
   shows once an <img>/<video> fills the box. */
.reveal-media__placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 1.5rem;
  color: #6a6864;
  font-size: 0.78rem;
  line-height: 1.4;
}
@media (prefers-reduced-motion: reduce) {
  .reveal-media { opacity: 1; transform: none; transition: none; }
  .reveal-media__inner { transform: none !important; }
}

/* ============================================
   BACK TO TOP — organic morphing pink blob, fixed
   bottom-right on every page. Border-radius keyframes
   give the "slowly breathing" organic-shape feel; the
   arrow inside is a plain up-arrow rotated to point at
   10 o'clock (up-left).
   ============================================ */
.back-to-top {
  position: fixed;
  right: clamp(1.2rem, 3vw, 2.4rem);
  bottom: clamp(1.2rem, 3vw, 2.4rem);
  z-index: 300;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  opacity: 0;
  pointer-events: none;
  transform: translateY(8px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.back-to-top.is-visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
/* Menu overlay sits at z-index:250, back-to-top at 300 — without this it
   would float on top of the full-screen menu, which looks broken. */
body.menu-open .back-to-top {
  opacity: 0 !important;
  pointer-events: none !important;
}
.back-to-top__label {
  font-size: 0.65rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #fff;
  opacity: 0.85;
  mix-blend-mode: difference;
  pointer-events: none;
}
/* mix-blend-mode:difference was meant to auto-invert this against light
   backgrounds but wasn't reliably doing it (white-on-white on every
   light-theme page) — every light page uses .nav--light, so key off that
   directly instead and force a dark, non-blended label there. */
body:has(.nav--light) .back-to-top__label {
  color: var(--dark);
  mix-blend-mode: normal;
  opacity: 0.75;
}
.back-to-top__blob {
  width: 62px;
  height: 62px;
  border: none;
  padding: 0;
  background: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
  transition: transform 0.25s ease;
  animation: blobMorph 9s ease-in-out infinite;
}
.back-to-top__blob:hover { transform: scale(1.08); }
.back-to-top__arrow {
  width: 22px;
  height: 22px;
  transform: rotate(-60deg);   /* up (12 o'clock) rotated to 10 o'clock */
  pointer-events: none;
}
@keyframes blobMorph {
  0%, 100% { border-radius: 62% 38% 34% 66% / 58% 32% 68% 42%; }
  25%      { border-radius: 40% 60% 68% 32% / 42% 68% 32% 58%; }
  50%      { border-radius: 68% 32% 48% 52% / 30% 62% 38% 70%; }
  75%      { border-radius: 48% 52% 30% 70% / 68% 40% 60% 32%; }
}
@media (prefers-reduced-motion: reduce) {
  .back-to-top__blob { animation: none; border-radius: 50%; }
}
@media (max-width: 760px) {
  .back-to-top__blob { width: 52px; height: 52px; }
  .back-to-top__arrow { width: 18px; height: 18px; }
  .back-to-top__label { font-size: 0.58rem; }
}

/* ============================================
   PROJECT NAV — prev/next case study, centered
   between the last section and the footer. Two
   symmetric halves separated by a hairline; each
   half shows a small directional label plus the
   project name, both of which slide toward the
   center hairline on hover.
   ============================================ */
.project-nav {
  display: flex;
  align-items: stretch;
  justify-content: center;
  margin: var(--space-xl) 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.project-nav__link {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: var(--space-l) var(--edge);
  text-decoration: none;
  color: var(--dark);
  transition: background 0.25s ease;
  overflow: hidden;
}
.project-nav__link:hover { background: rgba(15, 15, 15, 0.03); }
.project-nav__link--next {
  align-items: flex-end;
  text-align: right;
  border-left: 1px solid var(--line);
}
.project-nav__label {
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--grey);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.project-nav__arrow {
  display: inline-block;
  transition: transform 0.25s ease;
}
.project-nav__link:hover .project-nav__arrow--prev { transform: translateX(-4px); }
.project-nav__link:hover .project-nav__arrow--next { transform: translateX(4px); }
.project-nav__name {
  font-family: var(--font-body);
  font-weight: 800;
  letter-spacing: -0.01em;
  font-size: clamp(1.5rem, 3vw, 2.2rem);
  line-height: 1.1;
  transition: color 0.25s ease;
}
.project-nav__link:hover .project-nav__name { color: var(--accent); }
@media (max-width: 720px) {
  .project-nav { flex-direction: column; }
  .project-nav__link--next { border-left: none; border-top: 1px solid var(--line); align-items: flex-start; text-align: left; }
  .project-nav__link--next .project-nav__label { flex-direction: row; }
}

/* ============================================
   MOBILE — NO CROPPING. Every case-study image on phone widths reverts
   to its own natural aspect ratio instead of being forced into a box
   shape tuned for desktop composition. object-fit:cover only avoids
   cropping when the box ratio matches the source photo exactly — on
   mobile the fixed aspect-ratio boxes below don't match, so cover was
   slicing pieces off. Dropping the fixed ratio and taking each
   .reveal-media__inner out of absolute position (it's normally
   absolute+overscanned for the desktop parallax drift) lets every image
   render at full width, its own natural height, completely uncropped.
   ============================================ */
@media (max-width: 760px) {
  .case-media--full,
  .case-media--hero,
  .case-media--tall,
  .case-media--privacy,
  .case-media--settings,
  .case-media--r1416,
  .case-media--r1499,
  .case-media--row .reveal-media,
  .case-media--three .reveal-media,
  .case-media--three-auto .reveal-media,
  .case-media--four .reveal-media,
  .case-media--trio .reveal-media {
    aspect-ratio: auto !important;
  }
  .case-media--full .reveal-media__inner,
  .case-media--hero .reveal-media__inner,
  .case-media--tall .reveal-media__inner,
  .case-media--privacy .reveal-media__inner,
  .case-media--settings .reveal-media__inner,
  .case-media--r1416 .reveal-media__inner,
  .case-media--r1499 .reveal-media__inner,
  .case-media--row .reveal-media__inner,
  .case-media--three .reveal-media__inner,
  .case-media--three-auto .reveal-media__inner,
  .case-media--four .reveal-media__inner,
  .case-media--trio .reveal-media__inner {
    position: static;
    transform: none !important;
  }
  .case-media--full .reveal-media__inner img, .case-media--full .reveal-media__inner video,
  .case-media--hero .reveal-media__inner img, .case-media--hero .reveal-media__inner video,
  .case-media--tall .reveal-media__inner img, .case-media--tall .reveal-media__inner video,
  .case-media--privacy .reveal-media__inner img, .case-media--privacy .reveal-media__inner video,
  .case-media--settings .reveal-media__inner img, .case-media--settings .reveal-media__inner video,
  .case-media--r1416 .reveal-media__inner img, .case-media--r1416 .reveal-media__inner video,
  .case-media--r1499 .reveal-media__inner img, .case-media--r1499 .reveal-media__inner video,
  .case-media--row .reveal-media__inner img, .case-media--row .reveal-media__inner video,
  .case-media--three .reveal-media__inner img, .case-media--three .reveal-media__inner video,
  .case-media--three-auto .reveal-media__inner img, .case-media--three-auto .reveal-media__inner video,
  .case-media--four .reveal-media__inner img, .case-media--four .reveal-media__inner video,
  .case-media--trio .reveal-media__inner img, .case-media--trio .reveal-media__inner video {
    position: static;
    width: 100%;
    height: auto;
    object-fit: contain;
  }
  /* tall-left masonry cell used grid-row spanning to match its stacked
     neighbours' combined height, which only makes sense with a locked
     aspect-ratio on the whole grid — now that heights are content-driven
     it goes back to normal single-row flow, top to bottom in source order */
  .case-media--three .reveal-media--tall-left { grid-row: auto; }

  /* Same principle for the compact bottom slider — frames were a fixed
     16:10 box with cover-fit; now each slide shows its whole photo. */
  .slider__frame { aspect-ratio: auto !important; }
  .slider__frame img { position: static; width: 100%; height: auto; object-fit: contain; }
}
