/* Pro Negotiate - landing page
   Design read: premium-consumer / personal-mastery landing for professionals
   training deliberate negotiation. Cinematic, confident-noir editorial language.
   Dark theme locked (Section 4.11). Single accent: blue, the one saturated
   color against an otherwise black-and-white world.
   Corner-radius lock: buttons + pills = full radius, cards/panels/media = 2px.
*/

/* ---------- Fonts ---------- */
@font-face { font-family: 'Geo';  font-weight: 700; font-display: swap; src: url('assets/fonts/CalSansUI-GeoBold.woff2') format('woff2'); }
@font-face { font-family: 'Geo';  font-weight: 600; font-display: swap; src: url('assets/fonts/CalSansUI-GeoSemiBold.woff2') format('woff2'); }
@font-face { font-family: 'Geo';  font-weight: 500; font-display: swap; src: url('assets/fonts/CalSansUI-GeoMedium.woff2') format('woff2'); }
@font-face { font-family: 'Geo';  font-weight: 400; font-display: swap; src: url('assets/fonts/CalSansUI-GeoRegular.woff2') format('woff2'); }
@font-face { font-family: 'Geo';  font-weight: 300; font-display: swap; src: url('assets/fonts/CalSansUI-GeoLight.woff2') format('woff2'); }

@font-face { font-family: 'Text'; font-weight: 700; font-display: swap; src: url('assets/fonts/CalSansUI-TextBold.woff2') format('woff2'); }
@font-face { font-family: 'Text'; font-weight: 600; font-display: swap; src: url('assets/fonts/CalSansUI-TextSemiBold.woff2') format('woff2'); }
@font-face { font-family: 'Text'; font-weight: 500; font-display: swap; src: url('assets/fonts/CalSansUI-TextMedium.woff2') format('woff2'); }
@font-face { font-family: 'Text'; font-weight: 400; font-display: swap; src: url('assets/fonts/CalSansUI-TextRegular.woff2') format('woff2'); }
@font-face { font-family: 'Text'; font-weight: 300; font-display: swap; src: url('assets/fonts/CalSansUI-TextLight.woff2') format('woff2'); }

@font-face { font-family: 'UI'; font-weight: 700; font-display: swap; src: url('assets/fonts/CalSansUI-UIBold.woff2') format('woff2'); }
@font-face { font-family: 'UI'; font-weight: 600; font-display: swap; src: url('assets/fonts/CalSansUI-UISemiBold.woff2') format('woff2'); }
@font-face { font-family: 'UI'; font-weight: 500; font-display: swap; src: url('assets/fonts/CalSansUI-UIMedium.woff2') format('woff2'); }
@font-face { font-family: 'UI'; font-weight: 400; font-display: swap; src: url('assets/fonts/CalSansUI-UIRegular.woff2') format('woff2'); }

/* ---------- Tokens ---------- */
:root {
  --bg: #faf9f7;
  --bg-elevated: #f1efe9;
  --bg-elevated-2: #e9e6de;
  --text: #14120f;
  --text-dim: #5b564e;
  --text-faint: #928c81;
  --accent: #3358b8;
  --accent-strong: #29468f;
  --accent-ink: #faf9f7;
  --line: rgba(20, 18, 15, 0.1);
  --line-strong: rgba(20, 18, 15, 0.18);

  --radius-soft: 2px;
  --radius-pill: 999px;

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --nav-h: 72px;
}

* { box-sizing: border-box; }
html { color-scheme: light; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: 'Text', ui-sans-serif, system-ui, sans-serif;
  font-weight: 400;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* The kinetic wordmark is the first frame of each landing-page visit. The class is
   added in the head so the landing page never flashes before the film. */
html.intro-pending,
html.intro-pending body { overflow: hidden; }
.site-intro {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1000;
  place-items: center;
  overflow: hidden;
  background: #fff;
  opacity: 1;
  transition: opacity 1.85s cubic-bezier(0.4, 0, 0.2, 1);
}
html.intro-pending .site-intro { display: grid; }
.site-intro.is-leaving {
  opacity: 0;
  pointer-events: none;
}

/* Cinematic handoff: the page begins behind the white film, then resolves
   forward as the film dissolves. Only transform/opacity/filter change, so the
   fixed navigation and page layout never jump. */
html.intro-pending .nav,
html.intro-pending main,
html.intro-pending .footer,
html.intro-pending .skip-link {
  will-change: transform, opacity, filter;
  transition:
    opacity 1.85s cubic-bezier(0.4, 0, 0.2, 1),
    transform 2.1s cubic-bezier(0.4, 0, 0.2, 1),
    filter 1.85s cubic-bezier(0.4, 0, 0.2, 1);
}
html.intro-pending:not(.intro-revealing) .nav,
html.intro-pending:not(.intro-revealing) main,
html.intro-pending:not(.intro-revealing) .footer,
html.intro-pending:not(.intro-revealing) .skip-link {
  opacity: 0;
}
html.intro-pending:not(.intro-revealing) main {
  transform: scale(0.955) translateY(14px);
  transform-origin: 50% 48%;
  filter: blur(6px);
}
html.intro-pending:not(.intro-revealing) .nav {
  transform: translateY(-10px);
  filter: blur(3px);
}
html.intro-pending:not(.intro-revealing) .footer,
html.intro-pending:not(.intro-revealing) .skip-link {
  transform: translateY(10px);
  filter: blur(3px);
}
html.intro-pending.intro-revealing .nav,
html.intro-pending.intro-revealing main,
html.intro-pending.intro-revealing .footer,
html.intro-pending.intro-revealing .skip-link {
  opacity: 1;
  transform: none;
  filter: none;
}
.site-intro-video {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
  background: #fff;
}
.site-intro-skip {
  position: absolute;
  top: max(20px, env(safe-area-inset-top));
  right: max(20px, env(safe-area-inset-right));
  min-height: 44px;
  padding: 0 18px;
  border: 1px solid rgba(20, 18, 15, 0.18);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.9);
  color: var(--text);
  font: 600 13px/1 'UI', ui-sans-serif, sans-serif;
  cursor: pointer;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: background-color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.site-intro-skip:hover {
  background: #fff;
  border-color: var(--text);
}
.site-intro-skip:focus-visible {
  outline: 3px solid rgba(51, 88, 184, 0.35);
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .site-intro { display: none !important; }
}
.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;
}

img { max-width: 100%; display: block; }

.wrap {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 24px;
}
.wrap-narrow {
  max-width: 760px;
}

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--accent);
  color: var(--accent-ink);
  padding: 10px 16px;
  z-index: 200;
  border-radius: var(--radius-soft);
}
.skip-link:focus {
  left: 24px;
  top: 12px;
}

/* ---------- Buttons ----------
   Origin-fill hover: a circle expands from the pointer/tap/focus point to
   cover the button (0.5s, var(--ease)), while the label color transitions
   for contrast. Ported from a React/Motion "OriginButton" component to
   vanilla JS/CSS (see initOriginButtons in script.js) - same interaction,
   this project's own colors instead of the component's default theme. */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  isolation: isolate;
  white-space: nowrap;
  font-family: 'UI', ui-sans-serif, sans-serif;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.01em;
  text-decoration: none;
  border-radius: var(--radius-pill);
  padding: 13px 26px;
  transition: transform 0.2s var(--ease), border-color 0.2s var(--ease), color 0.3s var(--ease);
  cursor: pointer;
  border: 1px solid transparent;
}
.btn:active { transform: scale(0.97); }
.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
}
.btn-ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--line-strong);
}
.btn-ghost:hover { border-color: var(--text-dim); }
.btn-small { padding: 9px 20px; font-size: 13.5px; }
.btn-large { padding: 17px 34px; font-size: 16px; }

.btn-label {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.btn-fill {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  z-index: 0;
  border-radius: 50%;
  background: var(--text);
  transform: translate(-50%, -50%) scale(0);
  transform-origin: center;
  pointer-events: none;
  will-change: transform;
  transition: transform 0.5s var(--ease);
}
.btn-fill.is-filled { transform: translate(-50%, -50%) scale(1); }
.btn-primary .btn-fill { background: var(--accent-strong); }
.btn.is-filled { color: var(--bg); }

@media (prefers-reduced-motion: reduce) {
  .btn { transition: none; }
  .btn:active { transform: none; }
  .btn-fill { display: none; }
  .btn-primary:hover { background: #4569c9; }
}

/* ---------- Nav ---------- */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--nav-h);
  z-index: 100;
  display: flex;
  align-items: center;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color 0.3s var(--ease), border-color 0.3s var(--ease), backdrop-filter 0.3s var(--ease);
}
.nav.is-scrolled {
  background: rgba(250, 249, 247, 0.82);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom-color: var(--line);
}
.nav-inner {
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 24px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 32px;
}
.nav-brand {
  font-family: 'Geo';
  font-weight: 700;
  font-size: 24px;
  color: var(--text);
  text-decoration: none;
  letter-spacing: -0.02em;
  justify-self: start;
}
.nav-brand-accent { color: var(--accent); }
.brand-domain { color: var(--text); }
.nav-links {
  display: flex;
  gap: 28px;
  justify-self: center;
}
.nav-inner > .btn { justify-self: end; }
.nav-links a {
  font-family: 'UI';
  font-weight: 500;
  font-size: 14.5px;
  color: #000;
  text-decoration: none;
  transition: color 0.2s var(--ease);
}
.nav-links a:hover { color: #000; }
/* Variable-font-style hover, ported from a React component: each letter
   thickens, rippling out from the centre of the word. The shipped UI family
   is static - separate files per weight - so font-variation-settings does
   nothing on it (measured: identical widths at wght 400/550/700). This drives
   font-weight instead, which steps through the 400/500/600/700 faces. */
.nav-letter {
  display: inline-block;
  font-weight: 400;
  transition: font-weight 0.3s var(--ease);
  transition-delay: var(--stagger, 0s);
}
.nav-links a:hover .nav-letter,
.nav-links a:focus-visible .nav-letter { font-weight: 700; }

@media (prefers-reduced-motion: reduce) {
  .nav-letter { transition: none; }
}
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 40px;
  height: 40px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}
.nav-toggle span {
  display: block;
  height: 2px;
  width: 22px;
  background: var(--text);
  transition: transform 0.25s var(--ease), opacity 0.25s var(--ease);
}
.nav-mobile {
  display: none;
  position: fixed;
  top: var(--nav-h);
  left: 0; right: 0;
  background: rgba(250, 249, 247, 0.98);
  border-bottom: 1px solid var(--line);
  padding: 8px 24px 24px;
  flex-direction: column;
  gap: 4px;
}
.nav-mobile a {
  font-family: 'UI';
  font-weight: 500;
  font-size: 16px;
  color: var(--text);
  text-decoration: none;
  padding: 14px 4px;
  border-bottom: 1px solid var(--line);
}
.nav-mobile .btn { margin-top: 14px; }
.nav-mobile.is-open { display: flex; }

@media (max-width: 768px) {
  .nav-inner { grid-template-columns: minmax(0, 1fr) auto; }
  .nav-links, .nav-inner > .btn { display: none; }
  .nav-toggle { display: flex; justify-self: end; }
}
.nav-toggle.is-active span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle.is-active span:nth-child(2) { opacity: 0; }
.nav-toggle.is-active span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* ---------- Hero ---------- */
.hero {
  position: relative;
  min-height: 100dvh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}
.hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 68% center;
  transform: translateX(clamp(84px, 7vw, 132px));
  background: var(--bg);
}
.hero-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg, rgba(250,249,247,0.12) 0%, rgba(250,249,247,0) 30%, rgba(250,249,247,0.1) 66%, rgba(250,249,247,0.96) 100%),
    linear-gradient(90deg, rgba(250,249,247,0.82) 0%, rgba(250,249,247,0.38) 46%, rgba(250,249,247,0) 64%);
}
.hero-content {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 24px clamp(56px, 9vw, 108px);
}
.hero-title {
  font-family: 'Geo';
  font-weight: 700;
  /* Fluid between the two anchor widths (44px @375 -> 84px @1440) rather
     than a flat vw with a floor. A plain `6.4vw` bottoms out on its minimum
     well before phone widths, which left the hero smaller than the section
     titles below it and inverted the page's type hierarchy.
     --hero-fit-size (script.js) then caps that: this clamp alone was tuned
     to fit line one ("Stop guessing.") but never checked that "Start " plus
     the widest rotating word fits line two without wrapping again. min()
     means the JS value can only pull the size down to make that fit - on
     desktop, where it already fits, the clamp above stays the deciding
     value and nothing changes. */
  font-size: min(clamp(44px, 29.92px + 3.76vw, 84px), var(--hero-fit-size, 999px));
  line-height: 1.03;
  letter-spacing: -0.02em;
  margin: 0 0 22px;
  max-width: 20ch;
}
.hero-title-static { color: inherit; }
.hero-title-swap {
  display: inline-grid;
  justify-items: start;
  vertical-align: baseline;
}
.hero-title-accent {
  grid-area: 1 / 1;
  color: var(--accent);
  white-space: nowrap;
}
.hero-title-accent:not(.is-active) { pointer-events: none; }

/* FoldText, ported from React Bits to plain CSS. The hero accent words
   inherit Geo / size / colour from .hero-title; these rules only build
   the 3D fold panels and the crease shade. */
.fold-text-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;
}
.fold-text-visual { display: inline; }
.fold-text-whitespace { display: inline; }
.fold-text-segment {
  display: inline-block;
  line-height: inherit;
  perspective: var(--fold-perspective, 700px);
  transform-style: preserve-3d;
  vertical-align: baseline;
}
.fold-text-piece {
  position: relative;
  display: inline-block;
  color: inherit;
  line-height: inherit;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  will-change: transform, opacity;
}
@media (prefers-reduced-motion: reduce) {
  .fold-text-piece { transform: none !important; }
}
.hero-sub {
  font-family: 'Text';
  font-weight: 400;
  font-size: clamp(16px, 1.6vw, 19px);
  line-height: 1.55;
  color: #000;
  max-width: 46ch;
  margin: 0 0 34px;
}
.hero-actions {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}
@media (max-width: 768px) {
  /* Desktop keeps the copy clear of the figure by sitting it in the empty
     left third. That trick cannot survive a portrait viewport: the video is
     `object-fit: cover`, so on a tall narrow box it only ever overflows
     horizontally and object-position's Y axis does nothing - the figure is
     pinned across the middle, right where the headline lands.
     So the separation becomes vertical instead: the video takes the top of
     the frame and the copy sits on clean background beneath it. Same
     relationship as desktop (figure and words never overlap), rotated. */
  .hero-media {
    height: 58%;
    bottom: auto;
    object-position: 60% center;
    transform: none;
  }
  .hero-scrim {
    background: linear-gradient(
      180deg,
      rgba(250, 249, 247, 0.12) 0%,
      rgba(250, 249, 247, 0) 30%,
      rgba(250, 249, 247, 0.88) 52%,
      rgba(250, 249, 247, 1) 62%
    );
  }
}

/* ---------- Sections generic ---------- */
section { position: relative; }
.section-title {
  font-family: 'Geo';
  font-weight: 600;
  font-size: clamp(28px, 3.6vw, 44px);
  line-height: 1.12;
  letter-spacing: -0.01em;
  max-width: 18ch;
  margin: 0 0 14px;
}
.section-sub {
  font-family: 'Text';
  font-weight: 400;
  font-size: 17px;
  line-height: 1.6;
  color: var(--text-dim);
  max-width: 56ch;
  margin: 0 0 48px;
}

[data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
[data-reveal].is-visible {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; transition: none; }
}

/* ---------- Method statement ---------- */
.method-intro {
  min-height: 100svh;
}
.method-intro-inner {
  display: grid;
  place-items: center;
  width: 100%;
  min-height: 100svh;
  padding: calc(var(--nav-h) + 36px) 0 52px;
  overflow: hidden;
}
.method-intro-animation {
  width: 92%;
  max-width: 1080px;
  aspect-ratio: 8 / 5;
  overflow: hidden;
}
.method-intro-animation svg {
  display: block;
  width: 100% !important;
  height: 100% !important;
}

@media (max-width: 640px) {
  .method-intro-inner {
    padding: calc(var(--nav-h) + 24px) 0 44px;
    padding-inline: 0;
  }
  .method-intro-title {
    max-width: none;
  }
  .method-intro-animation {
    width: calc(100vw - 8px);
    max-width: none;
  }
}

/* ---------- Circling moves ----------
   Seven tiles orbiting a shared centre, ported from a React/Motion
   "CirclingElements" component to plain CSS since this page has no React or
   build step (same pattern as initOriginButtons). Each item carries its own
   start angle, so one keyframe pair drives every tile's orbit and the ring
   still reads correctly when animation is off. Hovering a tile pauses the
   orbit; the counter-rotation keeps every word upright. These tiles are the
   one deliberate exception to the 2px card/panel radius lock - they read as
   oversized pills, not panels.

   The path is an ellipse, not a circle, so the tiles use the full width of a
   landscape screen instead of a narrow column down the middle: the orbit is
   stretched horizontally by --circling-stretch and each tile cancels that
   stretch with the inverse scaleX. Because the counter-rotation leaves every
   tile axis-aligned, the two scales cancel exactly - the path is elliptical,
   the tiles stay square. Four layers, each owning one transform so they never
   fight: item = orbit, flat = stretch cancel + hover, depth = 3D scale/turn,
   tile = drift. */
.circling {
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: 48px 0;
  overflow: hidden;
  background: var(--bg);
  /* Confines the backdrop blend below to this section. */
  isolation: isolate;

  /* Radius and tile size are load-bearing, not taste: the tiles scale up to
     1.16 at the front of the ring and bob vertically, and at 23vmin they
     collided (measured -17.6px overlap between adjacent tiles). Verify with a
     full-cycle sweep before changing either. */
  --circling-radius: clamp(112px, 32vmin, 310px);
  --circling-tile: clamp(84px, 20vmin, 186px);
  --circling-duration: 9s;
  --circling-stretch: 1.85;
  /* How much the nearest tile swells. Counts against the collision budget,
     so the tight breakpoints dial it back. */
  --circling-pop: 1.16;
}
/* The studio shot sits behind the ring. No blend mode here: the photo is a
   grey studio, darker than the page everywhere (edge luma medians 211 top,
   223 bottom, 225 left against the cream's 249), so a darken blend would be
   a no-op and a lighten would erase it. The join is done purely by fading
   the top and bottom out, which is what stops the photo ending on a line
   against the sections above and below. Left and right need no fade, the
   photo is full-bleed and runs off the viewport. */
.circling::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: url('assets/images/two-chairs.jpg') center / cover no-repeat;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 18%, #000 84%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 18%, #000 84%, transparent 100%);
  pointer-events: none;
}
.circling-stage {
  display: grid;
  place-items: center;
  grid-template-areas: 'circling';
  width: 100%;
  min-height: calc(var(--circling-radius) * 2 + var(--circling-tile));
}
.circling-orbit {
  grid-area: circling;
  display: grid;
  place-items: center;
  grid-template-areas: 'circling';
  list-style: none;
  margin: 0;
  padding: 0;
  transform: scaleX(var(--circling-stretch));
}
/* Sits in the hole at the centre of the ring - a sibling of the orbit, never
   a child, so the orbit's horizontal stretch cannot distort it. */
.circling-center {
  grid-area: circling;
  z-index: 1;
  margin: 0;
  max-width: 12ch;
  font-family: 'Geo';
  /* 700 is the heaviest Geo weight that ships - going higher only triggers
     synthetic bolding, so the weight comes from size and tight tracking. */
  font-weight: 700;
  font-size: clamp(24px, 7.8vmin, 84px);
  line-height: 1;
  letter-spacing: -0.012em;
  color: var(--text);
  text-align: center;
  text-wrap: balance;
  pointer-events: none;
}
/* Enters from deep behind the screen plane and flies forward to rest, on the
   shared [data-reveal] observer - hence the transform overrides rather than a
   keyframe animation. */
.circling-center[data-reveal] {
  opacity: 0;
  transform: perspective(1000px) translateZ(-1500px);
  transition: opacity 0.75s var(--ease), transform 1.15s var(--ease);
}
.circling-center[data-reveal].is-visible {
  opacity: 1;
  transform: perspective(1000px) translateZ(0);
}
.circling-item {
  grid-area: circling;
  --angle: calc(360deg / var(--circling-count) * var(--i));
  transform:
    rotate(var(--angle))
    translateX(var(--circling-radius))
    rotate(calc(var(--angle) * -1));
  animation: circling-orbit var(--circling-duration) linear infinite;
  will-change: transform;
}
@keyframes circling-orbit {
  from {
    transform:
      rotate(var(--angle))
      translateX(var(--circling-radius))
      rotate(calc(var(--angle) * -1));
  }
  to {
    transform:
      rotate(calc(var(--angle) + 360deg))
      translateX(var(--circling-radius))
      rotate(calc(var(--angle) * -1 - 360deg));
  }
}
/* The orbit only stops when a tile itself is hovered, not on any pointer
   entering the section - so passing through the block leaves it turning.
   With JS the stop is eased by ramping playbackRate (initCirclingRamp adds
   .is-ramped and takes over); this rule is the no-JS fallback, which cuts
   straight to a halt rather than gliding to one. */
.circling-orbit:not(.is-ramped):has(.circling-item:hover) .circling-item,
.circling-orbit:not(.is-ramped):has(.circling-item:hover) .circling-depth,
.circling-orbit:not(.is-ramped):has(.circling-item:hover) .circling-tile { animation-play-state: paused; }
.circling-flat {
  display: block;
  transform: scaleX(calc(1 / var(--circling-stretch)));
  transition: transform 0.25s var(--ease);
  will-change: transform;
}
.circling-item:hover .circling-flat {
  transform: scaleX(calc(1 / var(--circling-stretch))) scale(1.14);
}
/* Depth. Phase-locked to the orbit by a negative delay of one seventh of the
   cycle per index, so animation progress tracks the tile's actual angle:
   25% = nearest the viewer, 75% = furthest. Scale, turn and stacking all key
   off that, which is what sells the ring as a solid object in space. */
.circling-depth {
  display: block;
  animation: circling-depth var(--circling-duration) linear infinite;
  animation-delay: calc(var(--circling-duration) / -7 * var(--i));
  will-change: transform;
}
@keyframes circling-depth {
  0%   { transform: perspective(1100px) rotateY(-15deg) scale(1);    opacity: 0.94; z-index: 2; }
  25%  { transform: perspective(1100px) rotateY(0deg)   scale(var(--circling-pop)); opacity: 1; z-index: 3; }
  50%  { transform: perspective(1100px) rotateY(15deg)  scale(1);    opacity: 0.94; z-index: 2; }
  75%  { transform: perspective(1100px) rotateY(0deg)   scale(0.84); opacity: 0.86; z-index: 1; }
  100% { transform: perspective(1100px) rotateY(-15deg) scale(1);    opacity: 0.94; z-index: 2; }
}
.circling-tile {
  display: grid;
  place-items: center;
  width: var(--circling-tile);
  height: var(--circling-tile);
  /* Proportional, not fixed: a flat 8px is a fifth of the tile at the small
     breakpoints and squeezes the longest word out of it. */
  padding: calc(var(--circling-tile) * 0.07);
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: clamp(20px, 4.6vmin, 46px);
  font-family: 'Geo';
  font-weight: 700;
  /* Sized off the tile, not the viewport: the tile shrinks at some
     breakpoints to keep the cards from colliding, and a viewport-based size
     would then overflow it. 0.155 keeps the longest word (TRANSFER) inside
     the padding at every size. */
  font-size: max(12px, calc(var(--circling-tile) * 0.155));
  line-height: 1.05;
  letter-spacing: -0.005em;
  text-transform: uppercase;
  text-align: center;
  will-change: transform;
  /* A bob on its own shorter period, so the seven read as seven moving pieces
     rather than one rigid wheel. Depth is carried entirely by the layer above
     - scale, turn, opacity and stacking - with no cast shadow. */
  animation: circling-drift calc(var(--circling-duration) / 3.5) ease-in-out infinite alternate;
  animation-delay: calc(var(--circling-duration) / -7 * var(--i));
}
@keyframes circling-drift {
  from { transform: translateY(calc(var(--circling-tile) * -0.07)); }
  to { transform: translateY(calc(var(--circling-tile) * 0.07)); }
}

/* The ellipse is much rounder here, so the centre hole is narrower than vmin
   alone implies and the label has to come down with it. */
@media (max-width: 1024px) {
  .circling {
    --circling-stretch: 1.15;
    /* The rounder ellipse packs the tiles closer together than the wide one
       does, so the tile has to come down with it or adjacent cards collide. */
    --circling-tile: clamp(84px, 17vmin, 186px);
  }
  .circling-center { font-size: clamp(24px, 5.6vmin, 52px); }
}
/* Portrait: the ellipse collapses to a circle, and the ring opens up (wider
   radius, smaller tiles) so the centre label still has a hole to sit in. */
@media (max-width: 640px) {
  .circling {
    --circling-stretch: 1;
    /* Ring opened up and tiles pulled in, which is what buys the centre
       label enough of a hole to sit at its proper scale below. */
    --circling-radius: clamp(140px, 42vmin, 300px);
    --circling-tile: clamp(64px, 18.5vmin, 186px);
    /* Tightest breakpoint: a full circle leaves the least room between
       neighbours, so the swell is dialled right back to keep them apart. */
    --circling-pop: 1.06;
  }
  /* Still capped below the shared scale so it keeps clear of the tiles, but
     the wider ring above means the cap can now sit near the desktop
     hero:label ratio (~37px @375) instead of the old 24px. */
  .circling-center {
    max-width: 7ch;
    font-size: clamp(30px, 9.9vmin, 44px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .circling-item,
  .circling-depth,
  .circling-tile { animation: none; }
  .circling-flat { transition: none; }
  /* Outranks the generic [data-reveal] reduced-motion reset, so it has to
     restore the resting state itself or the label never appears. */
  .circling-center[data-reveal],
  .circling-center[data-reveal].is-visible {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .circling-item:hover .circling-flat {
    transform: scaleX(calc(1 / var(--circling-stretch)));
  }
}

/* ---------- Negotiation word flight ----------
   An autonomous full-viewport loop. Each pair crosses the frame from opposite
   sides and settles into two closely stacked, opposing alignments. */
.word-flight {
  min-height: 100svh;
  overflow: hidden;
  background: var(--bg);
  isolation: isolate;
}
.word-flight-stage {
  position: relative;
  width: 100%;
  height: 100svh;
  min-height: 620px;
  overflow: hidden;
}
.word-flight-pair {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-rows: auto auto;
  align-content: center;
  gap: clamp(10px, 1.4vw, 24px);
  padding: var(--nav-h) clamp(24px, 5vw, 96px) 0;
  pointer-events: none;
}
.word-flight-word {
  color: #000;
  font-family: 'Geo', ui-sans-serif, sans-serif;
  font-weight: 700;
  /* --word-flight-size is measured in script.js as the size at which the
     longest word exactly fills the stage. min() means it can only pull the
     type down to fit a narrow screen - on desktop the designed clamp is
     already the smaller of the two, so nothing there changes. The 100vw
     fallback keeps the clamp in charge if the script never runs. */
  font-size: min(clamp(64px, 10.6vw, 190px), var(--word-flight-size, 100vw));
  line-height: 0.86;
  letter-spacing: -0.055em;
  white-space: nowrap;
  text-transform: uppercase;
  transform: translate3d(-115vw, 0, 0);
  will-change: transform;
}
.word-flight-word--from-left {
  justify-self: end;
  text-align: right;
}
.word-flight-word--from-right {
  justify-self: start;
  text-align: left;
  transform: translate3d(115vw, 0, 0);
}
.word-flight-word span { color: var(--accent); }

@media (max-width: 640px) {
  .word-flight-stage { min-height: 520px; }
  .word-flight-pair {
    gap: 10px;
    padding-inline: 16px;
  }
  .word-flight-word {
    /* No font-size here any more: the measured --word-flight-size above
       fills the width exactly, which a fixed clamp could only approximate. */
    letter-spacing: -0.06em;
  }
}

@media (prefers-reduced-motion: reduce) {
  .word-flight {
    display: grid;
    place-items: center;
    min-height: 100svh;
    padding: calc(var(--nav-h) + 48px) 18px 64px;
  }
  .word-flight-stage {
    display: grid;
    grid-template-columns: 1fr;
    align-content: center;
    gap: 18px;
    width: min(1200px, 100%);
    height: auto;
    min-height: 0;
    overflow: visible;
  }
  .word-flight-pair {
    position: static;
    display: flex;
    justify-content: center;
    gap: 0.26em;
    padding: 0;
  }
  .word-flight-word {
    font-size: clamp(42px, 8.5vw, 108px);
    line-height: 0.96;
    text-align: center;
    transform: none !important;
  }
}

/* ---------- Negotiation outcomes (proof strip) ----------
   One big real-outcome message at a time, filling the full width of the
   gap; the three clips take turns, crossfading as each finishes. */
.outcomes {
  position: relative;
  background: var(--bg);
  padding: clamp(12px, 2.4vw, 32px) 0 clamp(64px, 7vw, 108px);
}
.outcomes-row {
  position: relative;
  z-index: 1;
  width: min(880px, 84%);
  aspect-ratio: 1080 / 312;
  margin: 0 auto;
}

/* Reaction bubbles (Like.json) scattered around the message and left to
   drift + pop in a loop. Fixed perimeter slots keep them clear of the
   message bubble itself; each slot gets randomized motion in script.js. */
.reaction-field {
  position: absolute;
  inset: -34% 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.reaction-bubble {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--rs, 44px);
  height: var(--rs, 44px);
  /* Centers the box on its simulated point, so script.js can write a plain
     translate of that point without compounding a percentage offset. */
  margin: calc(var(--rs, 44px) / -2) 0 0 calc(var(--rs, 44px) / -2);
  will-change: transform, opacity;
  /* Each one simply fades up where it already is - no travel, no burst.
     script.js staggers --rdelay so they arrive one at a time. */
  opacity: 0;
  animation: reaction-appear 900ms ease-out var(--rdelay, 0ms) forwards;
}
@keyframes reaction-appear {
  from { opacity: 0; }
  to { opacity: 1; }
}
.reaction-bubble svg {
  display: block;
  width: 100% !important;
  height: 100% !important;
}

@media (prefers-reduced-motion: reduce) {
  .reaction-field { display: none; }
}
/* Deliberately still shown on phones: the bubble count is derived from the
   free area beside the message at run time, so a narrow field simply spawns
   fewer of them rather than jamming. Hiding it here would drop a whole piece
   of the desktop composition on mobile. */

.outcomes-item {
  position: absolute;
  inset: 0;
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
.outcomes-item.is-active {
  opacity: 1;
  transform: none;
}
.outcomes-item svg {
  display: block;
  width: 100% !important;
  height: 100% !important;
}

@media (prefers-reduced-motion: reduce) {
  .outcomes-item {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ---------- Curriculum (gooey folder tabs) ---------- */
.curriculum {
  padding: clamp(96px, 10vw, 148px) 0 clamp(108px, 11vw, 164px);
  overflow: hidden;
}
/* Same visual language as [data-reveal] (opacity + a small rise), but its own
   attribute and its own trigger: this only plays once the heading and
   standfirst above it have finished their vertical-cut reveal, driven
   explicitly from that timeline's onComplete rather than the generic
   scroll-observer every [data-reveal] element shares. */
[data-entrance] {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
[data-entrance].is-visible {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  [data-entrance] { opacity: 1; transform: none; transition: none; }
}
.curriculum .section-title {
  max-width: 24ch;
  font-size: clamp(38px, 5.2vw, 72px);
  line-height: 0.98;
  letter-spacing: -0.035em;
}
.curriculum .section-sub {
  max-width: 62ch;
  margin-bottom: clamp(46px, 5vw, 72px);
  color: #000;
}
.module-folder {
  --module-count: 6;
  --module-active: 0;
  position: relative;
  min-height: 590px;
  isolation: isolate;
}
.module-folder-filter {
  position: absolute;
  pointer-events: none;
}
.module-folder-shape {
  position: absolute;
  inset: 0;
  z-index: -1;
  filter: url('#module-gooey-filter');
  pointer-events: none;
}
.module-active-shape {
  position: absolute;
  top: 0;
  left: 0;
  width: calc(100% / var(--module-count));
  height: 88px;
  border-radius: 22px 22px 0 0;
  background: var(--accent);
  transform: translate3d(calc(var(--module-active) * 100%), 0, 0);
  transition: transform 0.52s var(--ease);
}
.module-panel-shape {
  position: absolute;
  top: 76px;
  right: 0;
  bottom: 0;
  left: 0;
  border-radius: 30px;
  background: var(--accent);
}
.module-tabs {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  align-items: end;
  height: 88px;
}
.module-tab {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-width: 0;
  height: 76px;
  padding: 0 12px;
  border: 0;
  border-radius: 18px 18px 0 0;
  background: var(--accent);
  color: #fff;
  font: 600 clamp(13px, 1.2vw, 17px)/1.15 'UI', ui-sans-serif, sans-serif;
  cursor: pointer;
  opacity: 0.62;
  transform: translateY(12px);
  transition: opacity 0.32s var(--ease), transform 0.42s var(--ease), background-color 0.32s var(--ease);
}
.module-tab::after {
  content: '';
  position: absolute;
  right: 12%;
  bottom: 0;
  left: 12%;
  height: 1px;
  background: rgba(255, 255, 255, 0.26);
  opacity: 1;
  transition: opacity 0.25s var(--ease);
}
.module-tab:hover { opacity: 0.82; }
.module-tab.is-active {
  opacity: 1;
  transform: translateY(0);
}
.module-tab.is-active::after { opacity: 0; }
.module-tab:focus-visible {
  outline: 3px solid #fff;
  outline-offset: -6px;
}
.module-tab-number {
  font-weight: 400;
  opacity: 0.65;
}
.module-panels {
  position: absolute;
  top: 76px;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1;
  overflow: hidden;
  border-radius: 30px;
}
.module-panel {
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(320px, 0.88fr);
  gap: clamp(48px, 8vw, 120px);
  align-items: end;
  min-height: 514px;
  padding: clamp(54px, 6.5vw, 92px);
  color: #fff;
}
.module-panel[hidden] { display: none; }
.module-days,
.module-learning-label {
  display: block;
  color: rgba(255, 255, 255, 0.64);
  font: 600 12px/1.2 'UI', ui-sans-serif, sans-serif;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.module-panel h3 {
  max-width: 13ch;
  margin: 16px 0 20px;
  color: #fff;
  font-family: 'Geo', ui-sans-serif, sans-serif;
  font-size: clamp(40px, 5vw, 72px);
  font-weight: 600;
  line-height: 0.98;
  letter-spacing: -0.035em;
}
.module-panel-copy p {
  max-width: 54ch;
  margin: 0;
  color: rgba(255, 255, 255, 0.8);
  font-size: clamp(16px, 1.45vw, 20px);
  line-height: 1.55;
}
.module-learning {
  padding-left: clamp(24px, 3vw, 48px);
  border-left: 1px solid rgba(255, 255, 255, 0.3);
}
.module-learning ul {
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
}
.module-learning li {
  position: relative;
  padding: 14px 0 14px 24px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.2);
  color: #fff;
  font-size: clamp(15px, 1.25vw, 18px);
  line-height: 1.4;
}
.module-learning li:first-child { border-top: 1px solid rgba(255, 255, 255, 0.2); }
.module-learning li::before {
  content: '';
  position: absolute;
  top: 1.35em;
  left: 2px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff;
}

/* React Bits FoldText, adapted to the landing page's existing GSAP runtime.
   The original semantic element stays in place while its visible copy is
   segmented into hinged 3D panels. */
.fold-text-target {
  white-space: pre-wrap;
  user-select: text;
}
.fold-text-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;
}
.fold-text-visual { display: inline; }
.fold-text-whitespace { display: inline; }
.fold-text-segment {
  display: inline-block;
  line-height: inherit;
  perspective: var(--fold-perspective, 700px);
  transform-style: preserve-3d;
  vertical-align: baseline;
}
.fold-text-piece {
  position: relative;
  display: inline-block;
  color: inherit;
  line-height: inherit;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  filter: none;
  text-shadow: none;
  will-change: transform, opacity;
}
.fold-text-piece::after {
  content: none;
}
@media (max-width: 900px) {
  .module-folder { min-height: 590px; }
  .module-tabs {
    grid-template-columns: repeat(6, minmax(132px, 1fr));
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
  }
  .module-tabs::-webkit-scrollbar { display: none; }
  .module-folder-shape { filter: none; }
  .module-active-shape { display: none; }
  .module-panel-shape { top: 76px; }
  .module-tab { opacity: 0.72; }
  .module-tab.is-active { opacity: 1; background: var(--accent-strong); }
  .module-panel {
    grid-template-columns: 1fr;
    gap: 42px;
    align-content: center;
    min-height: 514px;
    padding: 52px 40px;
  }
  .module-panel h3 { max-width: 16ch; }
  .module-learning { padding-left: 0; border-left: 0; }
}
@media (max-width: 640px) {
  /* The old `12vw` boost here rendered the curriculum heading at 45px on a
     375px screen - larger than the hero itself. The base rule already floors
     at 38px, which holds the desktop hero:heading ratio, so this override is
     deliberately gone rather than retuned. */
  .module-folder { min-height: 820px; }
  .module-tabs { grid-template-columns: repeat(6, minmax(118px, 1fr)); }
  .module-tab { height: 70px; padding-inline: 10px; }
  .module-panel-shape,
  .module-panels { top: 70px; border-radius: 22px; }
  .module-panel {
    min-height: 750px;
    padding: 48px 24px;
  }
  .module-panel h3 { font-size: clamp(38px, 12vw, 52px); }
  .module-panel-copy p { font-size: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  .module-active-shape,
  .module-tab { transition: none; }
  .fold-text-piece {
    opacity: 1 !important;
    transform: none !important;
  }
  .fold-text-piece::after { opacity: 0 !important; }
}

/* ---------- Practice (marquee) ---------- */
/* No top padding: the animation carries its own "Ten rooms" title, so it runs
   straight up into the space the heading and standfirst used to occupy. */
.practice {
  padding: 0;
  /* Explicit rather than inherited from the canvas: the animation below blends
     with this, and a propagated body background is not a reliable backdrop. */
  background: var(--bg);
}
/* Full-bleed 16:9 band matching the Lottie's 640x360 canvas, so the artwork
   fills the section edge to edge instead of sitting in the 1280px column.
   The artwork is shot on near-white (250,250,250), a shade off the page's
   cream - darken clamps anything lighter than the background to exactly the
   background while leaving the doors and type untouched, so the band has no
   visible edge. */
.practice-animation {
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 100svh;
  overflow: hidden;
  mix-blend-mode: darken;
}
.practice-animation svg {
  display: block;
  width: 100% !important;
  height: 100% !important;
}
.marquee {
  overflow: hidden;
  padding: 30px 0;
}
.marquee-track {
  display: flex;
  gap: 56px;
  width: max-content;
  animation: marquee 32s linear infinite;
}
.marquee-track span {
  font-family: 'Geo';
  font-weight: 500;
  /* Geo ships upright faces only, so this is a synthetic slant rather than a
     drawn italic. Fine at this display size; a real italic would need an
     italic woff2 added to assets/fonts. */
  font-style: italic;
  font-size: clamp(22px, 3vw, 34px);
  color: #000;
  white-space: nowrap;
}
/* The row above the doors. Offset half a cycle so the two rows do not line up
   word for word, which would read as a duplicate rather than a pair. */
.marquee-lead .marquee-track { animation-delay: -16s; }
@keyframes marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
.marquee:hover .marquee-track { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .marquee-track { animation: none; overflow-x: auto; }
}

/* ---------- Typed line ----------
   Typewriter effect ported from the React Bits "TextType" component to plain
   JS, since this page has no React or build step (same route as the origin
   button and the moves ring). GSAP drives the cursor blink exactly as the
   original does, and it is already on the page.

   The line is a one-cell grid holding a hidden copy of the full phrase plus
   the live one, stacked. The hidden copy reserves the final height, so the
   section cannot grow and shrink a line at a time as characters are added
   and deleted, which would shunt the rest of the page up and down. */
.typed {
  padding: clamp(90px, 14vh, 160px) 0;
  background: var(--bg);
}
.typed-line {
  display: grid;
  justify-items: center;
  text-align: center;
  margin: 0;
  font-family: 'Geo';
  font-weight: 700;
  font-size: clamp(34px, 7vw, 104px);
  line-height: 1.04;
  letter-spacing: -0.02em;
  color: var(--accent);
}
.typed-line > span { grid-area: 1 / 1; }
.typed-ghost { visibility: hidden; pointer-events: none; }
.typed-cursor {
  margin-left: 0.08em;
  display: inline-block;
}
.typed-accent { color: var(--accent); }

/* ---------- Blur text ----------
   Ported from the React Bits "BlurText" component to plain JS/GSAP, since
   this page has no React and does not carry the Motion library the source
   uses - GSAP (already loaded for every other ported component here) drives
   the same three-keyframe blur/opacity/y settle instead. Words are wrapped
   in spans by JS; this is only the static/centering half - the per-word
   animation is inline styles GSAP writes directly, not CSS. */
.blur-text {
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  max-width: none;
  margin: 0 0 40px;
  /* Large enough to visibly bridge the gap between the typed line above and
     the cards below on its own. This is a starting point, not a guarantee -
     "Every room scores the same four skills." is 40 characters, too long for
     any single vw coefficient to promise a one-line fit at every viewport
     width without either overflowing on some or being timid on others. JS
     measures the actual rendered width against the container after layout
     and scales this down only if it doesn't fit, so it renders as large as
     it can while staying on one line. */
  font-size: clamp(56px, 10vw, 168px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
  white-space: nowrap;
  text-align: center;
  text-transform: uppercase;
  color: #000;
}
.blur-text-word {
  display: inline-block;
}

/* ---------- Skills (hover-reveal cards) ----------
   Four image cards in a row; hovering or focusing one brings it forward
   while the rest settle back, dim, and soften - a group-hover effect done
   in plain CSS (the hovered card's own :hover styles win via specificity,
   no JS needed). */
.skills {
  padding: 108px 0;
  /* The cards carry a small drifting translateX. Nothing clips it, so at
     narrow widths that drift pushed the rightmost card past the viewport and
     gave the whole page a horizontal scrollbar. `clip` contains it without
     creating a scroll container the way `hidden` would. */
  overflow-x: clip;
}
.skills-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
.skill-card {
  position: relative;
  aspect-ratio: 9 / 16;
  display: flex;
  flex-direction: column;
  border-radius: 22px;
  overflow: hidden;
  cursor: pointer;
  background: #fff;
  box-shadow: 0 0 0 1px var(--line);
  transition: transform 0.5s var(--ease), opacity 0.5s var(--ease), filter 0.5s var(--ease);
}
.skills-grid:hover .skill-card {
  transform: scale(0.97);
  opacity: 0.6;
  filter: blur(2px);
}
.skill-card:hover,
.skill-card:focus-visible {
  transform: scale(1.03) !important;
  opacity: 1 !important;
  filter: none !important;
}
.skill-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.skill-card-label {
  flex: none;
  padding: 22px 20px 18px;
  background: #fff;
}
.skill-card-image {
  flex: 1;
  min-height: 0;
  background-size: cover;
  background-position: center;
}
.skill-card-label h3 {
  font-family: 'Geo';
  font-weight: 700;
  font-size: 22px;
  color: var(--accent);
  margin: 0 0 8px;
}
.skill-card-desc {
  font-family: 'Text';
  font-size: 14px;
  line-height: 1.5;
  color: #000;
  margin: 0;
}
@media (max-width: 900px) {
  .skills-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 560px) {
  .skills-grid { grid-template-columns: 1fr; }
  .skill-card { aspect-ratio: 3 / 4; }
}
@media (prefers-reduced-motion: reduce) {
  .skill-card,
  .skills-grid:hover .skill-card,
  .skill-card:hover,
  .skill-card:focus-visible {
    transition: none;
    transform: none !important;
    filter: none !important;
  }
}
/* JS positions and animates these off-screen before flying them in (see
   initSkillCardsEntrance); starting hidden here avoids a flash at rest
   before that runs. No-JS and reduced-motion visitors get them plain. */
[data-fly-card] { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  [data-fly-card] { opacity: 1; }
}

/* ---------- Manifesto ---------- */
.manifesto {
  display: grid;
  align-items: center;
  min-height: calc(100svh - var(--nav-h));
  padding: 112px 0;
}
.manifesto .wrap-narrow {
  width: 100%;
  max-width: 1040px;
}
.manifesto-text {
  font-family: 'Geo';
  font-weight: 300;
  font-size: clamp(32px, 4.15vw, 56px);
  line-height: 1.2;
  letter-spacing: -0.012em;
  color: var(--text-dim);
  margin: 0 0 42px;
}
.manifesto-accent {
  color: #000;
  font-weight: 600;
}
.vertical-cut-word--accent {
  color: var(--accent);
}
.vertical-cut-word {
  display: inline-block;
  white-space: nowrap;
}
.vertical-cut-char {
  position: relative;
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
}
.vertical-cut-measure {
  visibility: hidden;
}
.vertical-cut-slice {
  position: absolute;
  inset: 0;
  opacity: 0;
  will-change: transform, opacity;
}
.vertical-cut-slice-top { clip-path: inset(0 0 50% 0); }
.vertical-cut-slice-bottom { clip-path: inset(50% 0 0 0); }

@media (prefers-reduced-motion: reduce) {
  .vertical-cut-slice { transform: none !important; opacity: 1 !important; }
}

@media (max-width: 640px) {
  .manifesto {
    min-height: auto;
    padding: 88px 0;
  }
  .manifesto-text {
    font-size: clamp(28px, 8vw, 36px);
    line-height: 1.22;
    margin-bottom: 32px;
  }
}

/* ---------- Testimonials ----------
   The reference keeps its headline fixed while rounded masonry columns rise
   behind a soft fade. This version preserves that composition in the brand's
   white and cobalt rather than importing the reference's black palette. */
.testimonials {
  position: relative;
  min-height: 100svh;
  overflow: hidden;
  padding: 0;
  background: var(--bg);
}
.testimonials-heading {
  position: absolute;
  /* Sits ~32px higher than the space above it alone would suggest. The
     cards below are anchored independently (.testimonials-window keeps its
     own top), so lifting the heading does not drag them up with it - it
     converts that distance into clearance between the standfirst and the
     first row of cards, which were reading as crowded against it. */
  top: clamp(64px, 7svh, 84px);
  left: 50%;
  z-index: 2;
  width: calc(100% - 24px);
  transform: translateX(-50%);
  text-align: center;
}
.testimonials-heading h2 {
  margin: 0;
  color: var(--accent);
  font-family: 'Geo', ui-sans-serif, sans-serif;
  font-weight: 700;
  font-size: min(3.8vw, 64px);
  line-height: 0.98;
  letter-spacing: -0.035em;
  white-space: nowrap;
  text-transform: uppercase;
}
.testimonials-heading p {
  max-width: 58ch;
  margin: 24px auto 0;
  color: #000;
  font-family: 'Text', ui-sans-serif, sans-serif;
  font-size: clamp(16px, 1.5vw, 20px);
  line-height: 1.55;
  text-wrap: balance;
}
.testimonial-blur {
  display: flex;
  justify-content: center;
}
.testimonial-blur--headline { flex-wrap: nowrap; }
.testimonial-blur--supporting { flex-wrap: wrap; }
.testimonial-blur .blur-text-word {
  will-change: transform, filter, opacity;
}
.testimonials-window {
  position: absolute;
  top: clamp(270px, 32svh, 340px);
  right: 0;
  bottom: 0;
  left: 0;
  overflow: hidden;
  background: var(--bg);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 16%, #000 84%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 16%, #000 84%, transparent 100%);
}
.testimonials-source {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
  max-width: 1280px;
  margin: 0 auto;
  padding: 72px 24px;
  list-style: none;
}
.testimonials-stream {
  display: none;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(14px, 1.35vw, 24px);
  width: calc(100% + 72px);
  height: 100%;
  margin-left: -36px;
  padding: 0;
  background: transparent;
}
.testimonials.is-flowing .testimonials-source {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.testimonials.is-flowing .testimonials-stream { display: grid; }
.testimonial-column {
  min-width: 0;
  background: transparent;
  transform: translateZ(0);
}
.testimonial-track {
  display: flex;
  flex-direction: column;
  animation: testimonial-rise var(--testimonial-duration, 18s) linear infinite;
  animation-delay: var(--testimonial-delay, 0s);
  will-change: transform;
}
.testimonial-set {
  display: flex;
  flex-direction: column;
  gap: clamp(14px, 1.35vw, 24px);
  padding-bottom: clamp(14px, 1.35vw, 24px);
}
@keyframes testimonial-rise {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(0, -50%, 0); }
}
.testimonial-card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: clamp(28px, 2.4vw, 50px);
  padding: clamp(32px, 3.88vw, 80px);
  overflow: hidden;
  border: 4px solid rgba(51, 88, 184, 0.24);
  border-radius: clamp(26px, 2.1vw, 44px);
  background: #fff;
  color: var(--accent);
  box-shadow: none;
}
.testimonial-set .testimonial-card:nth-child(6n + 1) { width: 94%; align-self: flex-end; }
.testimonial-set .testimonial-card:nth-child(6n + 2) { width: 88%; align-self: flex-start; }
.testimonial-set .testimonial-card:nth-child(6n + 3) { width: 100%; align-self: center; }
.testimonial-set .testimonial-card:nth-child(6n + 4) { width: 91%; align-self: flex-end; }
.testimonial-set .testimonial-card:nth-child(6n + 5) { width: 96%; align-self: flex-start; }
.testimonial-set .testimonial-card:nth-child(6n + 6) { width: 90%; align-self: center; }
.testimonial-card--tall {
  min-height: clamp(310px, 22vw, 455px);
  padding-block: clamp(32px, 3.2vw, 66px);
}
.testimonial-card--medium {
  min-height: clamp(270px, 19.1vw, 394px);
  padding-block: clamp(30px, 2.7vw, 56px);
}
.testimonial-card--compact {
  min-height: clamp(220px, 13.85vw, 286px);
  gap: clamp(18px, 1.6vw, 32px);
  padding-block: clamp(26px, 2.1vw, 44px);
}
/* The quote is black; the attribution stays blue. The card sets
   color: var(--accent) for the whole subtree, so the quote has to opt out
   explicitly - the author block below simply inherits the blue. */
.testimonial-card blockquote {
  margin: 0;
  color: #000;
  font-family: 'Text', ui-sans-serif, sans-serif;
  font-size: clamp(18px, 1.55vw, 32px);
  font-weight: 500;
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.testimonial-author {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.testimonial-author strong {
  color: var(--accent);
  font-family: 'UI', ui-sans-serif, sans-serif;
  font-size: clamp(14px, 1.3vw, 27px);
  font-weight: 700;
  line-height: 1.2;
}
/* Full-strength blue rather than the translucent tint it used to carry: the
   quote above is now black, so the role no longer needs to be faded to sit
   below the name - size and weight already separate them. */
.testimonial-author span {
  color: var(--accent);
  font-family: 'UI', ui-sans-serif, sans-serif;
  font-size: clamp(12px, 1.15vw, 24px);
  line-height: 1.25;
}
@media (max-width: 900px) {
  .testimonials-stream {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 16px;
    width: calc(100% + 28px);
    margin-left: -14px;
    padding-inline: 0;
  }
  .testimonial-column:nth-child(4) { display: none; }
  .testimonials-source { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
  .testimonials { min-height: 100svh; }
  .testimonials-heading { width: min(100% - 32px, 540px); }
  .testimonials-heading p { font-size: 16px; }
  .testimonials-window {
    top: clamp(255px, 31svh, 300px);
    bottom: 0;
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 85%, transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 85%, transparent 100%);
  }
  .testimonials-stream {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    width: calc(100% + 28px);
    margin-left: -14px;
  }
  .testimonial-column:nth-child(3) { display: none; }
  .testimonial-set { gap: 16px; padding-bottom: 16px; }
  .testimonial-card {
    gap: 24px;
    padding: 24px 18px;
    border-radius: 24px;
  }
  .testimonial-card blockquote { font-size: 15px; line-height: 1.45; }
  .testimonial-author strong { font-size: 13px; }
  .testimonial-author span { font-size: 11.5px; }
  .testimonials-source { grid-template-columns: 1fr; padding-inline: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .testimonials {
    min-height: 0;
    padding: 88px 0 72px;
  }
  .testimonials-heading {
    position: static;
    transform: none;
    margin: 0 auto;
  }
  .testimonials-window {
    position: static;
    height: auto;
    margin-top: 36px;
    overflow: visible;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .testimonials-source { padding-block: 0; }
  .testimonial-track { animation: none; }
}

/* ---------- CTA ---------- */
.cta {
  padding: 120px 0 130px;
  text-align: center;
}
.cta-inner { display: flex; flex-direction: column; align-items: center; }
/* Grid-stacks the typed line's hidden full-length ghost under the live text
   (see "Typed line" above) without borrowing that component's own identity -
   this heading keeps its own size/color, only the structural mechanics are
   shared. */
.cta h2 {
  display: grid;
  justify-items: center;
  font-family: 'Geo';
  font-weight: 700;
  font-size: clamp(30px, 4.2vw, 50px);
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: #000;
  margin: 0 0 18px;
}
.cta h2 > span { grid-area: 1 / 1; }
.cta p {
  font-family: 'Text';
  font-size: 17px;
  color: #000;
  margin: 0 0 36px;
}
/* Launch pricing: the original price struck through, the discounted price
   in the site's headline face, and a pill badge stating the discount -
   deliberately not folded into the plain-text line below it, since a price
   drop is the thing this section most needs to read at a glance. */
.cta-price {
  display: flex;
  align-items: baseline;
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px;
  margin: 0 0 14px;
}
.cta-price-was {
  font-family: 'Geo';
  font-weight: 600;
  font-size: 20px;
  color: var(--text-faint);
  text-decoration: line-through;
  text-decoration-color: var(--text-faint);
}
.cta-price-now {
  font-family: 'Geo';
  font-weight: 700;
  font-size: 32px;
  color: var(--text);
  letter-spacing: -0.01em;
}
.cta-price-badge {
  font-family: 'UI';
  font-weight: 700;
  font-size: 12.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent-ink);
  background: var(--accent);
  padding: 5px 12px;
  border-radius: var(--radius-pill);
}
/* ---------- Footer ---------- */
.footer {
  padding: 48px 0 40px;
}
.footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 24px;
  justify-content: space-between;
}
.footer-brand {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.footer-mark {
  font-family: 'Geo';
  font-weight: 700;
  font-size: 24px;
  color: var(--text);
  text-decoration: none;
  letter-spacing: -0.02em;
}
.footer-links {
  display: flex;
  gap: 24px;
}
.footer-links a {
  font-family: 'UI';
  font-size: 13.5px;
  color: var(--text-dim);
  text-decoration: none;
}
.footer-links a:hover { color: var(--text); }
.footer-copy {
  font-family: 'UI';
  font-size: 12.5px;
  color: var(--text-faint);
  margin: 0;
}
