/* ═══════════════════════════════════════════════════════════════
   moove.studio — Main Stylesheet
   Cinematic · Editorial · Restrained
   ═══════════════════════════════════════════════════════════════ */

/* ─── DESIGN TOKENS ─────────────────────────────────────────── */
:root {
  /* Surfaces — from moove.studio Squarespace CSS */
  --dark:      #04060B;          /* page background — unified deep black */
  --charcoal:  #1D1D27;          /* card bg — exact from site */
  --panel:     #20202e;          /* hover state — exact from site */

  /* Accent — lifted from #0057B8 to #3385E8 for better legibility on
     the dark backgrounds used across the site (was hsl 213/100/36,
     now hsl 214/79/55 — noticeably brighter, still solidly sapphire). */
  --accent:     #3385E8;

  /* Blue gradient — deep navy → sapphire */
  --blue-lo:   rgb(15, 60, 210);           /* deep indigo-navy */
  --blue-hi:   rgb(51, 133, 232);          /* matches --accent */
  --blue-glow: rgba(51, 133, 232, 0.45);

  /* Gradient-only pair — high contrast, used only on surfaces where
     the gradient itself is the visual ("Stand Out"-style flourishes:
     button hover fill, work-cta wave fill, cycling word).
     Bright stop matches the original Stand Out underline so all
     gradient surfaces share the same neon-cyan signature.
     Deep stop is pushed near-black so the contrast against the
     bright is dramatic at fill scale.
     Solid-colour uses (such as .contact-email) stay on
     --blue-hi sapphire to keep calm brand-blue accents calm. */
  --grad-deep: rgb(0, 8, 60);              /* near-black navy */
  --grad-neon: rgb(0, 149, 255);           /* moove bright cyan (= Stand Out high stop) */

  /* Text hierarchy — see css/tokens.css for full reference */
  --text:           #FFFFFF;              /* primary: headings, important copy */
  --text-secondary: rgba(255,255,255,0.58); /* subtitles, card descriptions */
  --muted:          rgba(255,255,255,0.48); /* neutral mid-white — no purple */
  --soft:           rgba(255,255,255,0.30); /* very subtle — footer metadata */

  /* Dividers */
  --rule:      #2a2a3a;
  --rule-alt:  #252535;     /* used by project sub-pages — see css/project.css */

  /* Type — clean system sans-serif, no serif */
  --sans: 'Helvetica Neue', Helvetica, Arial, system-ui, sans-serif;

  /* ── Type scale (use these everywhere for consistency) ──────── */
  /* Display  — hero tagline, intro heading */
  --t-display:  clamp(52px, 8vw, 96px);
  --fw-display: 300;               /* light — matches live site */

  /* Heading 1 — section titles ("Projects.", "What we do.") */
  --t-h1:  clamp(34px, 5vw, 54px);
  --fw-h1: 600;

  /* Heading 2 — card/component headings */
  --t-h2:  clamp(20px, 2.8vw, 30px);
  --fw-h2: 600;

  /* Body large — intro text, service descriptions */
  --t-body-lg: clamp(15px, 1.4vw, 18px);
  --fw-body:   400;

  /* Body standard — card descriptions, about text */
  --t-body: clamp(14px, 1.2vw, 16px);

  /* Small — labels, captions, nav, metadata */
  --t-sm:  13px;
  --t-xs:  11px;

  /* ── Button size tokens — see tokens.css for full reference ── */
  --btn-font:       400 13px/1 var(--sans); /* font shorthand */
  --btn-tracking:   .1em;                   /* letter-spacing */
  --btn-pad-v:      .9rem;                  /* vertical padding */
  --btn-pad-h:      2rem;                   /* horizontal padding */
  --btn-radius:     8px;
  --btn-border-w:   1.5px;
  --btn-fill:       #1c1e2c;               /* dark gray interior */

  /* Page-transition curtain — overridable per-project via body style */
  --curtain-color: #08091c;

  /* Layout */
  --nav-h:   72px;
  --max-w:   1280px;
  --gutter:  clamp(1.25rem, 4.5vw, 3.5rem);

  /* Easing */
  --ease:    cubic-bezier(0.16, 1, 0.3, 1);
}

/* ─── SCROLL SMOOTHER ────────────────────────────────────────── */
/* ScrollSmoother sets overflow:hidden on body itself via JS.
   These rules ensure the wrapper fills the viewport and the
   transformed content doesn't cause layout shifts.              */
#smooth-wrapper {
  overflow: hidden;
}
#smooth-content {
  overflow: hidden;
  width: 100%;
}

/* ─── RESET ──────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: auto; } /* GSAP handles smooth scrolling */
body {
  font-family: var(--sans);
  background: var(--dark);
  color: var(--text);
  min-height: 100vh;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  cursor: none;          /* replaced by custom cursor */
}
img, video  { display: block; max-width: 100%; }
button      { cursor: none; background: none; border: none; font: inherit; color: inherit; }

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

.not-found-page {
  min-height: 100svh;
  cursor: auto;
  overflow: hidden;
}

.not-found-page a { cursor: pointer; }

.not-found {
  min-height: 100svh;
  padding: clamp(1.5rem, 4vw, 3.5rem);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  background:
    radial-gradient(circle at 78% 42%, rgba(0, 149, 255, 0.12), transparent 32%),
    var(--dark);
}

.not-found__logo { width: clamp(140px, 14vw, 220px); }
.not-found__logo img { width: 100%; height: auto; }

.not-found__content {
  width: min(760px, 100%);
  padding-bottom: clamp(2rem, 7vh, 6rem);
}

.not-found__content h1 {
  margin: 0.25em 0 0.3em;
  font-size: clamp(48px, 8vw, 108px);
  font-weight: 300;
  line-height: 0.95;
}

.not-found__content > p:not(.label) {
  color: var(--text-secondary);
  font-size: var(--t-body-lg);
}

.not-found__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2rem;
}
a           { text-decoration: none; color: inherit; cursor: none; }
ol, ul      { list-style: none; }

/* Restore cursor on touch/pointer:none devices */
@media (hover: none) {
  body, button, a { cursor: auto; }
}

/* ─── CUSTOM CURSOR ──────────────────────────────────────────── */
.cursor-dot {
  position: fixed;
  top: 0; left: 0;
  width: 8px; height: 8px;
  background: #fff;
  border-radius: 50%;
  pointer-events: none;
  z-index: 9999;
  will-change: transform;
  transition: width .2s var(--ease), height .2s var(--ease), background .2s;
}
.cursor-ring {
  position: fixed;
  top: 0; left: 0;
  width: 38px; height: 38px;
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 50%;
  pointer-events: none;
  z-index: 9998;
  will-change: transform;
  transition: width .35s var(--ease), height .35s var(--ease), border-color .3s, opacity .3s;
}

/* States */
.cursor-dot.hov  { width: 5px;  height: 5px; }
.cursor-ring.hov { width: 58px; height: 58px; border-color: #fff; }
.cursor-dot.clk  { width: 12px; height: 12px; }
.cursor-ring.clk { width: 26px; height: 26px; }

/* ─── TYPE ───────────────────────────────────────────────────── */
/* Base headings — override per context using type-scale vars */
h1, h2, h3 {
  font-family: var(--sans);
  font-weight: var(--fw-h1);
  line-height: 1.08;
  letter-spacing: -0.02em;
}

.label {
  display: block;
  font: 600 var(--t-xs)/1 var(--sans);
  letter-spacing: .35em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 1.1rem;
}

/* Section titles: "Projects.", "What we do.", "Built to frame." */
.section-title {
  font-size: var(--t-h1);
  font-weight: var(--fw-h1);
  line-height: 1.08;
  letter-spacing: -0.02em;
}

/* ─── LAYOUT ─────────────────────────────────────────────────── */
.container {
  max-width: var(--max-w);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.section      { padding-block: clamp(5rem,10vw,9rem); }
.section-head { margin-bottom: clamp(3rem,6vw,5rem); }

/* ─── NAVIGATION ─────────────────────────────────────────────── */
.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 2000;
  height: var(--nav-h);
  /* 3-column grid: logo | links (truly centred) | right controls */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding-inline: var(--gutter);
  border-bottom: 1px solid transparent;
  transition: background .5s var(--ease), border-color .5s var(--ease), backdrop-filter .5s;
}
.nav.scrolled {
  background: rgba(8,12,16,.92);
  border-color: var(--rule);
  /* 12px is plenty given the 92% opaque bg already obscures most of the
     content below. 18px was costing extra GPU work on every scroll for
     a barely perceptible visual difference. */
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.nav-logo img { height: 30px; width: auto; }

/* ── Project back-link — fixed position, identical on every project page.
   position:fixed means it is unaffected by parent overflow, transforms
   (ScrollSmoother), or z-index stacking inside the page content.
   text-shadow keeps it legible on both dark and light section backgrounds. */
.proj-back {
  position: fixed;
  top: calc(var(--nav-h) + 1.5rem);
  left: var(--gutter);
  z-index: 100;
  font: 400 0.85rem/1 var(--sans);
  letter-spacing: .06em;
  color: rgba(255, 255, 255, 0.75);
  text-decoration: none;
  text-shadow: 0 1px 4px rgba(0,0,0,0.55);
  padding-bottom: 3px;
  transition: color .2s;
  animation: projBackFadeIn 0.4s 0.6s backwards;
}
.proj-back::after {
  content: '';
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.3s ease;
}
@keyframes projBackFadeIn { from { opacity: 0; } to { opacity: 1; } }
.proj-back:hover { color: #fff; }
.proj-back:hover::after { transform: scaleX(1); }

.nav-links {
  display: flex;
  gap: 2.25rem;
  justify-self: center;   /* centred in the middle grid column */
}
.nav-link {
  font: 400 13px/1 var(--sans);
  letter-spacing: .07em;
  color: rgba(255,255,255,0.75);   /* white, slightly softened */
  position: relative;
  padding-bottom: 3px;
  transition: color .25s;
}
.nav-link::after {
  content: '';
  position: absolute;
  bottom: 0; left: 0;
  width: 0; height: 1px;
  background: linear-gradient(to right, var(--blue-lo), var(--blue-hi));
  transition: width .35s var(--ease);
}
.nav-link:hover        { color: var(--text); }
.nav-link:hover::after,
.nav-link.active::after{ width: 100%; }
.nav-link.active       { color: var(--text); }

.nav-right {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  justify-self: end;   /* pinned to the right grid column */
}

/* Social icons in nav */
.nav-social {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.nav-social-link {
  display: flex;
  align-items: center;
  color: rgba(255,255,255,0.55);
  transition: color .25s;
}
.nav-social-link:hover { color: rgba(255,255,255,0.95); }
.nav-social-link svg   { width: 18px; height: 18px; display: block; }

/* Hamburger */
.hamburger {
  display: none;
  flex-direction: column;
  gap: 7px;
  padding: 5px;
}
.hbar {
  display: block;
  width: 26px; height: 1.5px;
  background: var(--text);
  transform-origin: center center;
  transition: opacity .3s, width .3s;
}

/* ─── MOBILE MENU ────────────────────────────────────────────── */
.mobile-menu {
  position: fixed; inset: 0;
  z-index: 400;
  background: var(--dark);
  display: flex; flex-direction: column;
  padding: var(--nav-h) var(--gutter) 3rem;
  opacity: 0; pointer-events: none;
}
/* Hidden menus stay out of keyboard navigation even before JS/inert support.
   aria-hidden changes after the closing fade, preserving its animation. */
.mobile-menu[aria-hidden="true"] { visibility: hidden; }
/* Instant open state used only without GSAP or with reduced motion. */
.mobile-menu.open { opacity: 1; pointer-events: all; }
.mobile-menu.open .mobile-link { opacity: 1; }
.hamburger.open .hbar:first-child { transform: translateY(4.25px) rotate(45deg); }
.hamburger.open .hbar:last-child { transform: translateY(-4.25px) rotate(-45deg); }

.mobile-menu-inner {
  display: flex; flex-direction: column;
  justify-content: space-between;
  height: 100%; padding-top: 3.5rem;
}

.mobile-nav { display: flex; flex-direction: column; gap: .25rem; }

.mobile-link {
  font: 400 clamp(38px,10vw,70px)/1.05 var(--sans);
  color: var(--muted);
  display: inline-flex; align-items: baseline; gap: 1rem;
  opacity: 0;
  transition: color .25s;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.mobile-link::before {
  content: attr(data-idx);
  font: 800 9px/1 var(--sans);
  letter-spacing: .3em;
  color: var(--accent);
  margin-bottom: 6px;
}
.mobile-link:hover { color: var(--text); }


.mobile-footer {
  display: flex; justify-content: space-between; align-items: flex-end;
  border-top: 1px solid var(--rule); padding-top: 2rem;
}
.mobile-email { font: 400 14px/1 var(--sans); color: var(--muted); transition: color .2s; }
.mobile-email:hover { color: var(--accent); }
.mobile-socials { display: flex; gap: 1.5rem; }
.mobile-socials a { font: 400 12px/1 var(--sans); letter-spacing: .08em; color: var(--muted); transition: color .2s; }
.mobile-socials a:hover { color: var(--text); }

/* ─── HERO ───────────────────────────────────────────────────── */
.hero {
  position: relative;
  height: 100svh; min-height: 640px;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; background: var(--dark);
}

.hero-bg { position: absolute; inset: 0; }

.hero-video {
  width: 100%; height: 100%;
  object-fit: cover; opacity: 0;
  transition: opacity 1.2s;
}
.hero-video.loaded { opacity: .5; }

/* Subtle film grain texture */
.hero-grain {
  position: absolute; inset: 0;
  opacity: .035;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  pointer-events: none;
  /* Transition lives on the BASE rule, not the .is-watching rule, so the
     fade is symmetric on both enter (opacity → 0) and exit (opacity → .035). */
  transition: opacity .5s ease;
}

/* Cinematic vignette overlay — placed INSIDE .hero-bg in the markup so it
   shares the parallax transform with the video. */
.hero-vignette {
  position: absolute; inset: 0;
  background:
    linear-gradient(to bottom, rgba(8,12,16,.35) 0%, transparent 25%, transparent 70%, rgba(8,12,16,.85) 100%),
    radial-gradient(ellipse at center, transparent 35%, rgba(8,12,16,.55) 100%);
  pointer-events: none; z-index: 1;
}

/* ─── HERO LEFT BLOCK (logo + tagline) ────────────────────────── */
.hero-left {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-55%);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;   /* centre logo and tagline horizontally */
  text-align: center;
  max-width: 100%;
  /* Auto inline margins re-centre this absolute element when its width is
     clamped by the mobile media queries below (max-width: 70% / 85%).
     Without this, `left: 0; right: 0` keeps the clamped box anchored to
     the left edge, so the centred children appear off-centre at narrow
     viewports. */
  margin-inline: auto;
  /* GSAP handles the initial entrance (children: .hero-wordmark + .hero-tagline).
     The opacity transition here is for the Watch-reel escalation: when
     .hero.is-watching is added/removed, the wordmark+tagline group fades both
     ways at .5s. Declared on the BASE rule (not on .is-watching) so it stays
     in scope when the class is removed and the exit fade still runs. */
  transition: opacity .5s ease;
}

.hero-wordmark {
  width: clamp(200px, 36vw, 500px);
  height: auto;
  margin-bottom: clamp(1rem, 2.5vw, 2rem);
}

.hero-tagline {
  font-size: clamp(18px, 2.4vw, 30px);
  font-weight: 300;
  line-height: 1.5;
  color: #ffffff;
  max-width: 480px;
}

/* ─── HERO CENTRED CTA ROW ────────────────────────────────────── */
.hero-cta-row {
  position: absolute;
  bottom: clamp(5rem, 12vh, 9rem);
  left: 0; right: 0;
  display: flex;
  justify-content: center;
  z-index: 2;
}

.hero-cta {
  padding: 1rem 2.5rem;
  /* font-size and letter-spacing from shared --btn-font / --btn-tracking tokens */
}

/* ─── HERO WATCH-REEL BUTTON ───────────────────────────────────
   Secondary action on the hero: click escalates the ambient muted
   loop to focused playback (unmuted, video to full opacity, chrome
   fades out, close button appears). Click again on .hero-close to
   return to the loop.
   Independent of the GSAP hero-entrance timeline — uses a pure CSS
   keyframe with a 0.5s delay so it always appears at the same time
   regardless of GSAP state. */
.hero-watch {
  position: absolute;
  left: 50%;
  /* Anchored ~140px below the hero centre — sits in a tight visual group
     with the wordmark + tagline rather than floating down by the scroll
     indicator. Was at `bottom: clamp(5rem, 12vh, 9rem)` which overlapped
     the scroll indicator and felt disconnected from the tagline above. */
  top: calc(50% + 140px);
  transform: translateX(-50%);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: .75rem;
  padding: .65rem 1.4rem .65rem 1.05rem;
  font: 400 11px/1 var(--sans);
  letter-spacing: .22em;
  text-transform: uppercase;
  color: #fff;
  background: transparent;
  border: 1.5px solid var(--accent);
  border-radius: 8px;
  cursor: none;
  opacity: 0;
  /* opacity + Y slide are animated by GSAP in app.js, not CSS
     transitions. CSS owns colour/border/background/scale only. */
  transition:
    color .25s ease,
    border-color .25s ease,
    background .25s ease,
    box-shadow .25s ease,
    transform .25s ease;
}
.hero-watch:hover {
  color: #fff;
  border-color: var(--accent);
  box-shadow: 0 0 24px rgba(0, 149, 255, 0.22);
  transform: translateX(-50%) scale(1.03);
}
.hero-watch__icon {
  display: inline-flex;
  width: 16px; height: 16px;
  color: var(--accent);
}
.hero-watch__icon svg { width: 100%; height: 100%; display: block; }
.hero-watch__label { display: inline-block; }

/* ─── HERO CLOSE — only visible while .is-watching ─────────────── */
/* (The mute button positioning + appearance moved to .hero-mute-wrap +
   .hero-mute below, since it now hosts a hover-revealed volume popout
   that must share the same hover bounding box.) */
.hero-close {
  position: absolute;
  top: clamp(5rem, 6vh, 7rem);
  right: clamp(1.5rem, 3vw, 3rem);
  z-index: 4;
  width: 48px; height: 48px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.55);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 50%;
  color: #fff;
  cursor: none;
  opacity: 0;
  pointer-events: none;
  transform: scale(0.92);
  transition:
    opacity .3s ease,
    transform .3s ease,
    border-color .25s ease,
    color .25s ease;
}
.hero-close svg { width: 20px; height: 20px; display: block; }
.hero-mute  svg { width: 22px; height: 22px; display: block; }

.hero-close:hover,
.hero-mute:hover {
  color: var(--accent, #0095FF);
  border-color: var(--accent, #0095FF);
}

/* Speaker icon swap: 'on' lines = unmuted (sound waves visible);
   'off' lines = muted (X over speaker). Default state is unmuted. */
.hero-mute .hero-mute__off { display: none; }
.hero-mute[aria-pressed="true"] .hero-mute__on  { display: none; }
.hero-mute[aria-pressed="true"] .hero-mute__off { display: inline; }

/* ─── HERO MUTE WRAPPER + VOLUME POPOUT ─────────────────────────
   The .hero-mute-wrap is a positioning context for the volume column
   that drops down beneath the speaker button on hover. The wrap takes
   the exact position the bare .hero-mute used to occupy (top-right of
   the hero, to the left of the .hero-close). The button inside fills
   the wrap. */
.hero-mute-wrap {
  position: absolute;
  top: clamp(5rem, 6vh, 7rem);
  right: calc(clamp(1.5rem, 3vw, 3rem) + 60px);
  z-index: 4;
  width: 48px; height: 48px;
  /* Inherit appear/disappear from .is-watching state (see rule below). */
  opacity: 0;
  pointer-events: none;
  transform: scale(0.92);
  transition:
    opacity .3s ease,
    transform .3s ease;
}

/* The actual mute button sits INSIDE the wrap and fills it. Style stays
   identical to .hero-close (matched via the shared selector above) — only
   positioning moves to .hero-mute-wrap. */
.hero-mute {
  position: absolute;
  inset: 0;
  /* override top/right from .hero-close,.hero-mute base rule above */
  top: 0; right: 0;
  width: 48px; height: 48px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.55);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 50%;
  color: #fff;
  cursor: none;
  /* Opacity/pointer-events handled by the parent .hero-mute-wrap so the
     popout + button move as one unit. */
  transition:
    border-color .25s ease,
    color .25s ease;
}

/* The popout column. Sits BELOW the button, anchored centred under it.
   Hidden by default; appears when the wrap (button OR popout) is hovered
   or focus is inside it. */
.hero-volume {
  position: absolute;
  top: calc(100% + 10px);   /* sit just below the button with a small gap */
  left: 50%;
  transform: translateX(-50%) translateY(-6px);
  width: 36px;
  height: 120px;
  padding: 12px 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.55);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 24px;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity .22s ease,
    transform .22s cubic-bezier(.2,.6,.2,1);
}

/* Reveal on hover or keyboard focus inside the wrap. */
.hero-mute-wrap:hover .hero-volume,
.hero-mute-wrap:focus-within .hero-volume {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}

/* Visual track + fill — fully owned by CSS so we can theme it consistently.
   The native <input type="range"> sits on top, transparent, and just
   handles the pointer interaction; JS updates --hero-vol-pct on input to
   drive the gold fill height. */
.hero-volume__track {
  position: absolute;
  top: 12px;
  bottom: 12px;
  left: 50%;
  width: 3px;
  margin-left: -1.5px;
  background: rgba(255, 255, 255, 0.18);
  border-radius: 2px;
  overflow: hidden;
  pointer-events: none;
}
.hero-volume__fill {
  position: absolute;
  left: 0; right: 0;
  bottom: 0;
  /* CSS custom property updated by JS — 0 to 100 */
  height: calc(var(--hero-vol-pct, 100) * 1%);
  background: linear-gradient(to top, var(--accent, #0095FF), rgba(255,255,255,0.85));
  border-radius: 2px;
  transition: height .12s ease;
}

/* Native vertical range input — transparent, sized to overlap the track,
   carries the pointer interaction. orient="vertical" is Firefox-only;
   writing-mode is the modern cross-browser path. */
.hero-volume__slider {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  margin: 0;
  padding: 0;
  background: none;
  border: none;
  cursor: none;
  /* Vertical orientation — modern CSS. The deprecated
     `appearance: slider-vertical` was removed (Chrome 121+ warning). */
  writing-mode: vertical-lr;
  direction: rtl;       /* so 0 sits at the BOTTOM, 100 at the top */
  opacity: 0;           /* hidden — the visible track is .hero-volume__track */
}
.hero-volume__slider:focus-visible {
  outline: none;
}
/* Focus ring on the WRAP so the keyboard user knows they have it. */
.hero-mute-wrap:focus-within .hero-mute {
  border-color: var(--accent, #0095FF);
}

/* ─── HERO WATCHING STATE — chrome fades, video to full opacity ── */
/* Transitions for .hero-left / .hero-grain live on their BASE rules above,
   so the fade is symmetric on enter AND exit of the .is-watching class. */
.hero.is-watching .hero-video.loaded { opacity: 1; }
.hero.is-watching .hero-grain        { opacity: 0; }
.hero.is-watching .hero-left {
  opacity: 0;
  pointer-events: none;
}
/* .hero-cta-row + .hero-scroll have inline autoAlpha from gsap.js heroEntrance —
   their fade is handled by gsap.to() in app.js, not CSS. */

/* .hero-watch opacity + translate are driven by GSAP in app.js — entrance
   fade-in/slide-up at 0.5s after load, slide-down/fade-out on watching enter,
   rise/fade-in on watching exit. CSS only owns pointer-events here so
   GSAP-applied inline styles aren't overridden by the cascade. */
.hero.is-watching .hero-watch {
  pointer-events: none;
}
.hero.is-watching .hero-close,
.hero.is-watching .hero-mute-wrap {
  opacity: 1;
  pointer-events: auto;
  transform: scale(1);
}

/* Reduced motion: skip the entrance animation entirely. The JS tween
   honors reducedMotion too; this is the no-JS / late-JS fallback so the
   button is visible immediately rather than stuck at opacity 0. */
@media (prefers-reduced-motion: reduce) {
  .hero-watch {
    opacity: 1;
    translate: 0 0;
  }
}

/* ─── .moove-player — reusable cinematic video player ──────────────
   Used by project sub-pages (rivella, citroen-c3, comet700, smeg) via
   js/video-player.js. The homepage no longer uses this player; the
   former .showreel-section + .showreel-header rules were removed when
   that section was retired in favour of the hero Watch-reel pattern. */
.moove-player {
  position: relative;
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: 12px;
  overflow: hidden;
  box-shadow:
    0 30px 80px rgba(0, 0, 0, 0.5),
    0 0 0 1px rgba(255, 255, 255, 0.04);
}
/* (Removed the ::before/::after sub-pixel safety bars — they were
   addressing what turned out to be a misdiagnosed bug. The actual
   "bright pixel line" reported under the breakdown player was the
   border-top on .proj-cta-section, NOT video bleed. Fix is in
   project.css. Keeping the player's structure clean and flat,
   matching the working homepage hero pattern.) */

/* Vimeo iframe slot — SDK injects an <iframe> into this when player initialises. */
.moove-player__mount {
  position: absolute;
  /* 2px gap at top and bottom — the player's own #000 background fills
     this gap. The video element lives inside the mount, so it physically
     CANNOT render at the player's top/bottom edges (where GPU compositor
     sub-pixel rendering otherwise lets it bleed past `overflow: hidden`
     by 1-2px). Sides stay flush at 0. ──────────────────────────────── */
  inset: 2px 0;
  border: none;
}
.moove-player__mount iframe {
  position: absolute;
  inset: 0;
  width: 100% !important;     /* override Vimeo SDK inline sizing */
  height: 100% !important;
  border: none;
}

/* pointer-events: none on the iframe = events pass through to the parent
   document. Without this, the custom cursor stops working when hovered over
   the iframe (iframes are isolated browsing contexts and don't forward
   mousemove to the parent). Vimeo's UI is hidden via controls=0 so there's
   nothing inside the iframe that needs to be clickable. */
.moove-player__mount iframe {
  pointer-events: none;
}

/* Poster image — shown until first play. Hidden once playback starts. */
.moove-player__poster {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-color: #000;
  z-index: 1;
  transition: opacity 0.6s var(--ease);
  pointer-events: none;
}

.moove-player.is-started .moove-player__poster {
  opacity: 0;
}

/* ── Preview overlays — noise + vignette ─────────────────────────
   Sit on top of the video/poster to create a cinematic preview
   look before the user clicks play. Both fade out once playback
   starts (.is-started). Visual style matches .wp-vignette/.wp-grain
   on the work hub. */
.moove-player__vignette {
  /* Cinematic darkening over the video. The gradient's bottom stop
     is FULLY OPAQUE (rgba 1.0) at the very last pixel — this
     eliminates the perceived "bright line" that comes from the eye
     contrasting a darkened upper region (≤60% alpha) against an
     un-darkened bottom row. Now the last row IS pure black so
     there's nothing to contrast. */
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background:
    radial-gradient(ellipse at center, transparent 40%, rgba(8,12,16,0.5) 100%),
    linear-gradient(to bottom,
      rgba(8,12,16,0.35) 0%,
      transparent        25%,
      transparent        70%,
      rgba(8,12,16,0.85) 96%,
      rgba(8,12,16,1)    100%);
  transition: opacity 0.5s var(--ease, ease);
}
.moove-player__noise {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  opacity: 0.05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='200' height='200' filter='url(%23n)' opacity='0.6'/></svg>");
  transition: opacity 0.5s var(--ease, ease);
}
.moove-player.is-started .moove-player__vignette,
.moove-player.is-started .moove-player__noise {
  opacity: 0;
}

/* No `will-change` or `transform: translateZ(0)` on the mount. The
   homepage hero's working pattern (.hero-bg) has NO compositor-layer
   promotion — it's a flat single layer that ScrollSmoother moves
   wholesale, and the video + overlays sit on the same GPU surface
   naturally. Promoting the mount to its own layer was the actual
   cause of the sub-pixel desync (mount's GPU surface didn't align
   with the parent .moove-player's overflow:hidden clip). */

/* ── Native MP4 player controls bar (mute + scrubber + time) ──────
   Minimalistic strip pinned to the bottom of the player. Fades in
   once playback has started, with a subtle gradient backdrop so
   the controls remain legible over light footage. */
.moove-player__controls {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  z-index: 4;
  padding: 1.2rem 1.4rem 1rem;
  display: flex;
  align-items: center;
  gap: 1.1rem;
  background: linear-gradient(to top,
    rgba(0,0,0,0.72) 0%,
    rgba(0,0,0,0.45) 50%,
    transparent 100%);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}
/* Show controls only when:
   1. Video is actively playing (.is-playing), OR
   2. User is hovering after the video has started (.is-started:hover)
   When paused or not-yet-started, controls hide so the preview frame
   stays clean. The centered play button is enough UI in that state. */
.moove-player.is-playing .moove-player__controls,
.moove-player.is-started:hover .moove-player__controls {
  pointer-events: auto;
  opacity: 1;
}

/* Mute toggle button — small icon button, no fill, subtle hover */
.moove-player__mute {
  width: 28px;
  height: 28px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.85);
  cursor: none;
  transition: color 0.2s ease, transform 0.2s ease;
  flex-shrink: 0;
}
.moove-player__mute:hover {
  color: #fff;
  transform: scale(1.08);
}
.moove-player__ctl-icon {
  width: 22px;
  height: 22px;
  display: block;
}
/* Swap the speaker-waves icon for the X icon when muted */
.moove-player.is-muted [data-icon="on"]  { display: none; }
.moove-player.is-muted [data-icon="off"] { display: inline; }
.moove-player.is-unmuted [data-icon="on"]  { display: inline; }
.moove-player.is-unmuted [data-icon="off"] { display: none; }
[data-icon="off"] { display: none; }

/* Scrub track — slim line, subtle glow on hover */
.moove-player__scrub {
  flex: 1;
  height: 14px;             /* generous click target */
  position: relative;
  cursor: none;
  display: flex;
  align-items: center;
  outline: none;
}
.moove-player__scrub::before {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  height: 2px;
  margin-top: -1px;
  background: rgba(255, 255, 255, 0.18);
  border-radius: 2px;
  transition: height 0.2s ease, margin-top 0.2s ease;
}
.moove-player__scrub:hover::before,
.moove-player__scrub:focus-visible::before {
  height: 3px;
  margin-top: -1.5px;
}
.moove-player__scrub-fill {
  position: absolute;
  left: 0;
  top: 50%;
  height: 2px;
  margin-top: -1px;
  width: 0%;
  background: linear-gradient(to right, #00d4ff, #0095ff);
  border-radius: 2px;
  pointer-events: none;
  transition: height 0.2s ease, margin-top 0.2s ease;
}
.moove-player__scrub:hover .moove-player__scrub-fill,
.moove-player__scrub:focus-visible .moove-player__scrub-fill {
  height: 3px;
  margin-top: -1.5px;
}
.moove-player__scrub-thumb {
  position: absolute;
  top: 50%;
  left: 0%;
  width: 12px;
  height: 12px;
  margin-left: -6px;
  margin-top: -6px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 2px rgba(0, 149, 255, 0.4),
              0 2px 8px rgba(0, 0, 0, 0.4);
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
  pointer-events: none;
}
.moove-player__scrub:hover .moove-player__scrub-thumb,
.moove-player__scrub:focus-visible .moove-player__scrub-thumb {
  opacity: 1;
}

/* Time display — small monospace-feel, muted */
.moove-player__time {
  font: 400 11px/1 var(--sans);
  letter-spacing: 0.05em;
  color: rgba(255, 255, 255, 0.6);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
  min-width: 90px;
  text-align: right;
}

/* Custom play/pause control — centred on the player.
   Transparent dark fill + blurred backdrop for a clean glass look. */
.moove-player__btn {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: clamp(72px, 7vw, 110px);
  height: clamp(72px, 7vw, 110px);
  background: rgba(0, 0, 0, 0.45);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  border: 1.5px solid rgba(255, 255, 255, 0.7);
  border-radius: 50%;
  color: rgba(255, 255, 255, 0.95);
  cursor: none;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  z-index: 3;
  /* Non-overshooting ease — var(--ease) is a spring-out curve that
     overshoots, which on scale: 1 → 1.06 produced a visible juggle. */
  transition:
    background   0.3s ease-out,
    border-color 0.3s ease-out,
    color        0.3s ease-out,
    opacity      0.4s ease-out,
    transform    0.3s ease-out;
}

.moove-player__btn:hover {
  background: rgba(0, 0, 0, 0.6);
  border-color: var(--blue-hi);
  color: var(--blue-hi);
  transform: translate(-50%, -50%) scale(1.06);
}

.moove-player__icon {
  width: 38%;
  height: 38%;
  display: block;
}

/* While playing: fade the control to a discreet hint, full opacity on hover.
   This keeps the video unobstructed but the control still discoverable. */
.moove-player.is-playing .moove-player__btn {
  opacity: 0;
}

.moove-player.is-playing:hover .moove-player__btn {
  opacity: 1;
}

/* Loading pulse — runs while we're waiting for the Vimeo iframe to
   become ready on first play. Visual feedback that the click registered.
   Removed by the `play` event listener (and by the click handler's
   finally block) the moment playback starts. */
.moove-player.is-loading .moove-player__btn {
  animation: moovePlayerPulse 1.4s ease-in-out infinite;
}

@keyframes moovePlayerPulse {
  0%, 100% { opacity: 0.6; transform: translate(-50%, -50%) scale(0.95); }
  50%      { opacity: 1;   transform: translate(-50%, -50%) scale(1.02); }
}

/* ─── BUTTONS — legacy .btn-primary / .btn-intro removed.
   Both classes were retired in favour of the unified .btn-cta below.
   All 10 instances across the site (3 homepage work-card "Learn more"
   buttons + 7 project sub-page "Get In Touch" CTAs) have been
   migrated to .btn-cta markup. The accompanying btnGlow IIFE in
   js/app.js (cursor-tracked radial border gradient) was also removed
   since the new .btn-cta provides its own GSAP-driven ripple. */


/* ═══════════════════════════════════════════════════════════════
   .btn-cta — site-wide primary button.

   Style: ghost glow. Transparent fill + 2px sapphire (--accent)
   border + sapphire text/arrow at rest. On hover, a circular sphere
   (.btn-cta__bg) scales up from the cursor entry point to fill the
   button — see btnVariantRipple() in js/gsap.js. The label + arrow
   flip to white in sync so they read against the now-blue fill.

   The .btn-cta__bg lives inside the overflow:hidden pill so the
   circle stays clipped to the button's rounded corners.
   ═══════════════════════════════════════════════════════════════ */
.btn-cta {
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  gap: .7rem;
  padding: 1rem 2.2rem;
  font: 500 14px/1 var(--sans);
  letter-spacing: .02em;
  text-transform: none;
  white-space: nowrap;
  cursor: none;
  border-radius: 8px;
  text-decoration: none;
  background: transparent;
  /* WHITE text at rest, not the accent colour. With sapphire on dark
     for both border AND text, the button felt recessive — too much of
     it was muted blue. Switching the text to white lifts the button
     visually while the sapphire border carries the brand accent. The
     hover state is unchanged: the sphere fills with sapphire and the
     text stays white, so the transition feels continuous. */
  color: #fff;
  border: 1.5px solid var(--accent, #3385E8);
  /* No bg/border colour transition — the bg sphere does the visual.
     Only colour transitions on label/arrow so text legibility tracks
     the expanding circle (and so the colour stays steady at white).
     Box-shadow transitions in sync with the gradient reveal so the
     cyan outer glow rises with the fill rather than snapping on. */
  transition: border-color .35s ease, box-shadow .35s ease;
}

/* Hover: text already white, no colour change needed. The .is-hovering
   class is kept on the rule selector for parity with the JS-toggled
   state. Outer cyan glow lights the pill from within — kept low alpha
   so it reads as "lit" not "web 2.0 button". */
.btn-cta:hover,
.btn-cta.is-hovering {
  color: #fff;
  box-shadow: 0 0 24px rgba(0, 149, 255, 0.22);
}

/* Gradient layer — locked to the button's box (NOT to the cursor).
   The 135° diagonal sweep is always painted; clip-path circle()
   gates its visibility. JS tweens the three CSS custom props
   --reveal-r (radius, unitless → %), --reveal-x, --reveal-y
   (centre) to expand the reveal from the cursor's entry point on
   hover and collapse it on leave. Because the gradient lives on
   ::before (anchored to .btn-cta's coords), it never moves as the
   reveal circle grows — only its visible window changes.
   Default --reveal-r is 0 so nothing shows at rest. */
.btn-cta::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to right,
    var(--grad-deep) 0%,
    var(--grad-neon) 100%);
  clip-path: circle(calc(var(--reveal-r, 0) * 1%)
                    at var(--reveal-x, 50%) var(--reveal-y, 50%));
  z-index: 0;
  pointer-events: none;
  will-change: clip-path;
}

/* Old sphere element retired — the gradient is now drawn by ::before
   above and clipped via a CSS-variable-driven circle(). Kept the
   markup so HTML across 8 pages doesn't need re-touching; just
   hidden so layout / cursor coverage stays identical. */
.btn-cta__bg { display: none; }

.btn-cta__label,
.btn-cta__arrow {
  position: relative;
  z-index: 1;
  transition: color .35s ease;
}

.btn-cta__arrow {
  display: inline-block;
  transition: transform .25s cubic-bezier(.2,.6,.2,1), color .35s ease;
}

.btn-cta:hover .btn-cta__arrow {
  transform: translateX(3px);
}

/* ─── SCROLL INDICATOR ───────────────────────────────────────── */
.hero-scroll {
  position: absolute; bottom: 2.5rem; left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  display: flex; flex-direction: column; align-items: center; gap: .65rem;
  /* GSAP handles entrance opacity */
}

.scroll-label {
  font: 400 9px/1 var(--sans);
  letter-spacing: .3em; text-transform: uppercase; color: var(--accent);
}
.scroll-track { width: 1px; height: 56px; background: var(--rule); position: relative; overflow: hidden; }
.scroll-thumb {
  position: absolute; top: 0; left: 0;
  width: 1px; height: 50%;
  background: var(--blue-hi);
  animation: scrollDown 2.5s var(--ease) infinite 1.6s;
}
@keyframes scrollDown {
  0%   { transform: translateY(-100%); }
  100% { transform: translateY(300%); }
}

/* ─── WORK LIST — redesigned to match live site ─────────────── */
/*
  Each row: [text left 40%] [image right, bleeds to viewport edge]
  Image has left-side border-radius only. On hover it slides left,
  expanding toward the text column.
*/

.work-list {
  background: var(--dark);
  overflow: hidden;   /* prevents horizontal scroll when image extends */
  /* Top breathing space — was previously on .work-list-head (which held
     a "Recent Work" eyebrow label that has since been removed). The
     work cards now lead the section directly, so the padding moves up
     to the section itself. */
  padding-top: clamp(4rem, 8vw, 7rem);
}

/* "View all work" CTA — editorial section closer.
   Layout: headline (left, work-item-info column) → thin connecting rule
   (fills the row's empty middle as the eye's path) → confident chevron
   (right padding). On hover everything shifts to accent blue, the rule
   draws out from 40% → 100% width via scaleX (no layout reflow), the
   chevron slides right, and the headline nudges right slightly. Single
   coherent gesture — line "opens the path forward" — no per-element
   competing animations.

   Headline size matches the project-name max so the CTA is a peer of the
   work above; primary affordance signals come from the chevron + animated
   line, not type size. Bottom border closes the section before the
   wave-divider into Services. */
/* Container — positioning context + clip the wipe layer to the row */
.work-cta-row {
  position: relative;
  display: block;
  text-decoration: none;
  color: var(--text);
  border-bottom: 1px solid var(--rule);
  overflow: hidden;
  min-height: clamp(120px, 14vw, 180px);
}

/* Wave-edged sapphire fill that wipes in from the left on hover.
   Sits absolutely on top of the row, BUT z-index 0 so the .work-cta-inner
   (z-index 1) base layer's text/line/chevron stay readable on top of
   the fill as the wave sweeps past. JS in gsap.js (workCtaWipeInit)
   drives the clip-path with a wavy leading edge. */
.work-cta-inner--invert {
  position: absolute;
  inset: 0;
  z-index: 0;
  /* Horizontal high-contrast gradient — direction matches the wave wipe.
     Trailing edge (left, already revealed) is deep navy;
     leading edge (right, the freshest reveal) is electric cyan.
     The eye tracks the bright stop and reads it as motion-light. */
  background: linear-gradient(to right, var(--grad-deep), var(--grad-neon));
  clip-path: polygon(0% 0%, 0% 0%, 0% 100%, 0% 100%);
  will-change: clip-path;
  pointer-events: none;
}

/* Fullscreen expansion used after clicking "View all work". Its three
   stops mirror the destination page's SVG curtain exactly, preventing a
   solid-blue to dark-gradient color jump during navigation. */
.work-cta-transition-curtain {
  background: linear-gradient(
    135deg,
    color-mix(in srgb, var(--curtain-color, #08091c) 25%, #000) 0%,
    var(--curtain-color, #08091c) 50%,
    color-mix(in srgb, var(--curtain-color, #08091c) 25%, #000) 100%
  );
}

/* Base layer — flex row carrying the headline, hairline, chevron.
   z-index: 1 keeps it above the invert fill. */
.work-cta-inner {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2vw, 2rem);
  padding: 0 clamp(2rem, 5vw, 5rem) 0 clamp(4rem, 14vw, 26rem);
  min-height: clamp(120px, 14vw, 180px);
}

/* Headline + tagline group, left column */
.work-cta-text {
  display: flex;
  flex-direction: column;
  gap: .55rem;
  flex-shrink: 0;
  transition: transform .6s var(--ease);
}
.work-cta-row:hover .work-cta-text { transform: translateX(.25rem); }

.work-cta-headline {
  font: 300 clamp(24px, 3vw, 46px)/1 var(--sans);
  letter-spacing: -0.02em;
}

.work-cta-tagline {
  font: 400 var(--t-xs)/1.4 var(--sans);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
  transition: color .5s var(--ease);
}
.work-cta-row:hover .work-cta-tagline {
  color: rgba(255, 255, 255, 0.85);
}

/* Connecting hairline — middle of the row, draws rightward on hover.
   Constant opacity + locked compositing layer prevents the perceived
   thickness flicker that came from contrast (dim-on-dark → bright-on-
   sapphire) and from the GPU layer flipping on/off at transform endpoints. */
.work-cta-line {
  flex: 1;
  height: 1px;
  background: #fff;
  opacity: 0.55;
  transform-origin: left center;
  transform: scaleX(0.4);
  transition: transform .65s var(--ease);
  will-change: transform;
}
.work-cta-row:hover .work-cta-line {
  transform: scaleX(1);
}

.work-cta-chevron {
  width: clamp(22px, 2.4vw, 36px);
  height: auto;
  flex-shrink: 0;
  transition: transform .6s var(--ease);
  color: var(--text, #fff);
}
.work-cta-row:hover .work-cta-chevron { transform: translateX(.5rem); }

/* Accessibility — users who request reduced motion get a simple fade
   instead of the wavy clip-path animation. */
@media (prefers-reduced-motion: reduce) {
  .work-cta-inner--invert {
    clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%) !important;
    opacity: 0;
    transition: opacity .25s ease;
  }
  .work-cta-row:hover .work-cta-inner--invert,
  .work-cta-row:focus .work-cta-inner--invert {
    opacity: 1;
  }
}

/* Section header for the homepage work teaser. Gutter-aligned via .container;
   the image rows below stay full-bleed. Frames the list as a selection. */
.work-list-head {
  padding-top: clamp(3rem, 7vw, 7rem);
  margin-bottom: clamp(2rem, 4vw, 3.5rem);
}
.work-list-head .section-title { margin: 0.6rem 0 0; }

/* One row per project.
   50/50 grid — image's left edge sits at the page centre, never crossing it.
   Height is driven by the image's aspect-ratio (not a fixed px value),
   so it scales naturally with the viewport width. */
.work-item {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  border-bottom: 1px solid var(--rule);
  /* No max-width — row stays full-bleed so the image reaches the right
     viewport edge on every display, including ultrawide. The info column's
     left padding scales with viewport width to keep the text feeling
     anchored rather than lost in the left third. */
}
.work-item:last-child { border-bottom: none; }

/* Left info column — vertically centred, generous padding so text breathes.
   The left-padding cap (26rem ≈ 416px) keeps the text from drifting too far
   from the centre on ultrawide displays — at 3440 px viewport, 14vw ≈ 482px
   wins and the text sits roughly where it would on a 16:9 monitor relative
   to the image (which always begins at viewport centre). */
.work-item-info {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 clamp(2rem, 4vw, 4rem) 0 clamp(4rem, 14vw, 26rem);
  position: relative;
  z-index: 2;
}

.work-item-name {
  font: 300 clamp(22px,2.8vw,44px)/1.1 var(--sans);
  letter-spacing: -0.02em;
  color: var(--text);
  margin-bottom: .35rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Title rendered as the client logo instead of text — mirrors the /work/
   hub card logos so the homepage list and the work grid read as one brand
   system. Modifiers normalise wildly different logo aspect ratios / colours
   (same intent as .card-logo--padded / --narrow / --invert in work.css). */
.work-item-name--logo {
  white-space: normal;   /* undo the text-title ellipsis clamp */
  overflow: visible;     /* don't clip the logo's drop-shadow */
  line-height: 0;        /* drop inline text-box leading around the image */
}
.work-item-logo {
  display: block;
  width: auto; height: auto;
  max-height: clamp(40px, 4vw, 56px);
  max-width: clamp(170px, 15vw, 240px);
  object-fit: contain;
  object-position: left center;
  filter: drop-shadow(0 2px 8px rgba(0,0,0,0.6));
}
.work-item-logo--narrow { max-height: clamp(26px, 2.4vw, 36px); max-width: clamp(130px, 12vw, 180px); }
.work-item-logo--padded { max-height: clamp(64px, 6vw, 88px); }
.work-item-logo--invert { filter: brightness(0) invert(1) drop-shadow(0 2px 8px rgba(0,0,0,0.6)); }

.work-item-sub {
  font: 400 clamp(13px,1.1vw,15px)/1.4 var(--sans);
  color: var(--text-secondary);
  margin-bottom: 1rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.work-item-btn {
  align-self: flex-start;   /* don't stretch to fill the info column width */
  /* Size comes from shared button tokens — no overrides needed */
}

/*
  Right image column.
  height: 100% fills the fixed row height — image is always the same height.
  On hover: ONLY margin-left changes (slides left). No height change.
*/
.work-item-img {
  display: block;
  overflow: hidden;
  border-radius: 22px 0 0 22px;
  /* aspect-ratio drives the height — 3:1 gives a wide cinematic strip
     that scales with the viewport without needing a fixed pixel value */
  aspect-ratio: 3 / 1;
  width: 100%;
  position: relative;
  margin-left: 0;
  /* No hover transitions — scroll position drives the animation */
}

/* No hover movement on work cards — scroll-scrub animation is sufficient */

.work-item-img img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center center;
  display: block;
  /* No scale — image stays completely still, only the block moves */
}

/* Black background for PNG images with alpha — composites the transparency cleanly */
.work-item-img--black { background: #000; }

/* ─── ABOUT ──────────────────────────────────────────────────── */
.about { background: var(--dark); border-top: 1px solid var(--rule); }

.about .section-title {
  font-size: clamp(38px, 5.6vw, 61px);
}

.about-composition.reveal {
  transform: translateY(12px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}

.about-composition {
  display: grid;
  row-gap: clamp(4rem, 8vw, 7rem);
}

.about-grid {
  display: grid;
  grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  gap: clamp(2.5rem, 6.5vw, 7rem);
  align-items: start;
}

.about-copy {
  justify-self: end;
  width: min(100%, 600px);
}

.about-body {
  font-size: var(--t-body-lg);
  font-weight: 400;
  line-height: 1.75;
  color: rgba(255,255,255,0.65);
  margin-bottom: 1.25rem;
  max-width: 560px;
}

.about-body:last-child { margin-bottom: 0; }

.about-stats {
  grid-column: 1 / -1;
}

.stats-rule {
  width: 100%;
  border-top: 1px solid var(--rule);
}

.stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: clamp(2rem, 4vw, 3rem);
}

.stat {
  min-width: 0;
  padding: 0 clamp(.75rem, 2.5vw, 2rem);
}

.stat:first-child { padding-left: 0; }
.stat:last-child { padding-right: 0; }
.stat + .stat { border-left: 1px solid var(--rule); }

.stat-top {
  display: flex; align-items: baseline; gap: .1em;
  margin-bottom: .8rem;
}
.stat-num,
.stat-sfx { color: var(--text); }
.stat-num  { font: 400 clamp(43px, 7.5vw, 78px)/1 var(--sans); letter-spacing: -.02em; }
.stat-sfx  { font: 400 clamp(26px, 3.8vw, 43px)/1 var(--sans); }
.stat-label{
  font: 400 12px/1.45 var(--sans);
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}

/* ─── CONTACT ────────────────────────────────────────────────── */
.contact { background: var(--dark); border-top: 1px solid var(--rule); }

/* Two-column: pitch left, form right. Stacks on narrow viewports. */
.contact-grid {
  display: grid;
  grid-template-columns: minmax(260px, 1.12fr) minmax(320px, 1.3fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
  max-width: 1280px;
  margin: 0 auto;
}
.contact-grid > * { min-width: 0; }

.contact-pitch {
  display: flex; flex-direction: column;
  /* Shift the whole left text block leftward so the gold cycler line has
     more horizontal room before it would visually crash into the form on
     the right. Negative-margin (instead of transform) lets the row above
     reflow naturally and preserves correct hit/scroll behaviour for the
     email link below the title. */
  margin-left: clamp(-5rem, -4vw, -1.5rem);
}
.contact-pitch .label { margin-bottom: 1.5rem; }

.contact-title {
  font-size: var(--t-h1);
  font-weight: var(--fw-h1);
  line-height: 1.08;
  letter-spacing: -.02em;
  margin: 0 0 1.5rem;
}
.ct-line  { display: block; }
.ct-gold {
  color: var(--grad-neon);
  white-space: nowrap;
}
.ct-cycle {
  display: inline-block;
  max-width: 100%;
  will-change: transform, opacity;
}

.contact-meta {
  font-size: var(--t-body);
  line-height: 1.65;
  color: rgba(255,255,255,0.5);
  margin: 0 0 1.75rem;
  max-width: 420px;
}

.contact-email {
  display: inline-block;
  color: var(--blue-hi);
  font-size: 15px;
  text-decoration: none;
  border-bottom: 1px solid rgba(0,149,255,0.35);
  padding-bottom: 2px;
  transition: color 0.2s, border-color 0.2s;
}
.contact-email:hover { color: #fff; border-color: rgba(0,149,255,0.8); }

/* Form */
.contact-form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem 1.25rem;
  align-content: start;
  min-width: 0;
}
.cf-field { display: flex; flex-direction: column; gap: 0.5rem; }
.cf-field--full { grid-column: 1 / -1; }
.cf-field label {
  font: 500 12px/1 var(--sans);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.5);
}
.cf-field input,
.cf-field textarea {
  background: rgba(255,255,255,0.03);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: 8px;
  padding: 0.9rem 1rem;
  color: var(--text);
  font: 400 15px/1.4 var(--sans);
  outline: none;
  transition: border-color 0.2s, background 0.2s;
  resize: none;
}
.cf-field input::placeholder,
.cf-field textarea::placeholder { color: rgba(255,255,255,0.25); }
.cf-field input:focus,
.cf-field textarea:focus {
  border-color: rgba(0,149,255,0.55);
  background: rgba(0,149,255,0.04);
}
.cf-field textarea { min-height: 130px; }

.cf-actions {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  margin-top: 0.5rem;
}
/* cf-submit inherits btn-cta ghost style; these rules handle
   the form-specific content layout and the paper-airplane layer */
/* overflow is switched to visible via GSAP once the button has faded,
   so the gradient stays clipped to the rounded corners during hover */
.cf-submit__content {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  position: relative;
  z-index: 1;   /* stay above the btn-cta::before gradient */
}
.cf-submit__content svg { transition: transform 0.2s; }
.cf-submit:hover .cf-submit__content svg { transform: translateX(3px); }

/* Paper airplane — sized large enough to register as motion across the
   viewport (a 22px sprite moving 1000px reads as a flash; ~40px reads as
   flight). White stroke + glow filter give it presence on dark backdrops.
   Centered inside the button at rest (invisible). */
.cf-airplane {
  position: absolute;
  top: 50%; left: 50%;
  width: 40px; height: 40px;
  margin: -20px 0 0 -20px;
  color: #fff;
  opacity: 0;
  transform: scale(0.4);
  pointer-events: none;
  will-change: transform, opacity;
  filter: drop-shadow(0 0 8px rgba(0, 149, 255, 0.55))
          drop-shadow(0 2px 6px rgba(0, 0, 0, 0.5));
}

/* Post-flight confirmation — absolute-overlays the (now visibility:hidden)
   button so the layout slot stays the same height; no page shift when
   the swap happens. .cf-actions provides the positioning context. */
.cf-actions { position: relative; }
.cf-sent {
  position: absolute;
  top: 0; left: 0;
  display: none;
  align-items: center;
  gap: 0.5rem;
  padding: 0.95rem 0;          /* match button's vertical padding so the
                                  text baseline lines up with where the
                                  button label used to sit */
  font: 600 14px/1 var(--sans);
  color: var(--grad-neon);
  letter-spacing: 0.02em;
  opacity: 0;
}

.cf-status {
  margin: 0;
  font-size: 13px;
  color: rgba(255,255,255,0.55);
  min-height: 1.2em;
}

/* GDPR notice under the contact form — small, muted, with a link to the
   Datenschutzerklärung. No consent checkbox (the form stores only what the
   user types and is covered by the privacy policy). */
.cf-privacy {
  margin: 0.75rem 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: rgba(255,255,255,0.40);
}
.cf-privacy a {
  color: rgba(255,255,255,0.65);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.cf-privacy a:hover { color: #fff; }

/* The pitch sits beside the form only on wide screens. */
@media (max-width: 1300px) {
  .contact-grid { grid-template-columns: 1fr; gap: 2.5rem; }
  .contact-pitch { margin-left: 0; }
}

/* Phone only: collapse the form's own name/company row to a single column. */
@media (max-width: 820px) {
  .contact-form { grid-template-columns: 1fr; }
}

/* Keep the full-size headline readable: on narrow phones the cycling word
   gets a stable line of its own instead of widening or clipping the form. */
@media (max-width: 420px) {
  .ct-gold { white-space: normal; }
  .ct-cycle {
    display: block;
    width: fit-content;
  }
}

/* ─── FOOTER ─────────────────────────────────────────────────── */
.site-footer {
  background: var(--dark);
  border-top: 1px solid var(--rule);
  padding-block: 2.5rem;
}

.footer-inner { display: flex; flex-direction: column; gap: 2rem; }

.footer-top {
  display: flex; align-items: center; gap: 2rem; flex-wrap: wrap;
}
.footer-logo         { opacity: .55; transition: opacity .2s; }
.footer-logo:hover   { opacity: 1; }
.footer-logo img     { height: 40px; width: auto; }

.footer-nav { display: flex; gap: 2rem; margin-left: auto; }
.footer-nav a, .footer-social a {
  font: 400 12px/1 var(--sans); color: var(--muted);
  letter-spacing: .06em; transition: color .2s;
}
.footer-nav a:hover, .footer-social a:hover { color: var(--text); }

.footer-social { display: flex; gap: 1.5rem; }

.footer-bottom {
  display: flex; align-items: center; gap: 2rem;
  border-top: 1px solid var(--rule); padding-top: 1.5rem;
  flex-wrap: wrap;
}
.footer-mark  { font: 400 11px/1 var(--sans); letter-spacing: .15em; color: var(--soft); }
.footer-legal { display: flex; gap: 1.5rem; }
.footer-legal a { font: 400 11px/1 var(--sans); color: var(--soft); transition: color .2s; }
.footer-legal a:hover { color: var(--muted); }
.footer-copy  { margin-left: auto; font: 400 11px/1 var(--sans); color: var(--soft); }

/* ─── SCROLL REVEAL ──────────────────────────────────────────── */

/* Base: fade up (used on most elements) */
.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity .85s var(--ease), transform .85s var(--ease);
}
.reveal.visible { opacity: 1; transform: none; }

/* Stagger delays — apply to any reveal variant */
.rdelay-1 { transition-delay: .10s; }
.rdelay-2 { transition-delay: .20s; }
.rdelay-3 { transition-delay: .32s; }

/* Work item transforms are driven by JS scroll-scrub.
   No CSS transitions on transform/opacity for these elements —
   the JS tick() writes values every frame directly.
   Only margin-left keeps its transition (for the hover slide effect). */
.work-list .work-item-info,
.work-list .work-item-img {
  transition: margin-left .7s cubic-bezier(0.4,0,0.2,1),
              border-radius .7s cubic-bezier(0.4,0,0.2,1);
  /* transform and opacity intentionally NOT transitioned here */
}

/* GPU compositor hint — promotes the animated elements to their own
   layers so the browser composites them on the GPU instead of repainting
   per frame. Big perf win for the scrub-driven slide-in. */
.work-item-name,
.work-item-sub,
.work-item-btn,
.work-item-img {
  will-change: transform, opacity;
}

/* ─── REDUCED MOTION ─────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, .reveal, .scroll-thumb, .cursor-dot, .cursor-ring,
  .text-draw__box svg, .text-draw__box svg path {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  .about-composition.reveal { transform: none; }
  /* Visibility must update synchronously before the menu receives focus;
     the global tiny duration otherwise transitions inherited visibility. */
  .mobile-menu, .mobile-menu * { transition: none !important; }
}

/* ─── RESPONSIVE ─────────────────────────────────────────────── */
@media (max-width: 900px) {
  .nav-links       { display: none; }
  .nav-social      { display: none; }   /* already in mobile menu footer */
  .hamburger       { display: flex; }

  /* Work list — stack vertically on tablet */
  .work-item {
    grid-template-columns: 1fr;
    height: auto;          /* allow auto height when stacked */
    overflow: visible;
  }
  .work-item-info {
    padding: 1.5rem var(--gutter) 1.25rem;
    overflow: visible;
  }
  .work-item-name { white-space: normal; }
  .work-item-sub  { white-space: normal; }
  .work-item-img {
    aspect-ratio: 16 / 9;   /* wider on mobile */
    /* Stick to the right edge like the desktop layout: one gutter on the
       LEFT, flush to the right edge, with only the left corners rounded so
       the card "bleeds" off the right the same way it does at full width.
       (Previously margin-left:0 !important fought the gutter and shoved the
       card to the left.) */
    width: calc(100% - var(--gutter));
    margin: 0 0 1.5rem auto;
    border-radius: 16px 0 0 16px;
  }

  /* CTA row matches stacked work-item gutter on mobile. */
  .work-cta-row {
    min-height: auto;
  }
  .work-cta-inner {
    padding: 1.75rem var(--gutter);
    min-height: auto;
    gap: 1rem;
  }

  .about-grid {
    grid-template-columns: 1fr;
    gap: 2.5rem;
  }
  .about-copy { justify-self: start; }

  .footer-nav { margin-left: 0; order: 3; width: 100%; }
  .footer-copy { margin-left: 0; width: 100%; }
}

@media (max-width: 680px) {
  .about-composition { row-gap: 3.5rem; }
  .about-grid { gap: 2rem; }
  .stats { margin-top: 1.75rem; }
  .stat { padding-inline: .75rem; }
  .stat-num { font-size: clamp(39px, 10.8vw, 52px); }
  .stat-sfx { font-size: clamp(24px, 6.5vw, 35px); }
  .stat-label { font-size: 11.5px; letter-spacing: .065em; }
  .footer-social { order: 2; }
}

@media (max-width: 340px) {
  .stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    row-gap: 1.5rem;
  }
  .stat:last-child {
    grid-column: 1 / -1;
    width: 50%;
    justify-self: center;
    border-left: none;
    padding-inline: .75rem;
    padding-top: .25rem;
  }
}

/* ═══════════════════════════════════════════════════════════════
   SECTION 2 — INTRODUCTION TEXT
   ═══════════════════════════════════════════════════════════════ */
.intro-section {
  background: var(--dark);
  padding-block: clamp(6rem,12vw,10rem);
  text-align: center;
}

.intro-inner {
  max-width: var(--max-w);   /* full container width — lets heading stay on one line */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(2.5rem,5vw,4rem);
}

/* ── Heading ── */
.intro-heading-wrap {
  width: 100%;
  position: relative;
  top: 28px;
}

.intro-heading {
  /* Light weight — NOT bold, matching live site */
  font-size: var(--t-display);
  font-weight: var(--fw-display);   /* 300 */
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--text);
  display: inline;
}


/* "Stand Out." wrapper — holds multi-scribble SVG */
.wave-word {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}

/* ── Hover-draw underline (osmo pattern) ──────────────────────────
   On hover of .wave-word: a randomised SVG underline is injected
   into .text-draw__box and DrawSVG-animated in.
   Each injected SVG has its own inline <defs> with the moove gradient. */

.text-draw__box {
  position: absolute;
  left: 0;
  right: 0;                      /* both edges anchored — never collapses to 0 */
  bottom: -0.22em;
  height: 0.42em;                /* tall enough that the squashed stroke is clearly visible */
  pointer-events: none;
}

.text-draw__box svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;             /* let the rounded line caps extend past the bounding box */
}

/* ── Body text ── */
.intro-body-wrap {
  position: relative;
  top: 140px;
  /* Match the committed copy's rendered measure, with its 900px cap intact.
     Reserve its line budget plus the former CTA, margin and flex gap so
     removing the button does not move the headline or client wall. */
  --intro-copy-lines: 2;
  width: min(100%, calc(37.187 * var(--t-body-lg)));
  max-width: 900px;
  min-height: calc(var(--intro-copy-lines) * 1.7 * var(--t-body-lg) + clamp(2.5rem,5vw,4rem) + 2.5rem + 17px);
  padding-top: 12px;
  display: flex;
  flex-direction: column;
  gap: .35rem;
}

.intro-body {
  font-size: var(--t-body-lg);
  font-weight: 400;
  line-height: 1.7;
  color: var(--text);           /* WHITE — matches screenshot exactly */
  opacity: 0.88;
}

/* Preserve the committed copy's responsive line budget, not a viewport height. */
@media (max-width: 607px) {
  .intro-body-wrap { --intro-copy-lines: 3; }
}
@media (max-width: 342px) {
  .intro-body-wrap { --intro-copy-lines: 4; }
}
@media (max-width: 330px) {
  .intro-body-wrap { --intro-copy-lines: 5; }
}


/* ═══════════════════════════════════════════════════════════════
   WAVE DIVIDER — between sections (intro/logos, logos/work)
   ═══════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════
   SECTION 3 — CLIENT LOGOS + PARTICLES
   ═══════════════════════════════════════════════════════════════ */
.logos-section {
  background: var(--dark);
  padding-block: clamp(4rem,8vw,7rem);
  position: relative;
  overflow: hidden;
}

.logos-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(2rem,4vw,3.5rem);
}

/* Single row of 6 cycling slots — strict nowrap so all 6 stay on one line
   on desktop. Mobile wraps to 3+3 via the media query below. */
.logos-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(1rem, 3vw, 3rem);
  flex-wrap: nowrap;
  width: 100%;
}

/* Logo carousel — 6 fixed slots, each one cycles through a pool of 11 logos
   via GSAP (see clientLogoCarousel() in gsap.js). The slot is a clipped
   box; the inner element holds the current logo and tweens vertically out
   the top and the new logo slides up from below. Pattern modelled on
   the gsap.com homepage "Made with GSAP" rotation strip.

   Hover on a slot pauses that slot's rotation (so a visitor can read
   a specific logo without it changing mid-look). All other slots
   continue cycling. */
.logo-slot {
  position: relative;
  width: clamp(96px, 11vw, 160px);
  height: clamp(48px, 6vw, 76px);
  display: flex;
  align-items: center;
  justify-content: center;
  /* Clip the inner sliding element. */
  overflow: hidden;
  /* Subtle hint that something is interactive — cursor stays the site
     default ('none' globally), no extra effect needed. */
}

.logo-slot__inner {
  position: relative;
  width: 100%;
  height: 100%;
  /* Children are absolutely positioned so initial-markup imgs and
     JS-created replacement imgs use the SAME layout box. Previously
     the initial img was flex-positioned at its natural aspect-ratio
     width (e.g. 135px in a 160px slot), so a translateX(-100%)
     moved by only 135px — not enough to fully clear the slot's 160px
     edge, leaving a ~25px sliver of the logo visible during the wipe. */
}

.logo-slot__inner img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 1;
  filter: brightness(1.15);
}

/* No hover state — full brightness at all times reads as confidence
   ("these are our clients") and avoids competing with the wave animation
   for the visitor's attention. The hover-pause behaviour in JS was also
   removed for the same reason. */


/* ─── RESPONSIVE ─────────────────────────────────────────────── */
@media (max-width: 900px) {
  .hero-left { max-width: 70%; }
  .logos-row { gap: clamp(1.5rem,4vw,3rem); }
}
@media (max-width: 680px) {
  .hero-left  { max-width: 85%; }
  .hero-wordmark { width: clamp(140px,55vw,260px); }
  /* Force an exact 3-column grid on mobile so the 6 logos always lay out
     3 + 3. flex-wrap previously fit "as many as possible", which packed 4
     onto the first row and 2 onto the second at the narrowest widths. */
  .logos-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.25rem 1rem;
    justify-items: center;
  }
  .logo-slot { width: 100%; max-width: 120px; height: 40px; }
  .intro-heading { font-size: clamp(30px,8vw,52px); }
}
@media (max-width: 420px) {
  .logos-row { gap: 1rem 1.5rem; }
}


/* ═══════════════════════════════════════════════════════════════
   SERVICES — authored product states in one stable image stage.
   The image is the focal point; navigation and copy remain quiet.
   ═══════════════════════════════════════════════════════════════ */
.svc3-section {
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  padding-block: clamp(4rem, 7vw, 6.5rem);
  background: var(--dark);
  border-bottom: 1px solid var(--rule);
  scroll-margin-top: 72px;
  overflow: visible;
}

.svc3-section::before,
.svc3-section::after {
  content: none;
}

.svc3-section > .container {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: none;
  min-width: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  padding-left: clamp(2rem, 8vw, 10rem);
  padding-right: clamp(2rem, 4.5vw, 5.5rem);
}

.svc3-grid {
  flex: 1;
  min-width: 0;
  display: grid;
  grid-template-columns:
    minmax(190px, 240px)
    minmax(220px, .72fr)
    minmax(0, 2.3fr);
  grid-template-rows: 1fr auto auto 1fr;
  column-gap: clamp(2rem, 4vw, 4.5rem);
  row-gap: clamp(2rem, 3.5vw, 3.25rem);
  align-items: start;
}

.svc3-header,
.svc3-tabs,
.svc3-panels,
.services-visual-stage {
  min-width: 0;
  max-width: 100%;
}

.svc3-header {
  grid-column: 1 / 3;
  grid-row: 2;
  position: relative;
  z-index: 2;
}

.svc3-eyebrow {
  margin: 0 0 1rem;
  color: var(--blue-hi);
  font: 600 12px/1 var(--sans);
  letter-spacing: .2em;
  text-transform: uppercase;
}

.svc3-headline {
  margin: 0 0 1rem;
  font-size: var(--t-h1);
  font-weight: var(--fw-h1);
  line-height: 1.08;
  letter-spacing: -.02em;
}

.svc3-lede {
  max-width: 36ch;
  margin: 0;
  color: rgba(255, 255, 255, .55);
  font-size: clamp(15px, 1vw, 17px);
  line-height: 1.6;
}

.svc3-tabs {
  grid-column: 1;
  grid-row: 3;
  position: relative;
  z-index: 2;
  align-self: start;
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid rgba(255, 255, 255, .15);
}

.svc3-tabs > li {
  min-width: 0;
  list-style: none;
  border-bottom: 1px solid rgba(255, 255, 255, .1);
}

.svc3-tab {
  position: relative;
  width: 100%;
  min-width: 0;
  display: grid;
  grid-template-columns: 2.4rem minmax(0, 1fr);
  align-items: center;
  gap: .75rem;
  padding: 1.05rem 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: rgba(255, 255, 255, .52);
  font: 500 15px/1.3 var(--sans);
  letter-spacing: -.005em;
  text-align: left;
  cursor: none;
  transition: color .25s ease;
}

.svc3-tab::before {
  content: "";
  position: absolute;
  left: -.85rem;
  top: .8rem;
  bottom: .8rem;
  width: 2px;
  background: var(--blue-hi);
  opacity: 0;
  transform: scaleY(.5);
  transition: opacity .25s ease, transform .25s ease;
}

.svc3-tab:hover {
  color: rgba(255, 255, 255, .84);
}

.svc3-tab:focus-visible {
  outline: 1px solid var(--blue-hi);
  outline-offset: 4px;
}

.svc3-tab.is-active {
  color: var(--text);
}

.svc3-tab.is-active::before {
  opacity: 1;
  transform: scaleY(1);
}

.svc3-tab__num {
  color: rgba(255, 255, 255, .38);
  font: 400 12px/1 var(--sans);
  font-variant-numeric: tabular-nums;
  letter-spacing: .06em;
  transition: color .25s ease;
}

.svc3-tab.is-active .svc3-tab__num {
  color: var(--blue-hi);
}

.svc3-tab__name {
  min-width: 0;
}

.svc3-panels {
  grid-column: 2;
  grid-row: 3;
  position: relative;
  z-index: 2;
  min-height: 280px;
  align-self: start;
}

.svc3-panel {
  position: absolute;
  inset: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.2rem;
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
  transition: opacity .42s var(--ease), transform .42s var(--ease);
}

.svc3-panel[hidden] {
  display: none;
}

.svc3-panel.is-active {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.svc3-panel.is-active.is-entering {
  opacity: 0;
  transform: translateY(10px);
}

.svc3-panel.is-leaving {
  display: flex;
  opacity: 0;
  transform: translateY(-8px);
}

.svc3-panel__num {
  margin: 0 0 .25rem;
  color: var(--blue-hi);
  font: 500 13px/1 var(--sans);
  letter-spacing: .18em;
}

.svc3-panel__title {
  margin: 0;
  font-size: clamp(28px, 2.6vw, 42px);
  font-weight: 300;
  line-height: 1.05;
  letter-spacing: -.02em;
}

.svc3-panel__desc {
  max-width: 42ch;
  margin: 0;
  color: rgba(255, 255, 255, .6);
  font-size: clamp(15px, 1vw, 17px);
  line-height: 1.7;
}

.services-visual-stage {
  grid-column: 3;
  grid-row: 1 / -1;
  position: relative;
  align-self: center;
  width: 100%;
  aspect-ratio: 1500 / 749;
  isolation: isolate;
}

.services-visual-layer {
  position: absolute;
  inset: 0;
  z-index: 0;
  margin: 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px) scale(.985);
  transition:
    opacity .54s var(--ease),
    transform .54s var(--ease),
    visibility 0s linear .54s;
}

.services-visual-layer.is-active {
  z-index: 1;
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}

.services-visual-layer.is-failed .services-visual-image {
  visibility: hidden;
}

.services-visual-image {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  max-width: 100%;
  object-fit: contain;
  object-position: 50% 50%;
}

@media (max-width: 1100px) {
  .svc3-section {
    min-height: 0;
    display: block;
    padding-block: clamp(5.5rem, 10vw, 7rem) clamp(4.5rem, 8vw, 6rem);
  }

  .svc3-section > .container {
    display: block;
    padding-inline: clamp(1.5rem, 5vw, 3.5rem);
  }

  .svc3-grid {
    display: grid;
    grid-template-columns: minmax(220px, .72fr) minmax(0, 1.7fr);
    grid-template-rows: auto auto auto;
    gap: clamp(1.75rem, 4vw, 2.75rem) clamp(2rem, 5vw, 3.5rem);
  }

  .svc3-header {
    grid-column: 1 / 3;
    grid-row: 1;
  }

  .svc3-tabs {
    grid-column: 1 / 3;
    grid-row: 2;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    border-top-color: rgba(255, 255, 255, .14);
  }

  .svc3-tabs > li + li {
    border-left: 1px solid rgba(255, 255, 255, .08);
  }

  .svc3-tab {
    height: 100%;
    grid-template-columns: auto minmax(0, 1fr);
    align-content: start;
    align-items: baseline;
    gap: .55rem;
    padding: .9rem .8rem;
    font-size: 13px;
  }

  .svc3-tab::before {
    left: .8rem;
    right: .8rem;
    top: auto;
    bottom: -1px;
    width: auto;
    height: 2px;
    transform: scaleX(.5);
  }

  .svc3-tab.is-active::before {
    transform: scaleX(1);
  }

  .svc3-tab__num {
    font-size: 11px;
  }

  .svc3-panels {
    grid-column: 1;
    grid-row: 3;
    min-height: 250px;
  }

  .services-visual-stage {
    grid-column: 2;
    grid-row: 3;
    width: 100%;
    align-self: start;
  }
}

@media (max-width: 760px) {
  .svc3-section {
    padding-block: 5.25rem 4.5rem;
    scroll-margin-top: 64px;
  }

  .svc3-section > .container {
    width: 100%;
    min-width: 0;
    padding-inline: clamp(1rem, 5vw, 1.5rem);
  }

  .svc3-grid {
    width: 100%;
    min-width: 0;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto auto;
    gap: 1.6rem;
  }

  .svc3-header {
    grid-column: 1;
    grid-row: 1;
  }

  .svc3-lede {
    max-width: 34ch;
  }

  .svc3-tabs {
    grid-column: 1;
    grid-row: 2;
    width: 100%;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .svc3-tabs > li {
    min-width: 0;
  }

  .svc3-tabs > li:nth-child(odd) {
    border-left: 0;
  }

  .svc3-tabs > li:nth-child(even) {
    border-left: 1px solid rgba(255, 255, 255, .08);
  }

  .svc3-tab {
    min-width: 0;
    padding: .82rem .6rem;
    font-size: clamp(12.5px, 3.5vw, 14px);
    line-height: 1.25;
  }

  .svc3-tab::before {
    left: .6rem;
    right: .6rem;
  }

  .services-visual-stage {
    grid-column: 1;
    grid-row: 3;
    width: 100%;
    min-width: 0;
    max-width: 100%;
  }

  .svc3-panels {
    grid-column: 1;
    grid-row: 4;
    width: 100%;
    min-width: 0;
    min-height: 220px;
  }
}

@media (max-width: 340px) {
  .svc3-section > .container {
    padding-inline: 1rem;
  }

  .svc3-grid {
    gap: 1.35rem;
  }

  .svc3-tab {
    grid-template-columns: auto minmax(0, 1fr);
    gap: .45rem;
    padding-inline: .45rem;
    font-size: 12px;
  }

  .svc3-tab__num {
    font-size: 10.5px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .svc3-tab,
  .svc3-tab::before,
  .svc3-tab__num,
  .svc3-panel,
  .services-visual-layer {
    transition: none !important;
  }

  .svc3-panel,
  .svc3-panel.is-active,
  .svc3-panel.is-active.is-entering,
  .svc3-panel.is-leaving,
  .services-visual-layer,
  .services-visual-layer.is-active {
    transform: none !important;
  }
}


/* ═══════════════════════════════════════════════════════════════
   PAGE CURTAIN — curve-swipe page transition

   Full-screen overlay used by js/page-transition.js. Hidden by
   default; the JS shows + animates it. Exception: when the
   destination page is loaded via a transition, the head script
   adds .is-transitioning on <html> so this CSS rule starts the
   curtain in its covered state, killing the flash-of-new-page.
   ═══════════════════════════════════════════════════════════════ */
.page-curtain {
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  visibility: hidden;
  opacity: 0;
}
.page-curtain__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.page-curtain__path {
  fill: url(#page-curtain-grad);
}

/* Gradient stops are driven by --curtain-color so each project only
   needs to set one variable. color-mix() darkens the edge stops
   automatically — center glows, edges fall to near-black. */
#page-curtain-grad stop:first-child,
#page-curtain-grad stop:last-child {
  stop-color: color-mix(in srgb, var(--curtain-color, #08091c) 25%, #000);
}
#page-curtain-grad stop:nth-child(2) {
  stop-color: var(--curtain-color, #08091c);
}
.page-curtain__loader {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 56px;
  height: 56px;
  transform: translate(-50%, -50%);
  visibility: hidden;
  opacity: 0;
}
.page-curtain__loader-arc {
  transform-origin: center;
  animation: pageCurtainSpin 0.9s linear infinite;
}
@keyframes pageCurtainSpin {
  to { transform: rotate(360deg); }
}

/* When arriving via a curve-swipe, the head script adds this class
   so the curtain is fully covered the instant CSS parses — before
   any other content renders. JS then plays the reveal animation.
   The `overflow: hidden` rule suppresses the page's scrollbar for
   the duration of the transition; otherwise the destination page's
   scrollbar pops in on the right edge during the cover/reveal,
   visible past the curtain — jarring. Class is removed once the
   reveal animation completes (page-transition.js onComplete).
   padding-right compensates for the hidden scrollbar gutter so the
   content area stays the same width as it would be with the scrollbar
   visible — prevents the layout shift / "pop" when overflow:hidden is
   removed and the scrollbar reappears. --sb-width is set by
   page-transition.js at load time using a temp scrolling element. */
html.is-transitioning {
  overflow: hidden;
  padding-right: var(--sb-width, 0px);
}
/* Default state: curtain is REMOVED from rendering entirely (not just
   hidden). Even with visibility:hidden + opacity:0, a fixed-position
   SVG with a coloured gradient fill can leak 1-2px through sub-pixel
   compositor rendering during transformed scroll — visible as a thin
   red line on pages with a red --curtain-color (e.g. Rivella). The
   `display: none` rule keeps the curtain in the DOM (so JS can still
   target it) but removes it from the rendering tree completely. */
.page-curtain {
  display: none;
}
html.is-transitioning .page-curtain {
  display: block;
  visibility: visible;
  opacity: 1;
}
html.is-transitioning .page-curtain__loader {
  visibility: visible;
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  /* JS skips transitions entirely for reduced motion. Stop the CSS
     animation too so the arc never spins even if the curtain shows. */
  .page-curtain__loader-arc { animation: none; }
}

/* ── ZOOM ARRIVAL ─────────────────────────────────────────────────────
   .is-zoom-arriving is set by the head script on every page when
   sessionStorage['moove-zoom-from'] is present. It fires before CSS
   parses so the initial paint matches the zoom clone on the outgoing page:
   hero media covers the viewport, everything else starts hidden.
   page-transition.js removes the class and fades/slides content in. */

/* Hero media stays opaque — visual continuity with the zoom clone. */
html.is-zoom-arriving .proj-hero-media,
html.is-zoom-arriving .s3-hero-img,
html.is-zoom-arriving .smeg-sequence { opacity: 1; }

/* Project-specific overlays (Rivella's slate bars, Smart's back button)
   are not visible on the work-page card, so they fade in on arrival.
   The nav and hero vignette ARE matched on the work-page card at the
   end of the forward zoom, so they stay visible at first paint —
   any fade-in here would produce a flicker. */

/* Content items start invisible AND shifted so GSAP slide-up has a
   starting position. translateY(20px) is the slide distance. */
html.is-zoom-arriving .proj-hero-content,
html.is-zoom-arriving .s3-hero-content,
html.is-zoom-arriving .proj-slate-content {
  opacity: 0;
  transform: translateY(20px);
  will-change: transform;
}

/* Zoom-back arrival: barrel, side-nav and hint start hidden so
   the full-screen clone is the only thing visible on first paint.
   work.js fades them back in as the clone shrinks to the card. */
html.is-zoom-back-arriving .carousel,
html.is-zoom-back-arriving .wp-progress,
html.is-zoom-back-arriving .wp-hint { opacity: 0; }



/* ═══════════════════════════════════════════════════════════════
   SCROLLBAR — dark gray to blend with the site

   Targets all browsers:
     - Firefox: scrollbar-width + scrollbar-color (CSS standard)
     - Chrome / Edge / Safari: ::-webkit-scrollbar pseudo-elements
   The work page sets body { overflow: hidden } so it has no
   scrollbar — these rules are no-ops there.
   ═══════════════════════════════════════════════════════════════ */

/* Firefox + standard. thumb / track. */
html {
  scrollbar-width: thin;
  scrollbar-color: #2a2d3a transparent;
  /* Reserve space for the scrollbar at all times so toggling between
     overflow:hidden (during the curve-swipe transition) and overflow:auto
     (normal page scroll) doesn't shift content horizontally. The
     reserved gutter sits on the right with a transparent track when no
     scrollbar is needed; the actual thumb appears in the same gutter
     once the page becomes scrollable — zero layout shift. */
  scrollbar-gutter: stable;
}

/* WebKit/Blink — finer-grained styling. */
::-webkit-scrollbar         { width: 10px; height: 10px; }
::-webkit-scrollbar-track   { background: transparent; }
::-webkit-scrollbar-thumb {
  background: #2a2d3a;
  border-radius: 5px;
  /* Subtle inner padding via transparent border so the thumb feels
     less aggressive against the dark page background. */
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover  { background-color: #3a3f50; background-clip: content-box; }
::-webkit-scrollbar-thumb:active { background-color: #4a5168; background-clip: content-box; }
::-webkit-scrollbar-corner       { background: transparent; }

/* ─── PROJECT CTA — shared across all project pages ──────────── */
.proj-cta-section {
  background: var(--dark);
  border-top: 1px solid var(--rule);
  padding-block: clamp(7rem, 15vw, 13rem);
  text-align: center;
}
.proj-cta {
  max-width: 900px;
  margin: 0 auto;
}
.proj-cta-title {
  font-size: clamp(2.5rem, 7vw, 5.5rem);
  font-weight: var(--fw-h1);
  line-height: 1.05;
  letter-spacing: -0.03em;
  margin-bottom: 3rem;
  margin-top: 1rem;
}
