/* ============================================
   MICRODOSE STUDIO - HOMEPAGE STYLES
   Figma-accurate design tokens & layout
   ============================================ */

/* ---------- CSS CUSTOM PROPERTIES ---------- */
:root {
  /* Colors */
  --color-black: #19171b;
  --color-dark: #0c152a;
  --color-muted: #6b6c71;
  --color-white: #ffffff;
  --color-bg-dark: #121113;
  --color-card-bg: #e8e8ec;
  --color-blue-start: #1935d9;
  --color-blue-end: #2f2be2;
  --color-blue-border: #1f34c9;
  --color-footer-border: rgba(116, 84, 84, 0.15);
  --color-white-50: rgba(255, 255, 255, 0.5);

  /* Typography */
  --font-primary: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-accent: 'Metal', serif;

  /* Border Radius */
  --radius-lg: 16px;
  --radius-xl: 24px;
  --radius-pill: 100px;

  /* Shadows */
  --shadow-color-near: rgba(14, 15, 43, 0.08);
  --shadow-color-far: rgba(66, 39, 92, 0.02);
  --shadow-card: 0px 1px 2px var(--shadow-color-near), 0px 43px 80px var(--shadow-color-far);
  --shadow-blue-inset: inset 0px 1px 4px #2a3edc, inset 0px 4px 16px rgba(255, 255, 255, 0.2);
  --shadow-dark-pill-inset: inset 0px 1px 0px 1px #2d2a2d;

  /* Backgrounds & Borders */
  --color-pill-bg: rgba(62, 44, 62, 0.10);
  --color-dark-border: rgba(255, 255, 255, 0.06);

  /* Content widths */
  --content-max-width: 1530px;
  --content-padding: 195px;
}

/* ---------- RESET & BASE ---------- */
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  scroll-behavior: auto; /* lazy scroll is handled by JS for subtle ease */
  scrollbar-width: none; /* Firefox */
}

html::-webkit-scrollbar {
  display: none; /* Chrome, Safari, Edge */
}

/* Screen reader only — visually hidden but accessible */
.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;
}

/* Blur fade-in: initial state before GSAP runs (avoids flash).
   No filter:blur here — blur is only added by GSAP on desktop.
   Keeping this CSS-only ensures touch devices never get blur. */
.blur-fade-in {
  opacity: 0;
}

/* ---------- PRELOADER ---------- */
body.is-loading {
  overflow: hidden; /* prevent scroll while preloader is visible */
}

.preloader {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: transparent;
  will-change: transform;
}

.preloader-white {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: var(--color-white);
  pointer-events: none;
}

.preloader-bg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: hidden;
}

/* Reuse the same gradient + pattern positioning as .page-bg */
.preloader-bg .page-bg-gradient-wrap {
  position: absolute;
  top: -100px;
  left: 50%;
  transform: translateX(-50%) rotate(180deg) translateZ(0);
  width: 1920px;
  height: 643px;
  opacity: 0.3;
  overflow: hidden;
  filter: blur(60px);
}

.preloader-bg .page-bg-gradient {
  position: absolute;
  top: -46%;
  left: -49%;
  width: 207%;
  height: 192%;
  max-width: none;
}

/* The pattern is not duplicated here: `.page-bg-pattern` is unscoped on
   purpose (see FIXED PAGE BACKGROUND), and .preloader-bg is the same box as
   .page-bg, so one rule positions both copies identically at the hand-off. */

.preloader-content {
  position: relative;
  z-index: 1;
}

.preloader-logo {
  font-family: var(--font-primary);
  font-size: 20px;
  letter-spacing: -0.4px;
  opacity: 0;
  display: inline-block;
}

.pl-char {
  display: inline-block;
  opacity: 0;
  transform: translateY(8px);
}

.preloader-logo-bold {
  font-weight: 600;
  color: var(--color-black);
}

.preloader-logo-light {
  font-weight: 300;
  color: var(--color-black);
}

body {
  font-family: var(--font-primary);
  font-weight: 400;
  color: var(--color-black);
  background-color: transparent;
  overflow-x: hidden;
}

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background: var(--color-white);
}

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

a {
  text-decoration: none;
  color: inherit;
}

button {
  border: none;
  background: none;
  cursor: pointer;
  font-family: inherit;
}

/* ---------- UTILITY CLASSES ---------- */
.text-muted { color: var(--color-muted); }
.text-dark { color: var(--color-black); }

/* ---------- FIXED PAGE BACKGROUND ---------- */
.page-bg {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  will-change: transform;
}

/* Case-study background fade (shared.js, pages with <main data-bg-fade>):
 * the rainbow yields to the imagery while reading and returns at the footer.
 * body::before paints solid white at z-index -2, so opacity 0 here is a true
 * full-white page. The transition class is applied one frame after the
 * initial state so a mid-page refresh starts white without animating. */
.page-bg--fade {
  transition: opacity 1.2s ease;
}

.page-bg--hidden {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .page-bg--fade {
    transition: none;
  }
}

.page-bg-gradient-wrap {
  position: absolute;
  top: -100px;
  left: 50%;
  transform: translateX(-50%) rotate(180deg) translateZ(0);
  width: 1920px;
  height: 643px;
  opacity: 0.3;
  overflow: hidden;
  /* 60px — must match .preloader-bg .page-bg-gradient-wrap so the preloader
     and the live page render the identical gradient. Do not tune this for
     perf; use layer isolation instead (see below). */
  filter: blur(60px);
  /* Isolate as its own compositor layer so the blur is cached and the rest
     of the page doesn't trigger it to re-rasterize on scroll. */
  backface-visibility: hidden;
  contain: strict;
}

/* About page: the glow sits 100px higher than everywhere else, so it reads
 * above the hero heading rather than behind its second line, and the band
 * bottom lands at 443 instead of 543. That is also what buys the studio reel
 * a clear run before it starts slicing into the gradient - see ABOUT
 * BACKGROUND FADE in js/shared.js, which owns the fade that hides it.
 *
 * BOTH copies move together. The live .page-bg and the preloader's own
 * .preloader-bg deliberately render the same gradient in the same place: the
 * preloader slides up over 1.2s with both on screen, so a 100px difference
 * between them would read as a jump at the hand-off instead of a crossfade.
 *
 * Placed BEFORE the FINAL-SECTION rule below on purpose: that rule has the
 * same specificity, so if About ever gains the footer reposition, the
 * .page-bg--bottom geometry still wins on source order. */
.about-page .page-bg-gradient-wrap,
.about-page .preloader-bg .page-bg-gradient-wrap {
  top: -200px;
}

/* ---- FINAL-SECTION BACKGROUND (start) ----
 * Paired with the .page-bg--bottom toggles in js/shared.js (the homepage fade
 * block and the case-study fade module both set it). To remove the effect,
 * delete this rule, its touch counterpart in the touch-performance media query,
 * and the two toggles. Nothing else reads .page-bg--bottom.
 *
 * At the end of the page the rainbow belongs behind the footer, not above the
 * CTA. This is a plain state flip, NOT an animated move: it is only ever
 * switched while .page-bg is faded to 0, so the reposition is never seen. The
 * fade carries the whole transition.
 *
 * bottom:-100px mirrors the resting top:-100px. The band is also 20% shorter
 * here (643 -> 514) so it reads as a lower, tighter glow behind the footer
 * rather than a full second hero. The inner <img> is sized in % of this box,
 * so it scales with the height on its own.
 *
 * The .page-bg-pattern deliberately stays put: moving it too drags the
 * concentric crosshair into the footer copy and leaves a visible seam where
 * the SVG ends. The .preloader-bg copy is never inside .page-bg--bottom, so
 * the preloader is unaffected. */
.page-bg--bottom .page-bg-gradient-wrap {
  top: auto;
  bottom: -100px;
  height: 514px;
}
/* ---- FINAL-SECTION BACKGROUND (end) ---- */

.page-bg-gradient {
  position: absolute;
  top: -46%;
  left: -49%;
  width: 207%;
  height: 192%;
  max-width: none;
}

/* Also styles the preloader's copy. The selector is unscoped on purpose, so
   the two can never drift apart. max-width:none is load-bearing: bg-pattern.svg
   renders with preserveAspectRatio="none", so the box IS the aspect ratio, and
   the global `img { max-width: 100% }` was squashing that box to the viewport
   width on anything under 1270px while the height stayed 1321. Round came out
   oval on every phone and tablet. */
.page-bg-pattern {
  position: absolute;
  top: -337px;
  left: 50%;
  transform: translateX(-50%);
  width: 1270px;
  height: 1321px;
  max-width: none;
  pointer-events: none;
  opacity: 0.7;
}

/* ---------- NAVIGATION ---------- */
.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  height: 80px;
  display: flex;
  align-items: center;
  transition: background 0.3s;
}

.nav-wrapper {
  width: 100%;
  max-width: 100%;
  padding-left: 40px;
  padding-right: 40px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nav-logo {
  font-size: 18px;
  letter-spacing: -0.36px;
  line-height: normal;
}

.nav-logo-bold {
  font-size: 16px;
  font-weight: 600;
  color: var(--color-black);
  transition: color 0.3s;
}

.nav-logo-light {
  font-size: 16px;
  font-weight: 300;
  color: var(--color-black);
  transition: color 0.3s;
}

/* --- Pill-shaped nav menu --- */
.nav-pill {
  display: flex;
  align-items: center;
  gap: 4px;
  /* Asymmetric by design. The Book a call button is 2px taller than a
     .nav-pill-item, so align-items:center already leaves the active white pill
     3px of breathing room above and below. A flat 2px left made it read as
     tight against the container edge; 3px matches the vertical inset. The
     button keeps its 2px on the right. */
  padding: 2px 2px 2px 3px;
  border-radius: var(--radius-pill);
  background-color: var(--color-pill-bg);
  border: 1px solid rgba(21, 12, 64, 0.02);
  backdrop-filter: blur(25px);
  -webkit-backdrop-filter: blur(25px);
  overflow: clip;
  transition: background-color 0.3s, border-color 0.3s;
}

.nav-pill-item {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 14px 22px;
  border-radius: var(--radius-pill);
  transition: background-color 0.2s;
}

.nav-pill-item:hover .nav-pill-label,
.nav-pill-item:hover .nav-pill-number {
  color: var(--color-black);
}

.nav-pill-item[aria-current="page"] {
  border-radius: 6.25rem;
  background: #FFF;
  box-shadow: var(--shadow-card);
}

.nav-pill-item[aria-current="page"] .nav-pill-label,
.nav-pill-item[aria-current="page"] .nav-pill-number {
  color: var(--color-black);
}

.nav-pill-label {
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 560;
  color: #445365;
  letter-spacing: -0.28px;
  line-height: normal;
  transition: color 0.2s;
}

.nav-pill-number {
  font-family: 'Instrument Serif', var(--font-accent), serif;
  font-size: 10px;
  font-weight: 400;
  font-style: italic;
  color: #2d4869;
  line-height: 1.4;
  text-align: center;
  width: 12px;
  height: 17px;
  transition: color 0.2s;
}

/* --- Hamburger pill button --- */
.nav-hamburger {
  display: none;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

.nav-hamburger-pill {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2px;
  border-radius: 100px;
  background: var(--color-pill-bg);
  border: 1px solid rgba(21, 12, 64, 0.02);
  backdrop-filter: blur(25px);
  -webkit-backdrop-filter: blur(25px);
}

.nav-hamburger-pill-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px 22px;
  border-radius: 100px;
  background: white;
  box-shadow: var(--shadow-card);
}

.hamburger-icon {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 16px;
  height: 16px;
  position: relative;
}

.hamburger-line {
  display: block;
  position: absolute;
  width: 16px;
  height: 1.5px;
  background-color: var(--color-black);
  border-radius: 1px;
  transition: transform 0.4s cubic-bezier(0.87, 0.45, 0.33, 0.88),
              opacity 0.3s ease;
  transform-origin: center center;
}

/* Top line sits 3.5px above center, bottom line 3.5px below */
.hamburger-line:nth-child(1) { transform: translateY(-3.5px); }
.hamburger-line:nth-child(2) { transform: translateY(3.5px); }

/* Open state: both lines rotate to form an X */
.nav-hamburger.is-open .hamburger-line:nth-child(1) {
  transform: translateY(0) rotate(45deg);
}
.nav-hamburger.is-open .hamburger-line:nth-child(2) {
  transform: translateY(0) rotate(-45deg);
}

/* --- Mobile nav modal --- */
.mobile-nav {
  position: fixed;
  top: 0;
  left: 6px;
  right: 6px;
  bottom: 6px;
  z-index: 90; /* below nav (z-index: 100) so nav stays on top */
  pointer-events: none;
  opacity: 0;
  /* Blur + background live on the wrapper (never scales) so the browser
     keeps computing backdrop-filter even while the panel is at scale(0). */
  backdrop-filter: blur(25px);
  -webkit-backdrop-filter: blur(25px);
  background: rgba(255, 255, 255, 0.75);
  border-radius: 21px;
}

.mobile-nav-panel {
  width: 100%;
  height: 100%;
  transform: scale(0);
  transform-origin: top right;
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: 21px;
  padding: 2px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.mobile-nav-spacer {
  height: 80px; /* matches nav height — content sits below the real nav bar */
  flex-shrink: 0;
}

.mobile-nav-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 1;
  min-height: 0;
  justify-content: space-between;
  padding: 0 18px;
  padding-top: 40px; /* Figma: 40px gap between nav header and content */
}

/* Links + CTA grouped together at the top */
.mobile-nav-top-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 40px;
  width: 100%;
}

.mobile-nav-links {
  display: flex;
  flex-direction: column;
  gap: 24px;
  align-items: center;
  width: 100%;
}

.mobile-nav-item {
  display: flex;
  align-items: center;
  gap: 4px;
  justify-content: center;
  width: 100%;
  text-decoration: none;
}

.mobile-nav-label {
  font-family: var(--font-primary);
  font-size: 21px;
  font-weight: 560;
  letter-spacing: -0.42px;
  color: var(--color-dark);
  line-height: normal;
}

.mobile-nav-number {
  font-family: 'Instrument Serif', serif;
  font-style: italic;
  font-size: 14px;
  color: #2d4869;
  line-height: 1.4;
}

.mobile-nav-cta {
  width: 194px;
  align-self: center;
  flex-shrink: 0;
}

.mobile-nav-footer {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  padding: 32px 18px;
  flex-shrink: 0;
}

.mobile-nav-email {
  font-size: 14px;
  color: var(--color-muted);
  letter-spacing: -0.28px;
  line-height: 28px;
  text-decoration: none;
}

.mobile-nav-socials {
  display: flex;
  gap: 12px;
  align-items: center;
}

.mobile-nav-social {
  font-size: 14px;
  color: var(--color-black);
  letter-spacing: -0.28px;
  line-height: 28px;
  text-decoration: none;
}

.mobile-nav-dot {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--color-muted);
  flex-shrink: 0;
}

/* --- Nav light mode (over dark sections) --- */
.nav--light {
  background: var(--color-bg-dark);
}

.nav--light .nav-logo-bold,
.nav--light .nav-logo-light {
  color: var(--color-white);
}

.nav--light .nav-pill {
  background-color: rgba(205, 210, 215, 0.05);
  border-color: rgba(255, 255, 255, 0.01);
}

.nav--light .nav-pill-label {
  color: #b6b6b9;
}

.nav--light .nav-pill-number {
  color: #8e949c;
}

.nav--light .nav-pill-item:hover .nav-pill-label,
.nav--light .nav-pill-item:hover .nav-pill-number {
  color: var(--color-white);
}

.nav--light .nav-pill-item[aria-current="page"] {
  background: rgba(255, 255, 255, 0.12);
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.15), 0 43px 80px 0 rgba(0, 0, 0, 0.05);
}

.nav--light .nav-pill-item[aria-current="page"] .nav-pill-label,
.nav--light .nav-pill-item[aria-current="page"] .nav-pill-number {
  color: var(--color-white);
}

.nav--light .hamburger-line {
  background-color: var(--color-white);
}

.nav--light .nav-hamburger-pill {
  background: rgba(205, 210, 215, 0.05);
  border-color: rgba(255, 255, 255, 0.01);
}

/* ---------- PRIMARY BUTTON ---------- */
.btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 48px;
  padding: 14px 24px;
  background: linear-gradient(to bottom, var(--color-blue-start), var(--color-blue-end));
  border: 0.5px solid var(--color-blue-border);
  border-radius: var(--radius-xl);
  overflow: hidden;
  position: relative;
  cursor: pointer;
  box-shadow: 0 8px 18px rgba(25, 53, 217, 0);
  transition: box-shadow 0.22s ease, border-color 0.22s ease, background 0.22s ease;
}

.btn-primary::before {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.26), rgba(255, 255, 255, 0) 46%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
}

.btn-primary::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: var(--shadow-blue-inset);
  pointer-events: none;
}

.btn-primary-text {
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 400;
  letter-spacing: -0.28px;
  line-height: normal;
  color: var(--color-white);
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.6) 5%, white 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  text-shadow: 0px 1px 2px rgba(8, 19, 62, 0.01);
  position: relative;
  z-index: 1;
}

/* When SplitType splits text into chars, move gradient fill to each char */
.btn-primary-text .char {
  display: inline-block;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.6) 5%, white 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.btn-dark-pill .char,
.abt-pill-link-text .char {
  display: inline-block;
}

.btn-primary:hover {
  border-color: #2b43e7;
  background: linear-gradient(to bottom, #2442f0, #3431f4);
  box-shadow: 0 16px 30px rgba(25, 53, 217, 0.24), 0 6px 12px rgba(25, 23, 27, 0.08);
}

.btn-primary:hover::before {
  opacity: 1;
}

.btn-primary:active {
  background: linear-gradient(to bottom, #182fc6, #2925ca);
  box-shadow: 0 6px 14px rgba(25, 53, 217, 0.18);
}

.btn-primary:focus-visible {
  outline: 2px solid var(--color-blue-start);
  outline-offset: 3px;
}

.btn-primary-lg {
  width: 163px;
}

/* ---------- DARK PILL BUTTON ---------- */
.btn-dark-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  padding: 14px 22px;
  border-radius: var(--radius-pill);
  border: 1px solid black;
  background-image: linear-gradient(90deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.03) 100%),
                     linear-gradient(rgb(20, 19, 20) 0%, rgb(29, 27, 29) 50%, rgb(20, 19, 20) 100%);
  box-shadow: var(--shadow-card);
  position: relative;
  overflow: hidden;
  cursor: pointer;
  transition: box-shadow 0.22s ease, border-color 0.22s ease, background-image 0.22s ease;
}

.btn-dark-pill::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0) 48%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease;
}

.btn-dark-pill::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: var(--shadow-dark-pill-inset);
  pointer-events: none;
}

.btn-dark-pill span {
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 500;
  color: var(--color-white);
  letter-spacing: -0.28px;
  line-height: normal;
  text-shadow: 0px 0px 27px rgba(0, 0, 0, 0.25);
  position: relative;
  z-index: 1;
}

.btn-dark-pill:hover {
  border-color: #242124;
  background-image: linear-gradient(90deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.04) 100%),
                    linear-gradient(rgb(27, 25, 27) 0%, rgb(39, 36, 39) 50%, rgb(23, 21, 23) 100%);
  box-shadow: 0 16px 30px rgba(25, 23, 27, 0.16);
}

.btn-dark-pill:hover::before {
  opacity: 1;
}

.btn-dark-pill:active {
  background-image: linear-gradient(rgb(14, 13, 14) 0%, rgb(23, 21, 23) 100%);
}

.btn-dark-pill:focus-visible {
  outline: 2px solid var(--color-black);
  outline-offset: 3px;
}

/* ---------- BADGE ---------- */
.badge-dark {
  display: inline-flex;
  align-items: center;
  padding: 7px 16px;
  border-radius: var(--radius-pill);
  border: 0.5px solid #545454;
  background: linear-gradient(to bottom, #5c5c5c, #555555 50%, #5c5c5c);
  font-size: 12px;
  font-weight: 500;
  color: var(--color-white);
  letter-spacing: -0.24px;
  line-height: normal;
}

/* ---------- TAG ---------- */
.tag {
  display: inline-flex;
  align-items: center;
  padding: 10px 22px;
  border-radius: var(--radius-pill);
  border: 0.5px solid rgba(116, 84, 84, 0.15);
  background: linear-gradient(to bottom,
    rgba(249, 245, 249, 0.15),
    rgba(245, 245, 251, 0.15) 50%,
    rgba(131, 131, 131, 0.15));
  box-shadow: var(--shadow-card);
  font-size: 12px;
  font-weight: 500;
  color: #373737;
  letter-spacing: -0.24px;
  line-height: normal;
  position: relative;
  overflow: hidden;
}

.tag::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0px 1px 0px 1px var(--color-white-50);
  pointer-events: none;
}

/* ---------- COMPONENT REPOSITORY PAGE ---------- */
.component-library {
  padding: 140px var(--content-padding) 112px;
}

.component-library-inner {
  width: 100%;
  max-width: var(--content-max-width);
  margin: 0 auto;
}

.component-hero,
.component-section {
  display: grid;
  grid-template-columns: minmax(220px, 360px) minmax(0, 1fr);
  gap: 56px;
}

.component-hero {
  align-items: end;
  padding-bottom: 72px;
}

.component-section {
  padding: 56px 0;
  border-top: 1px solid rgba(25, 23, 27, 0.12);
}

.component-section-head,
.component-stack {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.component-kicker,
.component-panel-title,
.component-code,
.component-token {
  font-family: var(--font-primary);
  letter-spacing: 0;
}

.component-kicker,
.component-panel-title {
  color: var(--color-muted);
  font-size: 12px;
  font-weight: 600;
  line-height: 18px;
  margin: 0;
  text-transform: uppercase;
}

.component-intro {
  max-width: 560px;
}

.component-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

.component-grid-single {
  grid-template-columns: 1fr;
}

.component-panel {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 20px;
  border: 1px solid rgba(25, 23, 27, 0.12);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.82);
  padding: 22px;
}

.component-inline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.component-code {
  overflow-x: auto;
  border: 1px solid rgba(25, 23, 27, 0.12);
  border-radius: 8px;
  background: rgba(18, 17, 19, 0.96);
  color: #f5f5f5;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 12px;
  line-height: 20px;
  padding: 14px;
}

.component-token {
  color: var(--color-muted);
  font-size: 13px;
  line-height: 20px;
}

.component-type-row {
  display: grid;
  grid-template-columns: 150px minmax(0, 1fr);
  gap: 24px;
  align-items: baseline;
  border-bottom: 1px solid rgba(25, 23, 27, 0.12);
  padding-bottom: 18px;
}

.component-type-row:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.component-nav-preview {
  width: 100%;
  overflow-x: auto;
  padding: 8px;
}

.component-nav-preview .nav-pill {
  width: max-content;
}

/* ---------- HERO ---------- */
.hero {
  position: relative;
  width: 100%;
  padding-top: 80px; /* nav height */
  background-color: transparent;
  overflow: hidden;
}

.hero-content {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 40px;
  padding: 200px 0 40px var(--content-padding);
}

.hero-text {
  display: flex;
  flex-direction: column;
  gap: 32px;
  max-width: 1202px;
}

.hero-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid #545454;
  background: linear-gradient(to bottom, #5c5c5c, #555555 50%, #5c5c5c);
  box-shadow: var(--shadow-card);
  width: fit-content;
  position: relative;
  overflow: hidden;
}

.hero-badge::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0px 1px 0px 1px #7b7b7b;
  pointer-events: none;
}

.badge-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: #22c55e;
  box-shadow: 0 0 12px rgba(34, 197, 94, 0.6);
}

.badge-text {
  font-size: 12px;
  font-weight: 500;
  color: var(--color-white);
  letter-spacing: -0.24px;
  line-height: normal;
  text-shadow: 0px 0px 27px rgba(255, 255, 255, 0.25);
}

.hero-titles {
  display: flex;
  flex-direction: column;
  line-height: 80px;
}

.hero-subtitle {
  font-size: 36px;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: -2.16px;
  line-height: 80px;
}

.hero-heading {
  font-size: 72px;
  font-weight: 400;
  font-variation-settings: "wght" 400;
  color: var(--color-black);
  letter-spacing: -4.32px;
  line-height: 80px;
  cursor: default;
}

.hero-heading .char {
  display: inline-block;
  font-variation-settings: "wght" 400;
  will-change: font-variation-settings;
}

.hero-bottom {
  display: flex;
  align-items: center;
  gap: 9px;
  /* Mirror the left content padding so the right-aligned logos sit exactly
     as far from the right edge as the text does from the left. */
  padding-right: var(--content-padding);
}

.hero-body {
  font-size: 16px;
  font-weight: 400;
  letter-spacing: -0.32px;
  line-height: 28px;
  /* Size to the actual text (~630px with the forced break) instead of a fixed
     787px box, so the logo carousel gets the real leftover space. */
  width: auto;
  max-width: 787px;
  flex-shrink: 0;
}

.hero-body-muted {
  color: var(--color-muted);
}

.hero-body-dark {
  color: var(--color-black);
}

.hero-cta-mobile {
  display: none;
  align-items: center;
  justify-content: center;
  width: 142px;
  height: 48px;
  margin-top: 24px;
}

/* -- Hero logo carousel (fade in/out) -- */
.logo-carousel {
  display: flex;
  align-items: center;
  gap: 40px;
  flex: 1;
  justify-content: flex-end;
}

.logo-column {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 80px;
  height: 28px;
  /* Columns must never squish: the absolutely-positioned logo images keep
     their width and would overlap neighbouring columns. */
  flex-shrink: 0;
}

.logo-name {
  position: absolute;
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.3px;
  color: var(--color-muted);
  white-space: nowrap;
  user-select: none;
  opacity: 0;
  transform: translateY(8px);
}

.logo-name-img {
  height: 16px;
  width: auto;
  max-width: 80px;
  object-fit: contain;
  opacity: 0;
}

/* Drop logo columns one by one as the viewport narrows so the carousel never
   crowds the hero body text or clips off the right edge. Thresholds assume
   195px side padding + ~630px body text + 40px minimum breathing room.
   Scoped to .hero-bottom: the social-proof row reuses these same classes and
   has a whole line to itself, so it must not be thinned out with the hero. */
@media (max-width: 1640px) {
  .hero-bottom .logo-column:nth-child(5) {
    display: none;
  }
}

@media (max-width: 1520px) {
  .hero-bottom .logo-column:nth-child(4) {
    display: none;
  }
}

@media (max-width: 1400px) {
  .hero-bottom .logo-column:nth-child(3) {
    display: none;
  }
}

@media (max-width: 1280px) {
  .hero-bottom .logo-column:nth-child(2) {
    display: none;
  }
}

/* ---------- PORTFOLIO CAROUSEL ---------- */
.portfolio-carousel {
  position: relative;
  width: 100%;
  overflow: hidden;
  padding: 40px 0 56px;
  cursor: none;
  user-select: none;
  touch-action: pan-y;
}

.carousel-track {
  display: flex;
  gap: 24px;
  padding: 0;
  will-change: transform;
}

.carousel-card {
  flex-shrink: 0;
  width: 739px;
  height: 508px;
  padding: 4px;
  background: var(--color-white);
  border-radius: 32px;
  box-shadow: var(--shadow-card);
  overflow: hidden;
  transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

.carousel-track.is-dragging .carousel-card {
  transform: scale(0.93);
}

.carousel-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 28px;
  background-color: var(--color-card-bg);
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
}

.carousel-card-narrow {
  width: 238px;
}

.carousel-card-wide {
  width: 722px;
}

/* Drag cursor — frosted glass pill (same style as project cursor) */
.drag-cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 6.25rem;
  height: 6.25rem;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 6.25rem;
  border: 1px solid var(--color-dark-border);
  background: rgba(98, 96, 112, 0.30);
  box-shadow: var(--shadow-card);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  pointer-events: none;
  z-index: 9999;
  /* Same recipe, same reasoning, as .custom-cursor, see the long comment
     there. The blur stays on the base rule; visibility is the idle gate. */
  visibility: hidden;
  transform: translate(-50%, -50%) scale(0.01);
  transition: transform 0.35s cubic-bezier(0.43, 0.98, 0.87, 1),
              opacity 0.35s cubic-bezier(0.43, 0.98, 0.87, 1),
              visibility 0.35s cubic-bezier(0.43, 0.98, 0.87, 1);
  opacity: 0;
}

.drag-cursor.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, -50%) scale(1);
}

.drag-cursor.is-leaving {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.01);
}

.drag-cursor-text {
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 500;
  color: #fff;
  letter-spacing: -0.28px;
  text-transform: none;
}

/* ---------- ABOUT / MANIFESTO ---------- */
.about {
  padding: 0 var(--content-padding);
  position: relative;
  overflow: hidden;
}

.about-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh; /* iOS Safari: accounts for dynamic address bar */
  gap: 64px;
}

.about-text {
  display: flex;
  flex-direction: column;
  gap: 32px;
  max-width: 860px;
  margin-left: auto;
}

.about-paragraph {
  font-family: 'Geist', sans-serif;
  font-size: 1.625rem; /* 26px */
  font-weight: 400;
  line-height: 3rem; /* 48px */
  letter-spacing: -0.0975rem;
}

/* Text-fill animation: chars start grey, animate to dark on scroll */
.about-paragraph .char {
  display: inline-block;
  color: #c8c8c8;
}

.about-founders {
  display: flex;
  align-items: flex-end;
  gap: 60px;
  margin-left: auto;
  max-width: 860px;
  width: 100%;
}

.founder {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.founder-signature {
  margin-bottom: -8px;
  position: relative;
  z-index: 1;
}

.founder-signature svg {
  height: 48px;
  width: auto;
  display: block;
  overflow: visible;
  color: var(--color-dark);
}

.founder-signature-img {
  height: 48px;
  width: auto;
  display: block;
}

.founder-name {
  font-family: 'Geist', sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-dark);
  letter-spacing: -0.26px;
  line-height: normal;
}

.founder-role {
  font-family: 'Geist', sans-serif;
  font-size: 13px;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: -0.26px;
  line-height: 1.6;
}

/* ---------- PAGE SECTION (Labs, Hiring, etc.) ---------- */
.page-section {
  padding: 80px var(--content-padding);
}

.page-section-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.page-section-content .badge-dark {
  margin-bottom: 0;
}

.page-section-content .section-heading {
  margin-bottom: 0;
}

.page-section-body {
  font-size: 1.25rem;
  line-height: 1.75;
  color: var(--color-muted);
  max-width: 720px;
}

.page-section-body a {
  color: var(--color-dark);
  text-decoration: underline;
}

.page-section-body a:hover {
  color: var(--color-blue-start);
}

/* ---------- LEGAL PAGES (Privacy) ---------- */
.legal-hero {
  padding: 200px var(--content-padding) 0;
  text-align: center;
}

.legal-hero-subtitle {
  font-family: var(--font-primary);
  font-size: 36px;
  font-weight: 400;
  line-height: 80px;
  letter-spacing: -2.16px;
  color: var(--color-muted);
}

.legal-hero-title {
  font-family: var(--font-primary);
  font-size: 72px;
  font-weight: 400;
  line-height: 80px;
  letter-spacing: -4.32px;
  color: var(--color-black);
}

.legal-content {
  padding: 120px 195px 120px 350px;
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.legal-block {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 712px;
}

.legal-block h2 {
  font-family: var(--font-primary);
  font-size: 26px;
  font-weight: 400;
  line-height: 42px;
  letter-spacing: -1.56px;
  color: var(--color-dark);
  margin: 0;
}

.legal-block p,
.legal-block ul {
  font-family: var(--font-primary);
  font-size: 16px;
  font-weight: 400;
  line-height: 28px;
  letter-spacing: -0.32px;
  color: var(--color-muted);
  margin: 0;
}

.legal-block ul {
  padding-left: 20px;
  list-style: disc;
}

.legal-block ul li {
  margin-bottom: 2px;
}

.legal-block a {
  color: var(--color-dark);
  text-decoration: underline;
}

.legal-block a:hover {
  color: var(--color-blue-start);
}

/* ---------- SELECTED PROJECTS ---------- */
.projects {
  /* 128 = 160 - 20%. This bottom padding is the ENTIRE gap between the last
     case-study card (bioMérieux) and the "See more dosing" / labs section —
     .more-dosing adds no top padding of its own (Kamil, 2026-08-06). */
  padding: 0 var(--content-padding) 128px;
}

.projects-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
}

.projects-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 80px;
}

.projects-header .section-heading {
  margin-bottom: 0;
  max-width: 792px;
}

.projects-see-all {
  font-size: 21px;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: -0.63px;
  text-decoration: underline;
  line-height: 32px;
  transition: color 0.2s;
  flex-shrink: 0;
}

.projects-see-all:hover {
  color: var(--color-dark);
}

.section-heading {
  font-size: 56px;
  font-weight: 400;
  color: var(--color-dark);
  letter-spacing: -3.36px;
  line-height: 68px;
  margin-bottom: 48px;
}

.section-heading .word,
.services-heading .word,
.cta-heading .word,
.cal-modal-heading .word,
.project-title .word,
.service-title .word {
  display: inline-block;
  white-space: nowrap;
}

.section-heading .char,
.services-heading .char,
.cta-heading .char,
.cal-modal-heading .char {
  display: inline-block;
}

/* Full-width demo reel block at the top of the section.
   Kept on ice: the markup is commented out in index.html for the initial
   release (placeholder only, no film yet). Restore both together. */
.projects-reel {
  padding: 40px 0;
}

.projects-reel-card {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1530 / 850;
  background-color: #ebeced;
  border-radius: 16px;
  overflow: hidden;
  cursor: pointer;
  transition: transform 0.25s ease;
  contain: paint;
}

.projects-reel-card:hover {
  transform: translateY(-2px);
}

.projects-reel-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 100px;
  background: rgba(98, 96, 112, 0.3);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: #fff;
  cursor: pointer;
  padding: 0;
}

/* The showcase is three blocks of deliberately unequal weight rather than a
   grid (Figma 996:483): a wide feature, an offset landscape/portrait pair
   with the testimonial tucked into the whitespace it leaves, then a narrower
   feature to close. Card widths are percentages of the 1530px content column
   so the proportions survive every breakpoint. */
.projects-row {
  display: flex;
  justify-content: center;
}

/* Feature rows: a single card centred in the content column. */
.project-card--feature {
  width: 79.739%; /* 1220 / 1530 */
}

.projects-row--feature-narrow .project-card--feature {
  width: 59.346%; /* 908 / 1530 */
  margin-top: 160px;
}

/* The pair sits inset from the content column, and the portrait card drops
   80px so the two never read as a row. */
.projects-row--pair {
  justify-content: space-between;
  /* flex-start, not the default stretch: the short landscape card would
     otherwise grow an invisible click target down to the portrait's baseline. */
  align-items: flex-start;
  width: 94.118%; /* 1440 / 1530 */
  margin: 144px auto 0;
}

.project-card--landscape {
  width: 41.528%; /* 598 / 1440 */
}

.project-card--portrait {
  width: 30.694%; /* 442 / 1440 */
  padding-top: 80px;
}

.project-card {
  display: flex;
  flex-direction: column;
  gap: 24px;
  text-decoration: none;
  color: inherit;
  transition: opacity 0.25s ease;
}

.project-card:hover {
  opacity: 1;
}

.project-image {
  width: 100%;
  border-radius: 16px;
  overflow: hidden;
  background-color: #ededed;
  cursor: none;
}

/* 16:9, matching the Figma slot (1218 x 685.125) and the master preview's own
 * ratio, so the feature card crops nothing. Was 122/69, which is 1.768 and
 * shaved ~7px off the master's width at 1530. */
.project-image--feature {
  aspect-ratio: 16 / 9;
}

.project-image--landscape {
  aspect-ratio: 16 / 9;
}

.project-image--portrait {
  aspect-ratio: 3 / 4;
}

.project-image-dark {
  background-color: #edeef1;
}

.project-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Label + title on the left, category tag pushed to the card's right edge. */
.project-info {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.project-info-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.project-label {
  font-size: 16px;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: -0.32px;
  line-height: 28px;
}

.project-title {
  font-size: 24px;
  font-weight: 500;
  color: var(--color-dark);
  letter-spacing: -0.96px;
  line-height: 40px;
}

.project-tag {
  flex-shrink: 0;
}

/* Testimonial, indented under the offset pair — left-aligned and quiet so it
   reads as a caption to the work above it, not as its own section. */
.projects-testimonial {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
  margin-top: 32px;
  padding: 0 36.601% 0 11.765%; /* 560 / 1530 and 180 / 1530 */
}

/* ==================== MORE DOSING ==================== */

.more-dosing {
  position: relative;
  /* Normally-scrolling section (no pin): the ribbon animates while the section
     transits the viewport, driven by ScrollTrigger start 'top bottom' to end
     'bottom top' in shared.js. Height is one viewport (min 760px) so the
     transit spans ~2 screens of scroll, enough travel for the orbit to read. */
  height: auto;
  min-height: 760px;
  overflow: visible;
  background: transparent;
  isolation: auto;
}

.more-dosing-stage {
  position: relative;
  width: 100%;
  height: 100vh;
  min-height: 760px;
  overflow: hidden;
}

.more-dosing-copy {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 70;
  display: flex;
  width: min(430px, calc(100vw - 48px));
  flex-direction: column;
  align-items: center;
  text-align: center;
  transform: translate(-50%, -50%);
  pointer-events: auto;
}

.more-dosing-heading {
  margin: 0;
  font-family: var(--font-primary);
  font-size: 24px;
  font-weight: 500;
  line-height: 32px;
  letter-spacing: 0;
  color: var(--color-dark);
}

.more-dosing-body {
  margin: 8px 0 24px;
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 400;
  line-height: 24px;
  letter-spacing: 0;
  color: var(--color-muted);
}

.more-dosing-body sup {
  font-size: 8px;
  line-height: 0;
}

.more-dosing-copy .more-dosing-cta {
  align-self: center;
}

.more-dosing-media {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  perspective: 1800px;
  transform-style: preserve-3d;
}

.more-dosing-plane {
  position: absolute;
  left: 50%;
  top: 50%;
  width: clamp(190px, 23vw, 370px);
  aspect-ratio: 1.46;
  margin: 0;
  padding: 2px;
  overflow: hidden;
  border-radius: 18px;
  background: #fff;
  box-shadow:
    0 0.5px 1px 0 var(--shadow-color-near),
    0 24px 54px 0 rgba(66, 39, 92, 0.05);
  opacity: 0;
  transform: translate3d(-50%, -50%, 0) scale(0.8);
  transform-origin: 50% 50%;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  will-change: transform, opacity;
}

.more-dosing-plane--wide {
  width: clamp(250px, 32vw, 520px);
  aspect-ratio: 1.58;
}

.more-dosing-plane img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 16px;
  object-fit: cover;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}

/* Depth-of-field layer: a duplicate of each card image, pre-blurred once by
   the GPU and stacked on top of the sharp copy. JS drives only its opacity
   (0 = sharp front card, 1 = blurred back card), so the blur is a cheap
   compositor crossfade rather than a per-frame filter recompute. */
.more-dosing-plane img.more-dosing-plane-blur {
  position: absolute;
  inset: 2px;
  width: auto;
  height: auto;
  opacity: 0;
  filter: blur(8px);
  transform: scale(1.03);
  transform-origin: 50% 50%;
  pointer-events: none;
  will-change: opacity;
}

/* Depth-collage experiment: a long scroll track with a single sticky viewport.
   The original orbit remains the default styling unless the section carries
   data-more-dosing-variant="depth-collage" and JS marks it ready. */
.more-dosing[data-more-dosing-variant="depth-collage"] {
  min-height: 760px;
  isolation: isolate;
}

.more-dosing[data-more-dosing-variant="depth-collage"].more-dosing--depth-ready {
  height: 350vh;
  height: 350svh;
  min-height: 0;
}

.more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-stage {
  isolation: isolate;
  background: transparent;
}

.more-dosing[data-more-dosing-variant="depth-collage"].more-dosing--depth-ready .more-dosing-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  min-height: 0;
  overflow: hidden;
  overflow: clip;
}

.more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-copy {
  isolation: isolate;
}

/* Legibility scrim behind the copy: opaque white at the text, gone by the
   element's own edge. Two rules keep it from reading as an object of its own
   over the page gradient:
   1. `closest-side` pins the ellipse to the box, so the ramp is guaranteed to
      finish inside the paint area. A tint layer used to sit on top of this one
      sized at 72%/68% — its transparent stop landed past the box, so the box
      clipped it mid-ramp into a faint violet rectangle with a hard border that
      blur(22px) only smeared into a soft-edged panel.
   2. The stops follow a smoothstep curve rather than a handful of linear
      segments, which is what put a visible ring where the falloff changed
      slope. Pure white throughout — no second hue to shift the background. */
.more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-copy::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: -120px -210px;
  border-radius: 0;
  background: radial-gradient(
    ellipse closest-side at 50% 50%,
    rgba(255, 255, 255, 1) 0,
    rgba(255, 255, 255, 1) 30%,
    rgba(255, 255, 255, 0.977) 40%,
    rgba(255, 255, 255, 0.855) 50%,
    rgba(255, 255, 255, 0.632) 60%,
    rgba(255, 255, 255, 0.368) 70%,
    rgba(255, 255, 255, 0.145) 80%,
    rgba(255, 255, 255, 0.023) 90%,
    rgba(255, 255, 255, 0) 100%
  );
  filter: blur(22px);
  transform: translateZ(0);
  pointer-events: none;
}

.more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-media {
  perspective: 1400px;
  perspective-origin: 50% 50%;
  transform-origin: 50% 50%;
  transform-style: preserve-3d;
}

.more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-plane {
  width: clamp(160px, 17vw, 290px);
  box-shadow:
    0 1px 2px var(--shadow-color-near),
    0 18px 48px rgba(66, 39, 92, 0.08),
    0 0 0 1px rgba(25, 23, 27, 0.03);
}

.more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-plane--wide {
  width: clamp(210px, 23vw, 380px);
}

.more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-plane img.more-dosing-plane-blur {
  display: block;
  filter: blur(7px);
  transform: scale(1.035);
}

@media (max-width: 767px) {
  .more-dosing {
    min-height: 640px;
  }

  .more-dosing-stage {
    min-height: 640px;
  }

  .more-dosing-copy {
    left: 50%;
    top: 52%;
    width: min(310px, calc(100vw - 48px));
    transform: translate(-50%, -50%);
    align-items: center;
    text-align: center;
  }

  .more-dosing-heading {
    font-size: 22px;
    line-height: 30px;
  }

  .more-dosing-body {
    max-width: 270px;
    margin-bottom: 20px;
    line-height: 22px;
  }

  .more-dosing-cta {
    align-self: center;
    min-width: 108px;
    height: 46px;
  }

  .more-dosing-plane {
    width: clamp(132px, 50vw, 220px);
    border-radius: 14px;
  }

  .more-dosing-plane--wide {
    width: clamp(170px, 62vw, 270px);
  }

  .more-dosing-plane img {
    border-radius: 12px;
  }

  /* Cards are ~2.5x smaller on phones, so the desktop 8px depth blur reads
     proportionally much stronger and washes the back row out entirely. */
  .more-dosing-plane img.more-dosing-plane-blur {
    filter: blur(5px);
  }

  .more-dosing[data-more-dosing-variant="depth-collage"].more-dosing--depth-ready {
    height: 300vh;
    height: 300svh;
  }

  .more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-copy {
    top: 50%;
    width: min(300px, calc(100vw - 56px));
  }

  .more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-copy::before {
    inset: -88px -96px;
    filter: blur(16px);
  }

  .more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-media {
    perspective: 1050px;
  }

  .more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-plane {
    width: clamp(116px, 38vw, 178px);
  }

  .more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-plane--wide {
    width: clamp(148px, 46vw, 214px);
  }

  .more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-plane img.more-dosing-plane-blur {
    filter: blur(5px);
  }
}

@media (hover: none) and (pointer: coarse) {
  .more-dosing[data-more-dosing-variant="depth-collage"].more-dosing--depth-ready {
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .more-dosing[data-more-dosing-variant="depth-collage"] {
    height: auto;
    min-height: 640px;
  }

  .more-dosing[data-more-dosing-variant="depth-collage"] .more-dosing-stage {
    position: relative;
    top: auto;
    height: 100vh;
    min-height: 640px;
  }
}

/* ==================== CASE STUDIES PAGE ==================== */

.cs-hero {
  padding: 0 var(--content-padding);
  /* 116 nav + 100 of air, and 200 down to the first card image — Figma
     1236:50751 puts the subtitle at y=216 and the first card at y=708. */
  padding-top: 216px;
  padding-bottom: 200px;
}

.cs-hero-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 80px;
}

.cs-hero-subtitle {
  font-family: var(--font-primary);
  font-size: 16px;
  font-weight: 400;
  color: var(--color-muted);
  line-height: 26px;
  letter-spacing: -0.64px;
  text-align: right;
  max-width: 598px;
  align-self: flex-end;
}

.cs-hero-heading {
  font-family: var(--font-primary);
  font-size: 72px;
  font-weight: 400;
  color: var(--color-black);
  line-height: 80px;
  letter-spacing: -4.32px;
  max-width: 1222px; /* Figma's own text box — at 900 the copy wraps to 3 lines */
}

.cs-page {
  padding: 0 var(--content-padding);
}

/* The listing grid IS the homepage showcase (Figma 1236:50732 reuses the slots
   from 996:483), so everything below only supplies what the homepage never
   needed: the two extra row gaps in the longer running order, the one row whose
   landscape card drops to align image bottoms, and the coming-soon state. */
.cs-showcase {
  max-width: var(--content-max-width);
  margin: 0 auto;
}

/* Figma's rhythm is section padding, and two transitions differ from the
   homepage defaults: a pair after the narrow feature clears that section's
   160px bottom padding as well as its own 64px top (160 + 64), and a feature
   after a pair needs 160 where the homepage's leading feature needed none. */
.cs-showcase .projects-row--feature-narrow + .projects-row--pair {
  margin-top: 224px;
}

.cs-showcase .projects-row--pair + .projects-row--feature {
  margin-top: 160px;
}

/* Closing pair (Figma 1236:51396): the landscape card drops so the two IMAGE
   BOTTOMS line up rather than the tops. The drop is the portrait's own 80px
   offset plus the difference between the two image heights, and both heights
   are pure functions of the row width, so one calc holds at every breakpoint:
     portrait  30.694% * 4/3 = 40.925% of the row
     landscape 41.528% * 9/16 = 23.360% of the row     */
.projects-row--pair-drop .project-card--landscape {
  padding-top: calc(80px + 17.566%);
}

/* HockeyStack is a UI screenshot: the 3:4 slot crops horizontally, so it pins
   left to keep the sidebar and greeting in frame (docs/CASE-STUDY-PREVIEWS-PLAN.md). */
.project-image--portrait[data-focal="hockeystack"] img {
  object-position: 0% 50%;
}

/* Only four case studies ship in the first release. The rest render as a plain
   div rather than a link, so there is no href for js/home-morph.js to bind and
   no dead-end navigation. Kamil removed the static "Coming soon" badge that
   used to sit in the card's tag slot, so the hover cursor is now the only
   signal — .custom-cursor--soon swaps the arrow for the label. That also
   retired the rule stacking .project-info in the narrow columns, which only
   existed to keep the badge from squeezing those descriptions. */

/* .cs-card-link, .cs-card and .cs-card-image's own height belonged to the old
   listing grid. What survives is the box below, which the .csr-grid related
   blocks on the detail pages still use (they re-specify link display, height
   and aspect-ratio themselves). */
.cs-card-image {
  width: 100%;
  border-radius: 16px;
  overflow: hidden;
  background-color: #ebeced;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* PREVIEW FRAMING (Figma canvas 1210:404, "Case Studies Previews").
 * Every project preview is ONE 16:9 master cover-cropped to the slot, so the
 * only per-project variable is which part of it survives the crop. Centre is
 * the default and stays implicit; only projects the design pins off-centre get
 * a rule, keyed on data-focal so the value lives here rather than being copied
 * into each page's markup.
 *
 * HockeyStack is a UI screenshot, so it pins to the TOP in both wide slots:
 * centring a 2.19:1 or 1.91:1 crop eats the browser chrome and the greeting.
 * Both slots crop vertically only, so one rule covers them.
 *
 * The 0% 50% portrait cell is live too, on the /case-studies listing — see
 * .project-image--portrait[data-focal] above. bioMerieux's 85% 50% / 50% 76%
 * still has no page that puts it in those slots. The full table lives in
 * docs/CASE-STUDY-PREVIEWS-PLAN.md - read it before moving a project between
 * slots, or the crop silently reverts to centre. */
.csr-grid .cs-card-image[data-focal="hockeystack"] img {
  object-position: 50% 0%;
}

.cs-card-image--dark {
  background-color: #0f0e10;
  padding: 24px;
}

.cs-card-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 0;
}

.cs-card-image--dark img {
  border-radius: 0;
  object-fit: contain;
}

/* .cs-card-info / -meta / -labels / -title / -tags went with the old listing
   grid — the cards now use .project-info / .project-label / .project-title.
   .cs-tag stays: the Nomos, Finna, RelateWise and Finance detail pages use it. */
.cs-tag {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  padding: 14px 22px;
  border-radius: 100px;
  border: none;
  background:
    linear-gradient(90deg, var(--color-white-50) 0%, var(--color-white-50) 100%),
    linear-gradient(180deg, rgba(249, 245, 249, 0.15) 0%, rgba(245, 245, 251, 0.15) 50%, rgba(131, 131, 131, 0.15) 100%);
  box-shadow:
    0 43px 80px 0 var(--shadow-color-far),
    inset 0 1px 0 1px var(--color-white-50);
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 500;
  color: #373737;
  letter-spacing: -0.28px;
  text-shadow: 0 0 27px rgba(255, 255, 255, 0.25);
  white-space: nowrap;
}

/* ==================== ABOUT PAGE (abt-*) ==================== */

/* --- Hero ---
   Figma 1302:931 rebuilds this as the case studies hero, so every number below
   is deliberately .cs-hero's rather than one of its own: the same 216px drop
   from the top, the content column on the page's own gutter instead of the
   351px inset it used to carry, the muted line moved ABOVE the heading and set
   hard right, and the heading at the listing's 72/80. Read the two heroes as
   one composition; a change to either belongs in both.
   The bottom padding is the one number that stays local. Case studies opens
   onto a card grid, About onto the studio reel, and the reel's -8.5% pull
   (see .abt-video-section) was tuned against this 80px. */
.abt-hero {
  padding: 216px var(--content-padding) 80px;
}

.abt-hero-content {
  position: relative;
  z-index: 2;
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 80px;
}

.abt-hero-heading {
  font-family: var(--font-primary);
  font-weight: 400;
  font-size: 72px;
  line-height: 80px;
  letter-spacing: -4.32px;
  color: var(--color-black);
  max-width: 1222px; /* Figma 1302:938's own text box, as on .cs-hero-heading */
  margin: 0;
}

/* Above the heading, hard right, 16px: the case studies subtitle exactly. Only
   the width cap is this page's own: 417px is Figma 1302:935's text box, and it
   splits the sentence evenly under the heading's right edge, which the wider
   598px cap over on the listing does not. */
.abt-hero-body {
  font-family: var(--font-primary);
  font-weight: 400;
  font-size: 16px;
  line-height: 26px;
  letter-spacing: -0.64px;
  color: var(--color-muted);
  text-align: right;
  max-width: 417px;
  align-self: flex-end;
  margin: 0;
}

/* --- Studio reel ---
   Figma 526:1000: the reel runs 1869 wide inside the 1920 frame, i.e. a 25.5
   gutter, so it reads as full-bleed rather than as the 351-inset card it used
   to be. Rounded to 24 for the spacing scale, since 1.5px at 1920 is 0.08% of
   the width. Vertical padding stays at +40% over Figma, like the rest of the
   page's rhythm. */
.abt-video-section {
  padding: 56px 24px;
  /* Pulled up towards the hero copy, into the reel's own empty headroom: the
     comp never draws above y=186 of its 1070 (the ring's top card, at 1.42s),
     so the block opens with a band of nothing and closing it moves the
     animation, not the text.
     The pull is a PERCENTAGE, and that is the point. Percentage margins
     resolve against the containing block's width, so this scales with the
     reel exactly like the headroom it spends: -8.5% is the 164px Kamil asked
     for at the 1920 design width and stays proportional below it. A constant
     164px does not survive the trip, because the headroom does not: 223px of
     clearance at 1920, 138px at 1200, where a fixed pull would drive the
     ring's top card 26px into the copy. */
  margin-top: -8.5%;
}

.abt-video-wrap {
  position: relative;
  width: 100%;
  /* The reel owns the block's height. Every child of the coded stage is
     absolutely positioned and the white-matte poster that used to hold the
     box open is gone, so without this the section collapses to zero and the
     animation is invisible. 2246/1070 is the Jitter comp, which is also what
     the retired export was. */
  aspect-ratio: 2246 / 1070;
}

.abt-video-card {
  display: block;
  width: 100%;
  height: auto;
  /* Figma 1869x890 (1268:59265). The reel's own 2246x1070 export is 2.0991 and
     this box is 2.0999, so cover crops nothing. It is here to guarantee the
     frame is never letterboxed if a re-export lands at a different ratio. */
  aspect-ratio: 1869 / 890;
  object-fit: cover;
  border-radius: 16px;
  /* The export carries its own white matte, so there is no card colour to
     paint under it, and nothing here is interactive: no controls, no context
     menu route to pausing it. */
  background: none;
  pointer-events: none;
}

/* --- Studio reel, coded (js/about-orbit.js + js/about-orbit-data.js) ---
   The reel is eight absolutely positioned cards driven by one matrix() each,
   composed from the Jitter project's own keyframes. Nothing here paints a
   background: the reel is transparent, so the page shows through exactly
   where the old export used to carry its own white matte.
   Cards are 700x525 "comp px" scaled by the matrix, so the corner radius
   below is in card units and scales with the card, like the Jitter matte.
   Depth-of-field blur is a CSS filter on the photo itself, set per frame by
   the runtime; there is no second blurred layer and no backdrop-filter here.
   Until JS runs (and if it never does) the rules below hold every card in
   the animation's own first frame, the stacked deck at comp (773, 273):
   110.43% of a card width across, 52% of a card height down.
   .abt-orbit-video-mode revives the dormant <video> for ?reel=video review. */
.abt-orbit {
  position: absolute;
  inset: 0;
  overflow: hidden;               /* the comp frame: cards fly past it */
  border-radius: 16px;            /* same frame treatment the video had */
  /* Decoration, and the -64px pull above slides its box over the hero copy,
     so it must not intercept a selection or a click there. The retired
     <video> carried pointer-events: none for the same reason. */
  pointer-events: none;
}

.abt-orbit-card {
  position: absolute;
  left: 0;
  top: 0;
  margin: 0;
  width: 31.166%;                 /* 700 / 2246 */
  height: 49.065%;                /* 525 / 1070 */
  transform-origin: 0 0;          /* matrices map card-local (0,0) directly */
  transform: translate(110.43%, 52%);
  will-change: transform;
}

/* Bottom fade over the reel. Kamil's call after the alpha-per-card version:
   a plain white linear gradient, ~100px at the 1920 design width, expressed
   as a percentage of the stage so it keeps that proportion everywhere. It
   lives inside .abt-orbit, which is what guarantees the thing he underlined:
   the stage IS the animation's box, so the gradient ends where the animation
   ends at every viewport, and the stage's overflow and radius clip it to the
   same frame. z-index 20 clears the cards, whose inline indices run 1 to 8;
   the nav sits at 100 and stays above it. */
.abt-orbit-fade {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 11%;                    /* 98px of the 892px stage at 1920 */
  z-index: 20;
  pointer-events: none;
  /* Kamil's values. Note the ceiling: it stops at 0.80 rather than solid
     white, so a card crossing the clip line is veiled rather than erased,
     and the early 0.04 stop keeps the top of the band from showing an edge
     against the page gradient behind it. */
  background: linear-gradient(180deg,
    rgba(255, 255, 255, 0.00) 0%,
    rgba(255, 255, 255, 0.04) 4.56%,
    rgba(255, 255, 255, 0.80) 100%);
}

.abt-orbit-card > picture > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Card units, so it scales with the transform exactly like the Jitter
     matte did. The number is calibrated, not literal: at the 1920 design
     width the stage is 1872 wide (unit scale 0.834) and the carousel's
     enlarged card sits at 0.89, so a card-local 16 lands at ~11.9 CSS px on
     screen, which is the ~12px Kamil asked for in that state. Narrower
     viewports scale it down with everything else (~9px at 1440).
     scripts/build-about-orbit.mjs CARD_RADIUS must match. */
  border-radius: 16px;
  display: block;
}

/* The dormant review video: display is the only thing these states toggle
   (never backdrop-filter, see the Safari teardown note on the cursors). */
.abt-video-card { display: none; }
.abt-video-wrap.abt-orbit-video-mode .abt-video-card { display: block; }
.abt-video-wrap.abt-orbit-video-mode .abt-orbit { display: none; }

/* The reel's bottom dissolve (Figma 1268:59266) lived here: a white veil over
   a three-step backdrop-blur ramp, whose only job was to stop the opaque film
   being cut off by the block's edge. The coded reel has no edge to hide, so
   the band was just a white smear over a transparent stage and the page
   background behind it. Gone with the video, and its three backdrop-filters
   with it. */

/* --- Manifesto text (scroll-scrubbed char-by-char fill, matches homepage) --- */
.abt-manifesto {
  /* +40% vertical padding. */
  padding: 140px var(--content-padding);
  display: flex;
  align-items: flex-start;
  justify-content: center;
}

.abt-manifesto-content {
  max-width: 702px;
  width: 100%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 32px;
  text-align: left;
}

.abt-manifesto-paragraph {
  font-family: var(--font-primary);
  font-weight: 400;
  font-size: 26px;
  line-height: 48px;
  letter-spacing: -1.56px;
  color: var(--color-dark);
  margin: 0;
}

/* SplitType generates these at runtime. Start grey, animate to dark. */
.abt-manifesto-paragraph .char {
  display: inline-block;
  color: #c8c8c8;
}

/* --- Image collage with 3D parallax (per-image tilt on scroll + hover) --- */
.abt-collage {
  padding: 56px 0;
  overflow: hidden;
}

/* Perspective container — creates the 3D camera. `transform-style: flat`
   (not preserve-3d) keeps siblings from re-sorting their stacking based on
   their current Z — which was making items pop in front of each other as
   the mouse moved. Items still get a 3D tilt inside their own layer, but
   the paint order is locked by DOM/z-index. */
.abt-collage-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 1920 / 930;
  perspective: 1400px;
  perspective-origin: 50% 50%;
  transform-style: flat;
}

.abt-collage-item {
  position: absolute;
  left: var(--x);
  top: var(--y);
  overflow: hidden;
  border-radius: 12px;
  transform-style: flat;
  /* JS sets the transform each frame. No Z-translation — only X/Y and
     rotations so 2D siblings never need to re-sort. */
  --reveal-progress: 0;
  transition: none;
  will-change: transform, filter, opacity;
  filter: blur(calc((1 - var(--reveal-progress)) * 14px));
  opacity: var(--reveal-progress);
}

/* Lock a stable z-index per item using the DOM order so they never swap
   stacking, regardless of rotation or translate applied by the parallax. */
.abt-collage-item:nth-child(1)  { z-index: 1; }
.abt-collage-item:nth-child(2)  { z-index: 2; }
.abt-collage-item:nth-child(3)  { z-index: 10; }  /* big centre */
.abt-collage-item:nth-child(4)  { z-index: 4; }
.abt-collage-item:nth-child(5)  { z-index: 5; }
.abt-collage-item:nth-child(6)  { z-index: 3; }
.abt-collage-item:nth-child(7)  { z-index: 6; }
.abt-collage-item:nth-child(8)  { z-index: 7; }
.abt-collage-item:nth-child(9)  { z-index: 8; }
.abt-collage-item:nth-child(10) { z-index: 9; }
.abt-collage-item:nth-child(11) { z-index: 11; }

/* Sizes — percentages so the collage scales with viewport width. These are the
   Figma *frame* dimensions from node 526:1011 (276.8x235 / 192x163 / 68x57 on
   the 1920 stage). Each photo overflows its frame and is cropped by it, so
   reading the photo box instead (428x285, 247x164, 96x95) made every tile ~55%
   too large and the wrong shape. */
.abt-collage-item--lg {
  width: 14.417%;
  aspect-ratio: 276.8 / 235;
}

.abt-collage-item--md {
  width: 10%;
  aspect-ratio: 192 / 163;
}

.abt-collage-item--sm {
  width: 3.5417%;
  aspect-ratio: 68 / 57;
}

.abt-collage-item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  user-select: none;
}

/* Morph bridge — a single fixed-position element that ferries the centre
   collage image into the team-feature image as you scroll. Driven entirely
   by JS (transform + size). Starts hidden. */
.abt-morph-bridge {
  position: fixed;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  overflow: hidden;
  border-radius: 12px;
  z-index: 40;
  pointer-events: none;
  opacity: 0;
  will-change: transform, width, height, opacity, border-radius;
  transform: translate3d(0, 0, 0);
}

.abt-morph-bridge img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  user-select: none;
  pointer-events: none;
}

/* Blur-fade intro state for hero + video: start blurred/translated, then
   an inline GSAP timeline clears on DOM ready. */
.abt-hero-heading.is-reveal-prep,
.abt-hero-body.is-reveal-prep,
.abt-video-wrap.is-reveal-prep {
  opacity: 0;
  filter: blur(12px);
  transform: translateY(12px);
}

/* --- Numbers section --- */
.abt-numbers {
  /* Extra bottom padding — the last stat row sits close to its own bottom
     edge, so visible gap to .abt-founders lands at the target.
     Vertical padding +40% vs. previous baseline. */
  padding: 98px var(--content-padding) 112px;
}

.abt-numbers-content {
  display: flex;
  /* Heading sits at the TOP of the column, not vertically centred against
     the tall stats list. */
  align-items: flex-start;
  gap: 24px;
  max-width: 1530px;
  margin: 0 auto;
}

.abt-numbers-heading-col {
  flex: 1;
}

.abt-numbers-heading {
  font-family: var(--font-primary);
  font-weight: 400;
  font-size: 56px;
  line-height: 68px;
  letter-spacing: -3.36px;
  color: var(--color-dark);
  margin: 0;
}

.abt-numbers-stats-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding-bottom: 1px;
}

.abt-stat-row {
  display: flex;
  align-items: center;
  gap: 24px;
  height: 100px;
  border-top: 1px solid #e9e9e9;
  border-bottom: 1px solid #e9e9e9;
  margin-bottom: -1px;
}

.abt-stat-number {
  font-family: var(--font-primary);
  font-weight: 400;
  font-size: 40px;
  line-height: 68px;
  letter-spacing: -2.4px;
  color: var(--color-dark);
  width: 200px;
  flex-shrink: 0;
}

.abt-stat-label {
  font-family: var(--font-primary);
  font-weight: 400;
  font-size: 16px;
  line-height: 28px;
  letter-spacing: -0.32px;
  color: var(--color-muted);
  text-align: right;
  flex: 1;
}

/* --- Team feature (photo + text) --- */
.abt-team-feature {
  /* Stays inside the main content grid — no full-width bleed.
     Vertical padding bumped +40% (60 → 84). */
  padding: 84px var(--content-padding);
}

.abt-team-feature-content {
  display: flex;
  gap: 86px;
  align-items: stretch;
  /* Cap at Figma content width so the section feels condensed on ultra-wide
     monitors instead of spreading text across long line lengths. No fixed
     height — the row grows with whichever column is taller. */
  max-width: 1530px;
  margin: 0 auto;
  width: 100%;
}

.abt-team-feature-image {
  /* Fixed aspect-ratio Figma frame; shrinks proportionally at narrow
     desktop widths instead of squashing the text column. */
  flex: 0 1 907px;
  min-width: 0;
  aspect-ratio: 907 / 770;
  border-radius: 16px;
  overflow: hidden;
  background: #e3e3e5;
}

.abt-team-feature-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.abt-team-feature-text {
  flex: 1 1 0;
  /* min-width: 0 lets the column shrink to its flex share instead of
     being forced wide by its own contents. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 40px;
  padding-top: 40px;
}

.abt-team-feature-quote {
  font-family: var(--font-primary);
  font-weight: 400;
  font-size: 26px;
  line-height: 42px;
  letter-spacing: -1.56px;
  color: var(--color-dark);
  margin: 0;
}

.abt-team-feature-bottom {
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.abt-team-feature-body {
  font-family: var(--font-primary);
  font-weight: 400;
  font-size: 16px;
  line-height: 28px;
  letter-spacing: -0.32px;
  color: var(--color-muted);
  margin: 0;
}

.abt-pill-link {
  display: inline-flex;
  align-items: center;
  padding: 2px;
  border-radius: 100px;
  background: var(--color-pill-bg);
  backdrop-filter: blur(25px);
  -webkit-backdrop-filter: blur(25px);
  text-decoration: none;
  align-self: flex-start;
}

.abt-pill-link-text {
  font-family: var(--font-primary);
  font-weight: 560;
  font-size: 14px;
  letter-spacing: -0.28px;
  color: var(--color-dark);
  padding: 14px 22px;
  border-radius: 100px;
  line-height: normal;
}

/* --- Selected case studies (Figma 1311:398) --- */

/* The rows and cards ARE the homepage showcase slots (.projects-row--*,
   .project-card--*), reused without a single override — see the comment on the
   section in about.html for why even the row gaps come out right for free.
   What is left is this section's own padding and its content column.

   The padding is Figma's own 80 / 160 rather than the About page's +40%
   rhythm: the neighbours already carry 112 (numbers, bottom) and 84 (founders,
   top), so uplifting this section on both sides as well would open the two
   widest gaps on the page around it. */
.abt-cases {
  padding: 80px var(--content-padding) 160px;
}

.abt-cases-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
}

/* --- Founders section --- */
.abt-founders {
  /* Vertical padding +40% (60 → 84). */
  padding: 84px var(--content-padding);
}

/* The Labs call-out (Figma 526:1094) was cut from the About design in August
   2026, so its block lived here. The homepage teaser that borrowed its shape
   is .home-about and stands on its own rules. */

.abt-founders-content {
  display: flex;
  gap: 24px;
  max-width: 1530px;
  margin: 0 auto;
  width: 100%;
}

.abt-founder-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.abt-founder-photo {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 16px;
  overflow: hidden;
  background: #e3e3e5;
}

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

.abt-founder-info {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.abt-founder-name {
  font-family: var(--font-primary);
  font-weight: 500;
  font-size: 24px;
  line-height: 40px;
  letter-spacing: -1.44px;
  color: var(--color-dark);
  margin: 0;
}

.abt-founder-bio {
  font-family: var(--font-primary);
  font-weight: 400;
  font-size: 16px;
  line-height: 28px;
  letter-spacing: -0.32px;
  color: var(--color-muted);
  margin: 0;
}

.abt-founder-socials {
  display: flex;
  align-items: center;
  gap: 12px;
}

.abt-founder-social-link {
  font-family: var(--font-primary);
  font-weight: 400;
  font-size: 16px;
  line-height: 28px;
  letter-spacing: -0.32px;
  color: var(--color-black);
  text-decoration: none;
}

.abt-founder-social-link:hover {
  opacity: 0.7;
}

.abt-founder-dot {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--color-black);
  flex-shrink: 0;
}

/* ==================== HIRING PAGE (hr-*) ==================== */

/* Hero */
.hr-hero {
  padding: 200px var(--content-padding) 80px;
}

.hr-hero-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.hr-hero-text {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.hr-hero-heading {
  font-family: var(--font-primary);
  font-size: 72px;
  font-weight: 400;
  color: var(--color-dark);
  letter-spacing: -4.32px;
  line-height: 80px;
}

.hr-hero-body {
  font-family: var(--font-primary);
  font-size: 1.25rem;
  font-weight: 400;
  line-height: 1.75;
  max-width: 640px;
}

/* Why Microdose */
.hr-why {
  padding: 80px var(--content-padding);
}

.hr-why-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
}

.hr-why-header {
  display: flex;
  flex-direction: column;
  gap: 24px;
  margin-bottom: 56px;
}

.hr-why-header .section-heading {
  margin-bottom: 0;
}

.hr-why-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 24px;
}

.hr-why-card {
  border-radius: var(--radius-lg);
  border: 0.5px solid rgba(116, 84, 84, 0.15);
  background: linear-gradient(to bottom,
    rgba(249, 245, 249, 0.25),
    rgba(245, 245, 251, 0.25) 50%,
    rgba(131, 131, 131, 0.15));
  box-shadow: var(--shadow-card);
  overflow: hidden;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.hr-why-card:hover {
  transform: translateY(-2px);
  box-shadow: 0px 2px 8px rgba(14, 15, 43, 0.1), 0px 43px 80px rgba(66, 39, 92, 0.04);
}

.hr-why-card-inner {
  padding: 40px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.hr-why-number {
  font-family: 'Instrument Serif', serif;
  font-style: italic;
  font-size: 14px;
  color: #2d4869;
}

.hr-why-title {
  font-family: var(--font-primary);
  font-size: 24px;
  font-weight: 500;
  color: var(--color-dark);
  letter-spacing: -1.44px;
  line-height: 32px;
}

.hr-why-description {
  font-family: var(--font-primary);
  font-size: 16px;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: -0.32px;
  line-height: 28px;
}

/* Open Roles */
.hr-roles {
  padding: 80px var(--content-padding);
}

.hr-roles-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
}

.hr-roles-header {
  display: flex;
  flex-direction: column;
  gap: 24px;
  margin-bottom: 56px;
}

.hr-roles-header .section-heading {
  margin-bottom: 0;
}

.hr-roles-list {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.hr-role-card {
  display: flex;
  align-items: center;
  gap: 40px;
  padding: 32px 0;
  border-top: 1px solid #e9e9e9;
  text-decoration: none;
  color: inherit;
  transition: opacity 0.25s ease;
}

.hr-role-card:last-child {
  border-bottom: 1px solid #e9e9e9;
}

.hr-role-card:hover {
  opacity: 0.7;
}

.hr-role-card:hover .hr-role-arrow {
  transform: translateX(4px) translateY(-4px);
}

.hr-role-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.hr-role-title {
  font-family: var(--font-primary);
  font-size: 24px;
  font-weight: 500;
  color: var(--color-dark);
  letter-spacing: -1.44px;
  line-height: 32px;
}

.hr-role-description {
  font-family: var(--font-primary);
  font-size: 16px;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: -0.32px;
  line-height: 28px;
  max-width: 600px;
}

.hr-role-meta {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}

.hr-role-arrow {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  color: var(--color-dark);
  transition: transform 0.3s ease;
}

/* Culture / Team */
.hr-culture {
  padding: 80px var(--content-padding);
}

.hr-culture-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 56px;
}

.hr-culture-image {
  width: 100%;
  height: 500px;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: #ebeced;
}

.hr-culture-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hr-culture-text {
  display: flex;
  flex-direction: column;
  gap: 24px;
  max-width: 800px;
}

.hr-culture-heading {
  font-family: var(--font-primary);
  font-size: 40px;
  font-weight: 400;
  color: var(--color-dark);
  letter-spacing: -2.4px;
  line-height: 48px;
}

.hr-culture-body {
  font-family: var(--font-primary);
  font-size: 18px;
  font-weight: 400;
  color: var(--color-dark);
  letter-spacing: -0.36px;
  line-height: 32px;
}

.hr-culture-body-muted {
  color: var(--color-muted);
}

/* Apply CTA */
.hr-apply {
  padding: 80px var(--content-padding);
  background: var(--color-bg-dark);
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  margin-top: 40px;
}

.hr-apply-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 80px;
}

.hr-apply-text {
  flex: 1;
  max-width: 560px;
}

.hr-apply-heading {
  font-family: var(--font-primary);
  font-size: 56px;
  font-weight: 400;
  color: var(--color-white);
  letter-spacing: -3.36px;
  line-height: 68px;
  margin-bottom: 16px;
}

.hr-apply-subheading {
  font-family: var(--font-primary);
  font-size: 18px;
  font-weight: 400;
  color: var(--color-white-50);
  letter-spacing: -0.36px;
  line-height: 32px;
}

.hr-apply-actions {
  display: flex;
  flex-direction: column;
  gap: 16px;
  align-items: flex-start;
  flex-shrink: 0;
  padding-top: 12px;
}

.hr-apply-note {
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.4);
  letter-spacing: -0.28px;
  line-height: 24px;
}


/* ==================== CASE STUDY DETAIL PAGE (csd-*) ==================== */

/* Hero */
.csd-hero {
  padding: 0 var(--content-padding);
  padding-top: 160px;
  padding-bottom: 56px;
  position: relative;
  overflow: hidden;
  background: transparent;
}

.csd-hero-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 32px;
  align-items: flex-start;
}

.csd-hero-tags {
  display: flex;
  gap: 16px;
  align-items: center;
}

.csd-hero-heading {
  font-family: var(--font-primary);
  font-size: 72px;
  font-weight: 400;
  color: var(--color-black);
  line-height: 80px;
  letter-spacing: -4.32px;
  max-width: 912px;
}

/* Page container.
   Two rhythms, the same pair Figma 951:648 lays out: 40px between images and
   96px at a section boundary. Every media block used to sit on one 120px gap,
   so a bento group whose own images were 40px apart was 120px from the very
   next image. One grid read as two spacings, which is the inconsistency
   Kamil flagged across all eleven studies. Now the base gap IS the in-grid 40,
   and only a boundary widens it.
   The 767 block below states the same shape at 24/64, and this rule is the
   desktop and tablet half of it, not a second system. */
.csd-page {
  display: flex;
  flex-direction: column;
  gap: 40px;
  padding: 0 var(--content-padding);
  align-items: center;
  margin-bottom: 64px;
}

/* +56 on 40 = the 96px section boundary. Media is the enumerated set and
   everything else is prose: the meta row, the text sections, the quote and
   .csr. So a block type nobody has written yet defaults to the wide boundary
   rather than silently joining the image rhythm. Both halves require a
   preceding sibling, which is what keeps the first child flush with the hero.
   Selector shape and specificity mirror the 767 rule exactly, so that one
   still wins on phones purely by being later in the file. */
.csd-page > * + :not(.csd-key-visual, .csd-gallery, .csh-gallery, .csb-showcase, .csb-wide, .csb-tiles, .csb-duo, .csb-trio),
.csd-page > :not(.csd-key-visual, .csd-gallery, .csh-gallery, .csb-showcase, .csb-wide, .csb-tiles, .csb-duo, .csb-trio) + * {
  margin-top: 56px;
}

/* Key visual (full-width image) */
.csd-key-visual {
  width: 100%;
  max-width: var(--content-max-width);
}

/* Key visual: the image IS the hero. No light frame, no padding, no shadow —
 * so the morph from /case-studies card lands directly on the image itself
 * without a visible wrapper around it. */
.csd-image-full {
  width: 100%;
  /* 16:9, matching the Figma CS-HERO slot (1530 x 860.625) and the master
   * preview's own ratio, so the hero crops nothing. Was 1530/857, a ~4px
   * vertical crop the design does not have. */
  aspect-ratio: 16 / 9;
  border-radius: 16px;
  overflow: hidden;
  display: block;
  /* anchor for the .csb-hero-video overlay on video-hero pages */
  position: relative;
}

.csd-image-full img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 16px;
}

/* Text sections (2-column: title left + body right) */
.csd-section {
  width: 100%;
  max-width: 1200px;
}

.csd-section-inner {
  display: flex;
  gap: 180px;
  align-items: flex-start;
}

.csd-section-title {
  width: 400px;
  flex-shrink: 0;
}

.csd-heading {
  font-family: var(--font-primary);
  font-size: 32px;
  font-weight: 500;
  color: var(--color-dark);
  line-height: 40px;
  letter-spacing: -1.92px;
}

.csd-section-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 28px;
}

.csd-body-text {
  font-family: var(--font-primary);
  font-size: 16px;
  font-weight: 400;
  color: var(--color-muted);
  line-height: 28px;
  letter-spacing: -0.32px;
}

/* Prose on the media line. Figma 951:735 / 951:744 / 951:1211 (Treat, "Updated
 * by MP v2") moved the text sections off their own centred 1200 column and onto
 * the same 1530 line as every image: title still 400 on the left edge, body a
 * 680 measure hard against the right edge, 450 of air between them.
 *
 * space-between rather than `gap: 450px`, because the whitespace is the part
 * that should give. Between 1201 and 1919 the column is 100vw - 390, narrower
 * than 1530, and a fixed 450 gap would eat the measure instead — at a 1440
 * viewport the body would collapse to 200px. This way the gap closes to its
 * 80px floor first and the measure only shrinks after that.
 *
 * Opt-in: the other eleven case studies were built from their own frames and
 * still hold the 1200 column. Tablet and below is untouched — the shared rules
 * at 1200 (gap 80, title 280) and 767 (stacked) already handle it. */
.csd-section--wide {
  max-width: var(--content-max-width);
}

@media (min-width: 1201px) {
  .csd-section--wide .csd-section-inner {
    justify-content: space-between;
    gap: 80px;
  }

  .csd-section--wide .csd-section-body {
    flex: 0 1 680px;
  }
}

/* Image gallery (stacked images) */
.csd-gallery {
  width: 100%;
  max-width: 1200px;
  display: flex;
  flex-direction: column;
  gap: 40px;
}

/* Gallery image blocks — matched to the hero treatment: the image IS the
 * block. No light frame, no padding, no shadow. Consistent rounded corners
 * and an aspect ratio keeps the layout stable regardless of the actual
 * image's intrinsic ratio. */
.csd-image-block {
  width: 100%;
  aspect-ratio: 1200 / 900;
  border-radius: 16px;
  overflow: hidden;
  display: block;
}

.csd-image-block img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 16px;
  will-change: transform;
}

/* Opt-in 1:1 block. Some films are authored square; the default 1200/900 box
 * would object-fit: cover them and shave 12.5% off the top and bottom, which
 * cuts real footage whenever a scene runs edge to edge rather than sitting on
 * padding. Only applies where the class is set, so 4:3 blocks are unaffected. */
.csd-image-block--square {
  aspect-ratio: 1 / 1;
}

/* "See more our work" section */
.csd-more-work {
  width: 100%;
  max-width: var(--content-max-width);
  display: flex;
  flex-direction: column;
  gap: 24px;
  align-items: center;
}

.csd-more-heading {
  font-family: var(--font-primary);
  font-size: 32px;
  font-weight: 500;
  color: var(--color-dark);
  line-height: 40px;
  letter-spacing: -1.92px;
}

.csd-more-grid {
  display: flex;
  gap: 40px;
  width: 100%;
  max-width: 1200px;
}

.csd-more-grid .cs-card {
  flex: 1;
  min-width: 0;
}

.csd-more-grid .cs-card-image {
  height: 390px;
}

/* -- Custom project cursor (lazy follow + scale-in) --
 * backdrop-filter belongs on THIS base rule and must never move back onto
 * .is-visible. Toggling it is not a paint change, it is a layer-configuration
 * change: WebKit builds a backdrop layer plus a containment layer, moves the
 * running transform/opacity animations onto them, then tears it all down,
 * twice per hover. On Safari 26 that first teardown is destructive, and the
 * puck paints on hover 1 and never again, with a perfect DOM, a perfect
 * computed style and a perfect re-rendered screenshot. Same rule this
 * stylesheet already states at .mobile-nav and .cookie-banner-inner: never
 * toggle a backdrop-filter on the node carrying the animated transform.
 * The idle cost the old toggle bought is now paid by visibility, below. */
.custom-cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 6.25rem;
  height: 6.25rem;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 0.875rem 1.375rem;
  border-radius: 6.25rem;
  border: 1px solid var(--color-dark-border);
  background: rgba(98, 96, 112, 0.30);
  box-shadow: var(--shadow-card);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  pointer-events: none;
  z-index: 9999;
  /* The idle gate that replaces the toggle. visibility:hidden maps to
     CALayer.hidden, so the blur is not sampled while the puck is idle, not on
     mouse move, not on scroll. Unlike backdrop-filter it flips a property on a
     layer that already exists instead of restructuring the layer tree, which
     is the whole distinction this bug turns on. It also transitions discretely
     the way we need: visible the instant .is-visible lands, and held visible
     for the full 350ms fade-out. The three cursor scripts stop writing
     left/top while hidden too, so nothing invalidates the layer between
     hovers, strictly less per-frame work than the old toggle, which saved the
     blur but still paid 60 geometry commits a second, forever. */
  visibility: hidden;
  /* 0.01, not 0: a zero-scale layer has an empty transformed extent, which is
     what lets WebKit detach the backing store of a layer it keeps alive. One
     pixel at opacity 0 behind visibility:hidden is unobservable. */
  transform: translate(-50%, -50%) scale(0.01);
  transition: transform 0.35s cubic-bezier(0.43, 0.98, 0.87, 1),
              opacity 0.35s cubic-bezier(0.43, 0.98, 0.87, 1),
              visibility 0.35s cubic-bezier(0.43, 0.98, 0.87, 1);
  will-change: transform, opacity;
  opacity: 0;
}

.custom-cursor.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, -50%) scale(1);
}

/* No visibility here on purpose: the base rule's hidden value plus the
   transition above keeps the puck on screen for the whole fade-out. */
.custom-cursor.is-leaving {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.01);
}

/* The puck carries three glyphs and shows one. js/project-cursor.js injects all
   three; the two inline copies (index.html, case-studies.html) still ship only
   the diagonal one, which is why the default state hides the pair rather than
   showing the open arrow — an unclassed lone <svg> is unaffected by any of
   this. Swapping display, not opacity, keeps the flex box centred on whichever
   glyph is live and costs no extra compositing on a layer that must stay cheap
   (see the backdrop-filter note above). */
.custom-cursor .cc-glyph-prev,
.custom-cursor .cc-glyph-next {
  display: none;
}

.custom-cursor--prev .cc-glyph-open,
.custom-cursor--next .cc-glyph-open {
  display: none;
}

.custom-cursor--prev .cc-glyph-prev,
.custom-cursor--next .cc-glyph-next {
  display: block;
}

/* Over a coming-soon card the arrow is replaced by a label — the arrow is the
   only "read more" affordance these cards ever had, so it has to say something
   rather than simply disappear. */
.custom-cursor-label {
  display: none;
  font-family: var(--font-primary);
  font-size: 13px;
  font-weight: 500;
  color: var(--color-white);
  letter-spacing: -0.26px;
  line-height: 1;
  white-space: nowrap;
}

.custom-cursor--soon {
  width: auto;
  height: 3.25rem;
  padding: 0 1.5rem;
  /* .custom-cursor's --shadow-card barely reads on the 100px arrow circle but
     is obvious stretched across a 127px pill, where it looks like the label
     gained a drop shadow. Off for this variant. */
  box-shadow: none;
}

.custom-cursor--soon svg {
  display: none;
}

.custom-cursor--soon .custom-cursor-label {
  display: block;
}

/* ---------- SERVICES ---------- */
.services {
  padding: 0;
}

.services-card {
  background-color: var(--color-bg-dark);
  border-radius: 0;
  padding: 140px 155px;
  display: flex;
  gap: 60px;
  min-height: 100vh;
  width: 100%;
  align-items: stretch;
  box-sizing: border-box;
}

.services-left {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-width: 0;
}

.services-header {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.services-badge-wrapper {
  display: flex;
}

.services-heading {
  font-size: 64px;
  font-weight: 400;
  color: var(--color-white);
  letter-spacing: -3.84px;
  line-height: 80px;
  max-width: 900px;
}

.services-slides {
  position: relative;
  flex: 1;
  display: flex;
  align-items: flex-end;
  min-height: 320px;
}

.service-slide {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
  opacity: 0;
  pointer-events: none;
}

.service-slide--active {
  opacity: 1;
  pointer-events: auto;
}

.service-number {
  font-family: var(--font-accent), var(--font-primary);
  font-style: italic;
  font-size: 14px;
  color: #a7a7a7;
  line-height: 1.4;
}

.service-title {
  font-size: 26px;
  font-weight: 400;
  color: var(--color-white);
  letter-spacing: -1.56px;
  line-height: 32px;
}

.service-title .char {
  display: inline-block;
}

.service-description {
  font-size: 16px;
  font-weight: 400;
  color: var(--color-white);
  opacity: 0.4;
  letter-spacing: -0.32px;
  line-height: 24px;
  max-width: 516px;
}

.service-checklist {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px 24px;
  margin-top: 8px;
}

.service-checklist-item {
  display: flex;
  align-items: center;
  font-size: 16px;
  font-weight: 400;
  color: var(--color-white);
  letter-spacing: -0.48px;
  line-height: 24px;
}

.check-icon {
  margin-right: 12px;
  color: var(--color-white);
  font-size: 14px;
  opacity: 0.6;
}

.service-slide .btn-dark-pill {
  margin-top: 16px;
  align-self: flex-start;
}

.services-right {
  flex: 1;
  min-width: 0;
}

.service-video-wrapper {
  width: 100%;
  height: 100%;
  background-color: var(--color-white);
  border-radius: 16px;
  overflow: hidden;
  position: relative;
}

.service-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: none;
}

.service-video--active {
  opacity: 1;
}

/* ---------- TESTIMONIAL ---------- */
/* Lives inside the selected-projects showcase (.projects-testimonial). */
.testimonial-quote {
  font-size: 21px;
  font-weight: 400;
  color: var(--color-dark);
  letter-spacing: -1.26px;
  line-height: 32px;
}

.testimonial-author {
  display: flex;
  align-items: center;
  gap: 20px;
}

.testimonial-avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
}

.testimonial-author-info {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.testimonial-name {
  font-size: 14px;
  font-weight: 560;
  color: var(--color-dark);
  letter-spacing: -0.28px;
  line-height: normal;
}

.testimonial-role {
  font-size: 14px;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: -0.28px;
  line-height: 28px;
}

/* ---------- HOMEPAGE ABOUT TEASER ---------- */
/* Centred copy flanked by two founder photos at opposite angles (Figma 996:701).
   The About page's .abt-labs callout shared this shape until August 2026, so
   these rules stand alone. Bottom padding absorbs the 20% cut to the gap. */
.home-about {
  padding: 180px 40px 136px;
  overflow: hidden;
}

.home-about-content {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 100px;
  margin: 0 auto;
}

.home-about-deco {
  position: relative;
  flex-shrink: 0;
  width: 628px;
  height: 470px;
  overflow: visible;
  margin: 0;
  will-change: transform;
}

/* Each file is the exact 2x export of its individual Figma photo layer,
   including the final angle, crop and rounded corners. Centring the 612px-tall
   render inside the 470px layout row preserves the section's 930px height. */
.home-about-deco img {
  position: absolute;
  top: 50%;
  left: 0;
  display: block;
  width: 100%;
  height: auto;
  max-width: none;
  transform: translateY(-50%);
}

.home-about-text {
  flex-shrink: 0;
  width: 464px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 40px;
  text-align: center;
}

.home-about-heading {
  font-family: var(--font-primary);
  font-size: 26px;
  font-weight: 400;
  line-height: 42px;
  letter-spacing: -1.56px;
  color: var(--color-dark);
  margin: 0;
}

.home-about-body {
  font-family: var(--font-primary);
  font-size: 16px;
  font-weight: 400;
  line-height: 28px;
  letter-spacing: -0.32px;
  color: var(--color-muted);
  width: 390px;
  max-width: 100%;
  margin: 0;
}

.home-about .abt-pill-link {
  align-self: center;
}

/* ---------- LOGOS / SOCIAL PROOF ---------- */
.logos-section {
  padding: 120px var(--content-padding) 68px;
}

.logos-content {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 40px;
}

.logos-text {
  font-size: 16px;
  font-weight: 400;
  letter-spacing: -0.32px;
  line-height: 28px;
  text-align: center;
}

.logos-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 40px 80px;
  max-width: 1000px;
}

.logo-img {
  object-fit: contain;
  flex-shrink: 0;
  height: auto;
  max-height: 22px;
  width: auto;
  opacity: 0.5;
}

/* The phone stand-in for .logos-grid: the hero's cycling row, reusing its
   classes so the two cannot drift apart. Off by default at every width, and
   switched on only inside the mobile block below, and only once
   js/logo-carousel.js has flipped .logos-cycling onto the section. Slots that
   nothing ever fills would otherwise be a 28px hole where the client wall
   used to be. */
.logo-carousel.logos-cycle {
  display: none;
}

/* ---------- CTA / CONTACT ---------- */
.cta {
  position: relative;
  padding: 40px var(--content-padding) 0;
}

.cta-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
}

.cta-gradient {
  display: none;
}

.cta-content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  max-width: 862px;
  margin: 0 auto;
  text-align: center;
}

.cta-text {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.cta-heading {
  font-size: 72px;
  font-weight: 400;
  color: var(--color-black);
  letter-spacing: -4.32px;
  line-height: 80px;
}

.cta-subheading {
  font-size: 18px;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: -0.36px;
  line-height: 28px;
}

.cta-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

.cta-note {
  font-size: 12px;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: -0.24px;
  line-height: 28px;
  text-align: center;
  max-width: 416px;
}

/* ---------- FOOTER ---------- */
.footer {
  position: relative;
  padding: 0 var(--content-padding);
  overflow: hidden;
}

.footer-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  width: 100%;
}

/* Row 1: Explore AI Summary + email */
.footer-row--ai {
  padding-top: 40px;
  padding-bottom: 40px;
  align-items: flex-end;
}

.footer-ai {
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 626px;
  max-width: 100%;
}

.footer-ai-heading {
  font-size: 16px;
  font-weight: 400;
  color: var(--color-black);
  letter-spacing: -0.32px;
  line-height: 28px;
}

.footer-ai-icons {
  display: flex;
  flex-wrap: wrap;
  gap: 32px;
  align-items: center;
}

.footer-ai-icon-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  transition: opacity 0.2s;
}

.footer-ai-icon-link:hover {
  opacity: 0.7;
}

.footer-ai-icon {
  width: 20px;
  height: 20px;
  display: block;
  object-fit: contain;
}

/* Row 2: Brand + primary social row */
.footer-row--brand {
  padding-bottom: 32px;
  padding-top: 40px;
}

.footer-brand {
  display: flex;
  flex-direction: column;
  width: 626px;
  max-width: 100%;
}

.footer-brand-name {
  font-size: 16px;
  font-weight: 400;
  color: var(--color-black);
  letter-spacing: -0.32px;
  line-height: 28px;
}

.footer-brand-tagline {
  font-size: 16px;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: -0.32px;
  line-height: 28px;
}

.footer-email {
  font-size: 16px;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: -0.32px;
  line-height: 28px;
  text-align: right;
  transition: color 0.2s;
}

.footer-email:hover {
  color: var(--color-black);
}

.footer-social {
  display: flex;
  align-items: center;
  gap: 12px;
}

.footer-social-link {
  font-size: 16px;
  font-weight: 400;
  color: var(--color-black);
  letter-spacing: -0.32px;
  line-height: 28px;
  transition: opacity 0.2s;
  white-space: nowrap;
}

.footer-social-link:hover {
  opacity: 0.7;
}

.footer-dot {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background-color: var(--color-muted);
  flex-shrink: 0;
}

/* Row 3: Legal row (bordered). Just two blocks now — links on the left,
   copyright on the right — so a plain space-between row is enough. */
.footer-row--legal {
  border-top: 1px solid var(--color-footer-border);
  padding: 32px 0;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
}

.footer-legal {
  display: flex;
  align-items: center;
  gap: 40px;
  flex-shrink: 0;
}

.footer-legal-link {
  font-size: 14px;
  font-weight: 400;
  color: var(--color-dark);
  letter-spacing: -0.28px;
  line-height: normal;
  transition: opacity 0.2s;
  white-space: nowrap;
}

.footer-legal-link:hover {
  opacity: 0.7;
}

.footer-copyright {
  font-size: 14px;
  font-weight: 400;
  letter-spacing: -0.42px;
  line-height: 28px;
  text-align: right;
  white-space: nowrap;
}

/* Row 4: Giant wordmark particle effect, cut off at bottom.
   Height = 40px gap (matches GAP_ABOVE in microdose-wordmark.js) + the
   visible portion of the wordmark; the bottom of the wordmark bleeds below
   the container and is clipped by overflow:hidden.
   Total height reduced from 130 → 106 to pull the wordmark 24px closer to
   the rest of the footer and keep the bottom flush with the page edge. */
.footer-wordmark {
  position: relative;
  width: 100vw;
  left: 50%;
  transform: translateX(-50%);
  height: 106px;
  overflow: hidden;
  pointer-events: none;
}

.footer-wordmark-canvas {
  display: block;
  width: 100%;
  height: 100%;
  /* 7% — subtle solid-looking wordmark at rest; the mouse-repulsion
     interaction reveals it's actually particles when you hover. */
  opacity: 0.07;
  pointer-events: auto;
}

/* ============================================
   CAL.COM BOOKING MODAL
   ============================================ */

body.modal-open {
  overflow: hidden;
}

.cal-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease;
}

.cal-modal.is-open {
  opacity: 1;
  visibility: visible;
}

.cal-modal-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.25);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.cal-modal-card {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border-radius: 1.3125rem;
  border: 1px solid rgba(255, 255, 255, 0.50);
  background: rgba(255, 255, 255, 0.90);
  backdrop-filter: blur(25px);
  -webkit-backdrop-filter: blur(25px);
  display: flex;
  flex-direction: column;
  transform: scale(0.96) translateY(10px);
  transition: transform 0.35s cubic-bezier(0.43, 0.98, 0.87, 1);
}

.cal-modal.is-open .cal-modal-card {
  transform: scale(1) translateY(0);
}

.cal-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 24px 32px;
  flex-shrink: 0;
}

.cal-modal-logo {
  font-family: var(--font-primary);
  font-size: 16px;
  letter-spacing: -0.4px;
  user-select: none;
}

.cal-modal-logo-bold {
  font-weight: 600;
  color: var(--color-black);
}

.cal-modal-logo-light {
  font-weight: 300;
  color: var(--color-black);
}

.cal-modal-close {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.1);
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--color-black);
  transition: background 0.2s ease, border-color 0.2s ease;
}

.cal-modal-close:hover {
  background: rgba(0, 0, 0, 0.05);
  border-color: rgba(0, 0, 0, 0.2);
}

.cal-modal-close svg {
  width: 16px;
  height: 16px;
}

.cal-modal-body {
  padding: 0 32px 0;
  text-align: center;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.cal-modal-heading {
  font-family: var(--font-primary);
  font-size: 42px;
  font-weight: 400;
  color: var(--color-dark);
  letter-spacing: -2.5px;
  line-height: 1.15;
  margin: 0 0 12px;
  flex-shrink: 0;
}

.cal-modal-subheading {
  font-family: var(--font-primary);
  font-size: 15px;
  font-weight: 400;
  color: var(--color-muted);
  line-height: 1.5;
  margin: 0 0 32px;
}

.cal-modal-embed {
  flex: 1;
  min-height: 0;
  border-radius: 12px;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}

/* Hide cal.com default branding/footer inside embed */
.cal-modal-embed iframe {
  border: none;
}

.cal-modal-footer {
  padding: 20px 32px;
  text-align: center;
  flex-shrink: 0;
  border-top: 1px solid rgba(0, 0, 0, 0.06);
}

.cal-modal-footer a {
  font-family: var(--font-primary);
  font-size: 13px;
  font-weight: 400;
  color: var(--color-muted);
  text-decoration: none;
  transition: color 0.2s ease;
}

.cal-modal-footer a:hover {
  color: var(--color-dark);
}

.cal-modal-footer-email {
  margin-bottom: 8px;
}

.cal-modal-social {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
}

.cal-modal-social-sep {
  color: var(--color-muted);
  opacity: 0.4;
  font-size: 13px;
}

/* ============================================
   LABS — DRAGGABLE GALLERY
   ============================================ */

.labs-gallery {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  background: transparent;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
  z-index: 1;
}

.labs-gallery.is-dragging {
  cursor: grabbing;
}


.labs-grid {
  position: absolute;
  left: 0;
  top: 0;
  will-change: transform;
  z-index: 1;
}


.labs-card {
  position: absolute;
  background: #fff;
  padding: 2px;
  border-radius: 16px;
  overflow: hidden;
  box-shadow:
    0 0.5px 1px 0 var(--shadow-color-near),
    0 21.5px 40px 0 var(--shadow-color-far);
  cursor: pointer;
  transition: transform 0.6s cubic-bezier(0.25, 0.1, 0.25, 1),
              box-shadow 0.6s cubic-bezier(0.25, 0.1, 0.25, 1);
}

.labs-card:hover {
  box-shadow:
    0 2px 4px 0 rgba(14, 15, 43, 0.1),
    0 30px 60px 0 rgba(66, 39, 92, 0.06);
}

/* Gentle focus effect: scale is applied to `.labs-card-inner`
 * (not the outer card). Why: the outer .labs-card's `transform` is driven
 * by JS for the hover tilt, so interpolating a CSS `transform: scale(...)`
 * or a standalone `scale` on it fights the JS and snaps. The inner is
 * completely clear of JS transforms, so CSS can transition it smoothly.
 *
 * Uses `.labs-grid:hover:has(.labs-card:hover)` so the effect only kicks
 * in when a card is actually under the cursor (not just empty grid space),
 * and stays on through moves between cards (the grid itself is still
 * hovered) — so no reset-then-reapply flicker. */
.labs-card-inner {
  transform: scale(1);
  transform-origin: center center;
}

/* Everything the pointer tilt and the sibling dim need, and nothing a touch
 * device can use, behind one query. This is the iOS crash fix.
 *
 * `will-change` and `preserve-3d` each promote every card to its own permanent
 * compositing layer, and updateVisibleCells() keeps 48 to 77 of them alive at
 * once on a phone. The grid above them animates scale() on every frame of a
 * pinch, and WebKit re-rasterizes a promoted layer whenever an ancestor's
 * scale changes, so at ZOOM_MAX one card's backing store is 400x250 CSS px x2
 * (grid scale) x3 (dpr) = 14MB. Times 48 cards, times two promoted elements
 * each, that is over a gigabyte of tiles and iOS Safari kills the tab.
 * Unpromoted, the cards paint into the grid's single tiled layer, which WebKit
 * rasterizes per visible tile instead.
 *
 * The transition and the :has() rule are in here for the same reason, not just
 * for tidiness: iOS synthesises a :hover that sticks to the last-tapped
 * element, so one tap could satisfy `:has(.labs-card:hover)` and start a 1.4s
 * accelerated transform on all 62 other cards, promoting every one of them and
 * handing back the exact layer explosion this query exists to prevent. */
@media (hover: hover) and (pointer: fine) {
  .labs-card {
    will-change: transform;
    transform-style: preserve-3d;
  }

  .labs-card-inner {
    will-change: transform;
    transition: transform 1.4s cubic-bezier(0.22, 1, 0.36, 1);
  }

  .labs-grid:hover:has(.labs-card:hover) .labs-card:not(:hover) .labs-card-inner {
    transform: scale(0.97);
  }
}

/* 14px inner over the card's 16px outer and 2px white pad is the design's own
   card recipe (Labs frame 242:14849 -> "Frame 1171287945" 16/2px -> child "2"
   14px), where the image rectangle inside it is radius 0. This container is
   therefore the ONLY radius in play, and the shipped image files are true
   rectangles: scripts/build-labs-images.mjs strips the 16px radius Figma bakes
   into a frame export. Two radii on one image used to mean the baked arc peeked
   out of the lightbox on wide displays, where the CSS clip is proportionally
   smaller than the arc. */
.labs-card-inner {
  width: 100%;
  height: 100%;
  border-radius: 14px;
  overflow: hidden;
  background: #e8e8ec;
  /* The blur-up placeholder, set per card as an inline background-image from the
     24px WebP in js/labs-items.js. #e8e8ec stays as the ground under it. */
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.labs-card-inner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
  user-select: none;
  /* Hidden until it has actually decoded, so the placeholder shows through
     instead of a half-painted image. createCard() adds .is-loaded on load AND
     on error, so a rung that 404s still reveals rather than leaving a blank. */
  opacity: 0;
}

.labs-card-inner img.is-loaded {
  opacity: 1;
}

/* Only a genuinely first load crossfades. A card remounting from cache gets
   .is-loaded with no transition at all, which is not just a flicker fix: cards
   remount continuously while dragging, and 40-70 concurrent opacity
   transitions on a canvas that is being scaled is the same layer explosion
   that used to jetsam-kill iOS Safari on this page. */
.labs-card-inner img.will-fade {
  transition: opacity 0.26s ease-out;
}

/* The crossfade is the only motion the blur-up adds, and it is opt-out like the
   rest of the site's motion. Without the transition the image simply appears
   the instant it decodes, which is the behaviour reduced-motion asks for and
   loses nothing: the placeholder has already been showing the shape. */
@media (prefers-reduced-motion: reduce) {
  .labs-card-inner img.will-fade {
    transition: none;
  }
}

.labs-gallery.is-dragging .labs-card {
  cursor: grabbing;
}

.labs-watermark {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-primary);
  font-size: 144px;
  font-weight: 400;
  letter-spacing: -8.64px;
  line-height: 80px;
  color: var(--color-black);
  opacity: 0.05;
  pointer-events: none;
  z-index: 0;
  white-space: nowrap;
}

.labs-vignette {
  display: none;
}

/* Lightbox modal */
.labs-lightbox {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 100;
  display: none;
  pointer-events: none;
}

.labs-lightbox.is-open {
  display: block;
  pointer-events: auto;
  /* The open lightbox covers the gallery, so it has to refuse the browser's
     native pinch the same way .labs-gallery does. Without this a two-finger
     gesture on the overlay zooms the whole document instead of doing nothing,
     and the page is left scaled with no way back. */
  touch-action: none;
}

/* Frosted glass panel — fades in with delay */
.labs-lightbox-panel {
  position: absolute;
  top: 6px;
  left: 6px;
  right: 6px;
  bottom: 6px;
  border-radius: 1.3125rem;
  border: 1px solid rgba(255, 255, 255, 0.50);
  background: rgba(255, 255, 255, 0.90);
  backdrop-filter: blur(25px);
  -webkit-backdrop-filter: blur(25px);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.5s cubic-bezier(0.25, 0.1, 0.25, 1) 0.1s;
  z-index: 1;
}

.labs-lightbox.is-visible .labs-lightbox-panel {
  opacity: 1;
}

/* Image wrap — positioned absolutely, animated via JS */
.labs-lightbox-image-wrap {
  position: fixed;
  z-index: 2;
  background: #fff;
  padding: 6px;
  border-radius: 32px;
  /* Three shadows, matching the Labs modal design frame (261:56568 ->
     "Frame 1171288153"). The middle one was missing, and it is the one doing
     most of the visible lifting: near and far alone are 0.08 and 0.02 alpha, so
     the opened image sat almost flat against the frosted panel. The colour has
     no token because nothing else on the site uses it. */
  box-shadow:
    0 1.4px 2.8px 0 var(--shadow-color-near),
    0 40px 60px 0 rgba(36, 36, 62, 0.05),
    0 60.24px 112px 0 var(--shadow-color-far);
  cursor: pointer;
  will-change: transform, width, height, top, left, border-radius, padding;
  transition: top 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              left 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              width 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              height 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              border-radius 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              padding 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              box-shadow 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              opacity 0.4s ease;
}

.labs-lightbox-image-inner {
  width: 100%;
  height: 100%;
  border-radius: 24px;
  overflow: hidden;
  background: #e8e8ec;
}

.labs-lightbox-image-inner img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  user-select: none;
}

/* Hide scroll indicator on labs page */
body.labs-page .scroll-indicator {
  display: none;
}

/* ============================================
   SCROLL INDICATOR
   ============================================ */

.scroll-indicator {
  position: fixed;
  right: 40px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 99;
  pointer-events: none;
  width: 48px;
  height: 272px;
}

.scroll-indicator-track {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.scroll-tick {
  height: 1px;
  background: rgba(107, 108, 113, 0.18);
  width: 24px;
  margin-left: auto;
  flex-shrink: 0;
  will-change: width, background-color;
}

.scroll-tick-major {
  height: 1px;
  background: rgba(107, 108, 113, 0.28);
  width: 40px;
  margin-left: auto;
  flex-shrink: 0;
  will-change: width, background-color;
}

/* ============================================
   RESPONSIVE - TABLET (max-width: 1200px)
   ============================================ */
@media (max-width: 1200px) {
  :root {
    --content-padding: 80px;
  }

  .hero-content {
    /* Symmetric side padding: the base rule leaves the right side open for
       the logo carousel, which is hidden at this width. */
    padding: 120px var(--content-padding) 40px;
  }

  .hero-heading {
    font-size: 52px;
    line-height: 64px;
    letter-spacing: -3.12px;
  }

  .hero-subtitle {
    font-size: 28px;
    line-height: 64px;
    letter-spacing: -1.68px;
  }

  .hero-bottom {
    flex-direction: column;
    align-items: flex-start;
    gap: 32px;
    padding-right: 0;
  }

  .hero-body {
    width: 100%;
  }

  .hero-heading br,
  .hero-body br {
    display: none;
  }

  .hero-cta-mobile {
    display: inline-flex;
  }

  .hero-bottom .logo-carousel {
    display: none;
  }

  .carousel-card {
    width: 500px;
    height: 380px;
  }

  .carousel-card-narrow {
    width: 180px;
  }

  .carousel-card-wide {
    width: 500px;
  }

  .section-heading {
    font-size: 40px;
    line-height: 48px;
  }

  .services-card {
    padding: 80px 64px;
  }

  .services-heading {
    font-size: 48px;
    line-height: 56px;
    letter-spacing: -2.88px;
  }

  .service-checklist {
    grid-template-columns: 1fr;
  }

  .cta-heading {
    font-size: 56px;
    line-height: 64px;
    letter-spacing: -3.36px;
  }

  .footer-top {
    flex-direction: column;
    align-items: flex-start;
    gap: 32px;
    min-height: auto;
  }

  .footer-info {
    width: 100%;
  }

  .scroll-indicator {
    right: 20px;
  }

  .legal-hero {
    padding-top: 160px;
  }

  .legal-hero-title {
    font-size: 52px;
    letter-spacing: -3.12px;
  }

  .legal-hero-subtitle {
    font-size: 28px;
  }

  .legal-content {
    padding: 80px 80px 80px 80px;
  }

  .legal-block {
    max-width: 100%;
  }

  .cs-hero {
    padding-top: 176px;
    padding-bottom: 120px;
  }

  .cs-hero-heading {
    font-size: 56px;
    line-height: 64px;
    letter-spacing: -3.36px;
  }

  .cs-showcase .projects-row--feature-narrow + .projects-row--pair {
    margin-top: 144px;
  }

  .cs-showcase .projects-row--pair + .projects-row--feature {
    margin-top: 112px;
  }

  /* Case Study Detail — Tablet */
  .csd-hero-heading {
    font-size: 56px;
    line-height: 64px;
    letter-spacing: -3.36px;
  }

  .csd-section-inner {
    gap: 80px;
  }

  .csd-section-title {
    width: 280px;
  }

  .csd-image-full,
  .csd-image-block {
    border-radius: 16px;
  }

  .csd-more-grid .cs-card-image {
    height: 300px;
  }

  /* About page, tablet: .cs-hero's own tablet step. The 64px bottom stays
     local for the same reason the desktop 80px does, it is the reel's. */
  .abt-hero {
    padding: 176px var(--content-padding) 64px;
  }

  .abt-hero-heading {
    font-size: 56px;
    line-height: 64px;
    letter-spacing: -3.36px;
  }

  /* The reel keeps its own aspect at every width now, so there is no fixed
     height to override here. Cropping it to a taller tablet box would cut
     photos out of the collage it animates. */
  .abt-video-section {
    padding: 40px var(--content-padding);
  }

  .abt-manifesto {
    padding: 80px var(--content-padding);
  }

  .abt-numbers-content {
    flex-direction: column;
    gap: 40px;
  }

  .abt-numbers-heading {
    font-size: 44px;
    line-height: 54px;
  }

  .abt-team-feature {
    /* Symmetrical tablet padding so the section sits inside the grid. */
    padding: 60px 80px;
  }

  .abt-team-feature-content {
    flex-direction: column;
    height: auto;
    gap: 40px;
  }

  /* `flex: none` is load-bearing: once the row flips to `flex-direction:
     column` the base `flex: 0 1 907px` becomes the *main size*, so flex-basis
     silently beats `height` and the box renders 907px tall, stretching the 3:2
     photo into a narrow vertical crop. */
  .abt-team-feature-image {
    flex: none;
    width: 100%;
    height: 500px;
  }

  .abt-cases {
    padding: 56px var(--content-padding) 104px;
  }

  .abt-founders {
    padding: 40px 80px;
  }

  /* About teaser — tablet: same treatment, keep the cards bleeding but let
     the copy have room. */
  .home-about {
    padding: 160px 40px 140px;
  }

  .home-about-content {
    gap: 100px;
  }

  .home-about-deco {
    width: 409px;
    height: 340px;
  }

  /* Hiring page — tablet */
  .hr-hero-heading {
    font-size: 56px;
    line-height: 64px;
    letter-spacing: -3.36px;
  }

  .hr-why-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 20px;
  }

  .hr-why-card-inner {
    padding: 32px;
  }

  .hr-role-card {
    flex-wrap: wrap;
    gap: 16px;
  }

  .hr-apply-content {
    flex-direction: column;
    gap: 40px;
  }

  .hr-apply-heading {
    font-size: 44px;
    line-height: 54px;
  }

  .hr-culture-image {
    height: 400px;
  }
}

/* ============================================
   TOUCH DEVICE PERFORMANCE OVERRIDES
   iOS Safari cannot efficiently handle backdrop-filter and filter: blur()
   on composited layers. Disable or reduce these on touch devices.
   ============================================ */
@media (hover: none) and (pointer: coarse) {
  /* Page background: replace live filter:blur(60px) with a CSS gradient (zero GPU cost) */
  .page-bg-gradient-wrap {
    filter: none;
    opacity: 1;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    transform: none;
    background: radial-gradient(ellipse 80% 50% at 50% 0%, rgba(180, 140, 210, 0.18) 0%, rgba(120, 100, 180, 0.10) 40%, transparent 70%);
  }

  /* FINAL-SECTION BACKGROUND, touch counterpart. Here the box is the whole
     viewport and the glow is painted into it, so the desktop rule's
     bottom/height geometry makes no sense — reset it and move the ellipse
     centre to the bottom edge instead. 50% -> 40% radius is the same 20%
     height reduction the desktop band gets. Repainting the gradient is fine
     because this flips once, while hidden, rather than per scroll frame. */
  .page-bg--bottom .page-bg-gradient-wrap {
    top: 0;
    bottom: auto;
    height: 100%;
    background: radial-gradient(ellipse 80% 40% at 50% 100%, rgba(180, 140, 210, 0.18) 0%, rgba(120, 100, 180, 0.10) 40%, transparent 70%);
  }

  .page-bg-gradient-wrap img,
  .page-bg-gradient {
    display: none;
  }

  /* Mobile nav: remove always-computing backdrop-filter (applied via JS only when open) */
  .mobile-nav {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: rgba(255, 255, 255, 0.92);
  }

  /* Undo the About page's 100px lift here, and ONLY here. Up in the desktop
     rules `.about-page .page-bg-gradient-wrap` is specificity (0,2,0) against
     this block's (0,1,0), and a media query adds nothing, so without this line
     the -200px wins on touch and drags the glow off the top of the screen. It
     has no business applying anyway: on touch the wrap is not a 643px band that
     can be raised, it is the whole viewport with the glow painted into it. */
  .about-page .page-bg-gradient-wrap {
    top: 0;
  }

  /* Both frosted cursors: remove the backdrop blur. Neither puck can ever be
     shown on a touch device (there is no hover to trigger it, and cursor:none
     hides nothing), but index.html and case-studies.html ship the markup in
     raw HTML, so the element exists. Its blur now lives on the base rule, and
     WebKit samples a backdrop-filter even through visibility:hidden, so this
     override, not the visibility gate, is what guarantees zero GPU cost here.
     It was a no-op before: `.drag-cursor` lost to `.drag-cursor.is-visible`. */
  .drag-cursor,
  .custom-cursor {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  .drag-cursor {
    background: rgba(98, 96, 112, 0.55);
  }

  /* Remove unnecessary will-change on static/low-priority elements */
  .page-bg {
    will-change: auto;
  }

  .carousel-track {
    will-change: auto;
  }

  .scroll-indicator,
  .scroll-indicator-track {
    will-change: auto;
  }

  /* Help iOS create clean compositing layers for pinned sections */
  .about,
  .more-dosing,
  .services {
    -webkit-transform: translateZ(0);
    transform: translateZ(0);
  }
}

/* ============================================
   RESPONSIVE - MOBILE (max-width: 767px)
   ============================================ */
@media (max-width: 767px) {
  :root {
    --content-padding: 24px;
  }

  .scroll-indicator {
    display: none;
  }

  /* Page background pattern: full opacity, and re-sized from the viewport.
     The 1270x1321 desktop box has no room at phone widths, so the motif reads
     as the top mark rather than a full-bleed field.
     - aspect-ratio holds the SVG's own 1270:1321; with
       preserveAspectRatio="none" the box is the only thing keeping it round
     - 50vh is the cap: the motif stops at the halfway line
     - 110vw caps the side overhang at ~4vw on very tall phones, where 50vh
       alone would take the box past 120vw. .page-bg clips what is left, and
       every stroke fades to #FAFAFA at its ends, so no mask is needed. */
  .page-bg-pattern {
    opacity: 1;
    top: 0;
    width: auto;
    height: min(50vh, 110vw);
    aspect-ratio: 1270 / 1321;
  }

  /* Nav */
  .nav-wrapper {
    padding-left: 24px;
    padding-right: 24px;
  }

  .nav-pill {
    display: none;
  }

  .nav-hamburger {
    display: flex;
  }

  /* Keep hamburger pill default (dark) colors on mobile — no theme switch */
  .nav--light .hamburger-line {
    background-color: var(--color-black);
  }

  .nav--light .nav-hamburger-pill {
    background: var(--color-pill-bg);
    border-color: rgba(21, 12, 64, 0.02);
  }

  .nav--light .nav-hamburger-pill-inner {
    background: white;
  }

  /* Hero */
  .hero-subtitle {
    font-size: 14px;
    line-height: normal;
    letter-spacing: -0.28px;
  }

  .hero-heading {
    font-size: 40px;
    line-height: 46px;
    letter-spacing: -2.4px;
  }

  .hero-titles {
    gap: 16px;
  }

  .hero-body {
    width: auto;
    max-width: 600px;
    font-size: 14px;
    letter-spacing: -0.28px;
  }

  .hero-bottom {
    padding-right: 0;
    gap: 24px;
  }

  .hero-content {
    padding: 60px 24px 40px;
    gap: 24px;
  }

  .hero-heading br,
  .hero-body br {
    display: none;
  }

  .hero-cta-mobile {
    display: inline-flex;
  }

  .hero-bottom .logo-carousel {
    display: none;
  }

  /* Carousel */
  .portfolio-carousel {
    cursor: auto;
  }

  .carousel-card {
    width: 494px;
    height: 340px;
  }

  .carousel-card-narrow {
    width: 159px;
  }

  .carousel-card-wide {
    width: 483px;
  }

  /* About */
  .about {
    padding: 80px 24px;
    overflow: visible; /* override desktop overflow:hidden — breaks ScrollTrigger pin on iOS Safari */
  }

  .about-text {
    margin-left: 0;
  }

  .about-paragraph {
    font-size: 1.25rem;
    line-height: 2.25rem;
    letter-spacing: -0.075rem;
  }

  .about-founders {
    margin-left: 0;
    gap: 40px;
  }

  /* Projects — the desktop offsets collapse into one full-width column. */
  .projects {
    padding: 0 24px 64px; /* 80 - 20%, matching the desktop reduction */
  }

  .section-heading {
    font-size: 32px;
    line-height: 40px;
    letter-spacing: -1.92px;
    margin-bottom: 32px;
  }

  .projects-header {
    margin-bottom: 40px;
  }

  .projects-row {
    display: block;
  }

  .projects-row--pair {
    width: 100%;
    margin-top: 40px;
  }

  .project-card--feature,
  .project-card--landscape,
  .project-card--portrait {
    width: 100%;
  }

  .project-card--portrait {
    padding-top: 40px;
  }

  /* Same specificity as the desktop rule, so it actually wins the width back. */
  .projects-row--feature-narrow .project-card--feature {
    width: 100%;
    margin-top: 56px;
  }

  .project-image--feature,
  .project-image--landscape {
    aspect-ratio: 16 / 9;
  }

  .project-image--portrait {
    aspect-ratio: 4 / 5;
  }

  /* Tag drops under the copy — there is no room for it beside a wrapped title. */
  .project-info {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
  }

  .project-title {
    font-size: 20px;
    letter-spacing: -1.2px;
    line-height: 32px;
  }

  .projects-see-all {
    font-size: 16px;
  }

  .projects-testimonial {
    padding: 0;
    margin-top: 56px;
    gap: 20px;
  }

  /* Services */
  .services {
    padding: 0;
    margin: 64px 0 80px;
  }

  /* Pinned at `top top`, so the fixed 80px nav sits over the card: top padding
     is 80 (nav) + 40 (Figma 424:7595 badge offset). svh not vh, because iOS
     `100vh` is the large viewport and hides the card's last ~84px under the
     browser chrome. */
  .services-card {
    border-radius: 0;
    padding: 120px 24px 80px;
    flex-direction: column;
    min-height: 100svh;
    gap: 24px;
    box-sizing: border-box;
  }

  /* Flatten .services-left so its children participate in parent flex */
  .services-left {
    display: contents;
  }

  /* Headline: badge + heading — stays at top (order 1) */
  .services-header {
    order: 1;
    gap: 8px;
  }

  /* Video (order 2). `flex: none` so the column hugs the 270px video instead
     of inheriting the desktop `flex: 1` and stretching past it; that stretch
     plus the bottom-anchored slides opened a ~170px hole under the video. */
  .services-right {
    order: 2;
    flex: none;
  }

  /* Slides (order 3) still absorb the leftover height so the pinned card fills
     the viewport, but `.service-slide` is top-anchored below, so the copy sits
     24px under the video per Figma 424:7595. */
  .services-slides {
    order: 3;
    min-height: auto;
    flex: 1;
  }

  .services-heading {
    font-size: 32px;
    line-height: 40px;
    letter-spacing: -1.92px;
    max-width: 100%;
  }

  .service-slide {
    gap: 4px;
    top: 0;
    bottom: auto;
  }

  .service-slide .btn-dark-pill {
    margin-top: 12px;
  }

  .service-title {
    font-size: 21px;
    letter-spacing: -1.26px;
  }

  .service-description {
    font-size: 14px;
    letter-spacing: -0.28px;
    line-height: 24px;
    max-width: 100%;
  }

  .service-checklist {
    display: none;
  }

  .service-video-wrapper {
    height: 270px;
  }

  /* Testimonial */
  .testimonial-quote {
    font-size: 18px;
    line-height: 30px;
    letter-spacing: -1.08px;
  }

  /* About teaser — mobile: drop the flanking cards, keep the copy stack. */
  .home-about {
    padding: 80px 24px;
  }

  .home-about-content {
    gap: 0;
  }

  .home-about-deco {
    display: none;
  }

  .home-about-text {
    width: 100%;
    margin-bottom: 0;
  }

  .home-about-heading {
    font-size: 22px;
    line-height: 32px;
    letter-spacing: -1.1px;
  }

  .home-about-heading br {
    display: none;
  }

  /* Case Studies */
  .cs-hero-content {
    gap: 40px;
  }

  .cs-hero-subtitle {
    font-size: 14px;
    line-height: 22px;
    max-width: 100%;
  }

  .cs-hero-heading {
    font-size: 40px;
    line-height: 48px;
    letter-spacing: -2.4px;
  }

  .cs-hero {
    padding-top: 136px;
    padding-bottom: 56px;
  }

  /* The listing keeps the single column it has always had here: one full-width
     card per row, every image the same 300px tall. The offsets, the drop and
     the wide Figma section gaps are all desktop composition — flatten them to
     the 40px rhythm the homepage showcase already uses at this width. */
  .cs-showcase .projects-row--feature-narrow + .projects-row--pair,
  .cs-showcase .projects-row--pair + .projects-row--feature {
    margin-top: 40px;
  }

  /* Desktop separates a stacked pair with the offset card's own padding-top,
     which only reads as a gap while the padded card is the SECOND one. Rows 4
     and 6 mirror the pair, so there the padding lands above the row (80px) and
     the two cards touch — Clam's badge sat flush on the Finna plate. Drop the
     offsets here and space the column with a sibling margin instead. */
  .cs-showcase .project-card--portrait,
  .cs-showcase .projects-row--pair-drop .project-card--landscape {
    padding-top: 0;
  }

  .cs-showcase .projects-row > .project-card + .project-card {
    margin-top: 40px;
  }

  .cs-showcase .project-image {
    height: 300px;
    aspect-ratio: auto;
  }

  .cs-tag {
    height: 38px;
    padding: 10px 16px;
    font-size: 13px;
  }

  /* Logos, mobile (Figma 254:5559). The design puts the logos three to a row
     on a phone; we keep that rhythm but tighten it, because the site carries 15
     logos where the design mocked 6. A grid rather than the desktop's wrapping
     flex: with widths this uneven, flex rows drift and the block reads as a
     ragged pile instead of a wall of clients. 15 / 3 divides evenly, so the
     last row is full. */
  .logos-section {
    padding: 0 24px 40px;
  }

  .logos-content {
    gap: 32px;
  }

  .logos-text {
    font-size: 14px;
    letter-spacing: -0.28px;
  }

  .logos-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    /* Fixed rows, not content-sized: .logo-img is width/height:auto, so a row
       sized by its contents collapses to 0 until the lazy image decodes and
       then jumps. A 20px track (the logo cap) holds the space and keeps every
       row on the same rhythm. */
    grid-auto-rows: 20px;
    place-items: center;
    gap: 24px 12px;
    width: 100%;
    max-width: none;
  }

  /* 22px -> 20px so the cap matches the row track. The wide lockups (Student
     Depot, Hyundai, Android) are wider than a third of a phone; the global
     `img { max-width: 100% }` already caps them at the cell and height:auto
     takes the ratio down with it, which is what stops one logo from
     dominating its row. Scoped to the grid, since hiring.html reuses
     .logo-img in a .logos-row of its own. */
  .logos-grid .logo-img {
    max-height: 20px;
  }

  /* Kamil, 26 Aug 2026: the 15-logo wall is too much of the screen on a phone.
     It gives way to the hero's row instead, three logos deep rather than five
     because three 80px slots are the most that fit at full size on a 375px
     screen, and shrinking them would stop it reading as the same object as the
     hero. The grid is only hidden, never removed, so it is still what a
     visitor with reduced motion or a failed GSAP sees. */
  .logos-cycling .logos-grid {
    display: none;
  }

  .logos-cycling .logo-carousel.logos-cycle {
    display: flex;
    /* .logo-carousel is flex: 1 + right-aligned for its place beside the hero
       body text. This row owns its whole line, so both are undone. */
    flex: none;
    justify-content: center;
    width: 100%;
    gap: clamp(16px, 6vw, 40px);
  }

  /* Fixed 80px columns overflow a 320px screen once the gaps are counted, and
     .logo-column is flex-shrink: 0. Let them share the line instead and cap
     them at the hero's 80px, so they only ever shrink on the narrowest
     phones. */
  .logos-cycle .logo-column {
    flex: 1 1 0;
    width: auto;
    min-width: 0;
    max-width: 80px;
  }

  .logos-cycle .logo-name-img {
    max-width: 100%;
  }

  /* CTA */
  .cta {
    padding: 40px 24px 0;
  }

  .cta-heading {
    font-size: 32px;
    line-height: 40px;
    letter-spacing: -1.92px;
  }

  .cta-subheading {
    font-size: 14px;
    letter-spacing: -0.28px;
  }

  .cta-note {
    max-width: 100%;
    line-height: 20px;
  }

  /* Footer, mobile (Figma 242:13729 → 254:5396). Deliberately leaner than
     the desktop footer: the AI-summary row and the brand lockup are dropped,
     and what is left is a single centred column, email, socials, rule,
     legal links, copyright. The markup is shared with desktop, so every
     difference here is a mobile-only override. */
  .footer {
    padding: 0 var(--content-padding);
  }

  .footer-row {
    flex-direction: column;
    align-items: center;
    gap: 0;
  }

  /* The two blocks the phone layout does without. They are the reason the old
     mobile footer read as a wall of text, and neither appears in the design:
     the AI links are a desktop affordance, and the brand name is already
     carried by the email and the copyright line. */
  .footer-ai,
  .footer-brand {
    display: none;
  }

  /* Email + socials are one 8px-gapped pair in the design, but they live in
     two different rows in the markup. So the block's 32px padding goes on the
     outside of the pair, and the seam between the rows carries the 8px. */
  .footer-row--ai {
    padding: 32px 0 0;
  }

  .footer-row--brand {
    padding: 8px 0 32px;
  }

  .footer-email {
    width: 100%;
    text-align: center;
    font-size: 14px;
    letter-spacing: -0.28px;
  }

  .footer-social {
    width: 100%;
    justify-content: center;
    flex-wrap: wrap;
    gap: 12px;
  }

  .footer-social-link {
    font-size: 14px;
    letter-spacing: -0.28px;
  }

  /* 32px above and below the rule, 24px between the links and the copyright. */
  .footer-row--legal {
    padding: 32px 0;
    gap: 24px;
  }

  .footer-legal {
    justify-content: center;
    flex-wrap: wrap;
    gap: 8px 40px;
  }

  .footer-legal-link {
    line-height: 28px;
  }

  .footer-copyright {
    width: 100%;
    text-align: center;
    line-height: 28px;
    white-space: normal;
  }

  /* The design breaks the copyright after the domain rather than wherever the
     measure happens to run out, so the two spans become their own lines. */
  .footer-copyright span {
    display: block;
  }

  .footer-wordmark {
    /* The particle wordmark is too narrow to read at phone widths, so we
       hide it entirely on mobile — the rest of the footer already carries
       the brand identity. */
    display: none;
  }

  /* Modal responsive */
  .cal-modal-header {
    padding: 16px 20px;
  }

  .cal-modal-body {
    padding: 0 20px 20px;
  }

  .cal-modal-heading {
    font-size: 28px;
    letter-spacing: -1.5px;
  }

  .cal-modal-subheading {
    font-size: 14px;
    margin-bottom: 20px;
  }

  .cal-modal-embed {
    flex: 1;
    min-height: 0;
  }

  .cal-modal-footer {
    padding: 16px 20px;
  }

  .legal-hero {
    padding-top: 120px;
  }

  .legal-hero-title {
    font-size: 32px;
    line-height: 40px;
    letter-spacing: -1.92px;
  }

  .legal-hero-subtitle {
    font-size: 22px;
    line-height: 40px;
  }

  .legal-content {
    padding: 60px 24px;
  }

  .legal-block h2 {
    font-size: 22px;
    line-height: 32px;
    letter-spacing: -1px;
  }

  .legal-block p,
  .legal-block ul {
    font-size: 15px;
    line-height: 26px;
  }

  /* Case Study Detail — Mobile */
  .csd-hero {
    padding-top: 120px;
    padding-bottom: 32px;
  }

  .csd-hero-content {
    gap: 24px;
  }

  .csd-hero-tags {
    flex-wrap: wrap;
    gap: 8px;
  }

  .csd-hero-heading {
    font-size: 32px;
    line-height: 40px;
    letter-spacing: -1.92px;
  }

  /* One rhythm between images.
     .csd-page stacks every block on a single gap, so a bento group (.csb-tiles
     at 24px inside itself, .csb-duo, .csd-gallery) sat 64px from the very next
     image while its own images sat 24px apart. Read on a phone that is one
     inconsistency, not two intents: the groups are only groups because a
     desktop row put them side by side, and stacked there is nothing left to
     group. So media now packs to the same 24px it uses internally.
     The 40px restores the 64px wherever a NON media block is on either side,
     which is every text section, the project meta, the quote and .csr, so
     prose keeps its air and .csr keeps the space Figma leaves above its
     heading. Desktop and tablet are untouched: at 120px the two rhythms read
     as deliberate grouping rather than a mistake. */
  .csd-page {
    gap: 24px;
  }

  .csd-page > * + :not(.csd-key-visual, .csd-gallery, .csh-gallery, .csb-showcase, .csb-wide, .csb-tiles, .csb-duo, .csb-trio),
  .csd-page > :not(.csd-key-visual, .csd-gallery, .csh-gallery, .csb-showcase, .csb-wide, .csb-tiles, .csb-duo, .csb-trio) + * {
    margin-top: 40px;
  }

  .csd-section-inner {
    flex-direction: column;
    gap: 24px;
  }

  .csd-section-title {
    width: 100%;
  }

  .csd-heading {
    font-size: 24px;
    line-height: 32px;
    letter-spacing: -1.44px;
  }

  .csd-body-text {
    font-size: 14px;
    line-height: 24px;
    letter-spacing: -0.28px;
  }

  .csd-image-full {
    border-radius: 12px;
  }

  .csd-image-block {
    border-radius: 12px;
  }

  .csd-gallery {
    gap: 24px;
  }

  .csd-more-heading {
    font-size: 24px;
    line-height: 32px;
    letter-spacing: -1.44px;
  }

  .csd-more-grid {
    flex-direction: column;
    gap: 40px;
  }

  .csd-more-grid .cs-card-image {
    height: 280px;
  }

  /* About page, mobile: .cs-hero's mobile step (136px drop, 40px gap, 40/48
     heading). The 48px bottom stays local, it is the reel's. */
  .abt-hero {
    min-height: auto;
    padding: 136px var(--content-padding) 48px;
  }

  .abt-hero-content {
    max-width: 100%;
    gap: 40px;
  }

  .abt-hero-heading {
    font-size: 40px;
    line-height: 48px;
    letter-spacing: -2.4px;
    word-break: normal;
    overflow-wrap: break-word;
  }

  /* Figma's break after the first sentence would strand one word on a line of
     its own at this width, so the copy reflows on its own instead. Same
     treatment .home-about-heading's break gets. */
  .abt-hero-heading br {
    display: none;
  }

  .abt-hero-body {
    font-size: 14px;
    line-height: 22px;
    max-width: 100%;
  }

  .abt-video-section {
    padding: 24px;
  }

  .abt-manifesto {
    padding: 60px 24px;
  }

  .abt-manifesto-paragraph {
    font-size: 20px;
    line-height: 36px;
    letter-spacing: -1.2px;
  }

  /* --- Image collage, mobile ---
     The desktop scatter is a 1920-wide composition; scaled to 402px the stage
     is 195px tall, the lg tiles are 58px and the --sm tiles land at 14px,
     reading as specks rather than photographs. Mobile gets its own layout: a
     taller stage, the --sm tiles dropped, and the five remaining photos
     re-placed by hand. `left`/`top` are overridden rather than --x/--y because
     those are inline on the elements (verbatim Figma frame coordinates) and
     inline styles would win. */
  .abt-collage {
    padding: 32px 0;
  }

  .abt-collage-stage {
    aspect-ratio: 402 / 395;
  }

  /* Items 2 and 6 are the two slots the August 2026 design filled in. They
     stay out of the mobile scatter: the five hand-placed tiles below already
     use most of the 402x395 stage, and there is no mobile About design to
     re-place seven against. Desktop shows all eleven. */
  .abt-collage-item--sm,
  .abt-collage-item:nth-child(2),
  .abt-collage-item:nth-child(6) {
    display: none;
  }

  .abt-collage-item--lg {
    width: 44%;
  }

  .abt-collage-item--md {
    width: 30%;
  }

  /* nth-child indices follow the DOM order in about.html. 3 is the centre
     photo the morph bridge flies from, so it stays the visual anchor. */
  .abt-collage-item:nth-child(1) { left: 1.5%; top: 4%; }
  .abt-collage-item:nth-child(3) { left: 9%;   top: 47%; }
  .abt-collage-item:nth-child(4) { left: 54%;  top: 25%; }
  .abt-collage-item:nth-child(5) { left: 66%;  top: 1%; }
  .abt-collage-item:nth-child(7) { left: 60%;  top: 66%; }

  .abt-numbers-content {
    flex-direction: column;
    gap: 32px;
  }

  .abt-numbers-heading {
    font-size: 32px;
    line-height: 40px;
    letter-spacing: -1.92px;
  }

  .abt-stat-number {
    font-size: 28px;
    width: 120px;
    letter-spacing: -1.68px;
  }

  .abt-stat-label {
    font-size: 14px;
  }

  .abt-stat-row {
    height: 80px;
  }

  .abt-team-feature {
    padding: 40px 24px;
  }

  .abt-team-feature-content {
    flex-direction: column;
    height: auto;
    gap: 32px;
  }

  /* See the tablet note for `flex: none`. Height is left to the base
     `aspect-ratio: 907 / 770`, which also makes the morph bridge's source and
     target the same shape, so the flight no longer reframes the photo. */
  .abt-team-feature-image {
    flex: none;
    width: 100%;
    height: auto;
  }

  .abt-team-feature-quote {
    font-size: 20px;
    line-height: 36px;
    letter-spacing: -1.2px;
  }

  .abt-team-feature-body {
    font-size: 14px;
    letter-spacing: -0.28px;
  }

  /* The global .projects-row block above has already flattened these rows to
     one full-width card each. Two things it cannot know about this section.
     First, the pair is MIRRORED against the homepage's, so the portrait card's
     offset lands ABOVE the stacked row instead of between the two cards, and
     HockeyStack's caption ends up flush on the Student Depot plate — the same
     trap the /case-studies listing hit on its mirrored rows. Second, 80/160 of
     section padding is desktop composition. Images keep their aspect ratios
     here, unlike the listing's flat 300px: this is a curated strip inside a
     page, so it follows the homepage showcase. */
  .abt-cases {
    padding: 40px var(--content-padding) 64px;
  }

  .abt-cases .project-card--portrait {
    padding-top: 0;
  }

  .abt-cases .projects-row > .project-card + .project-card {
    margin-top: 40px;
  }

  .abt-founders {
    padding: 40px 24px;
  }

  .abt-founders-content {
    flex-direction: column;
    gap: 48px;
  }

  .abt-founder-name {
    font-size: 20px;
    line-height: 32px;
    letter-spacing: -1.2px;
  }

  .abt-founder-bio {
    font-size: 14px;
    letter-spacing: -0.28px;
  }

  .abt-founder-social-link {
    font-size: 14px;
    letter-spacing: -0.28px;
  }

  /* Hiring page — mobile */
  .hr-hero {
    padding: 160px 24px 60px;
  }

  .hr-hero-heading {
    font-size: 40px;
    line-height: 46px;
    letter-spacing: -2.4px;
  }

  .hr-hero-body {
    font-size: 14px;
    line-height: 28px;
    letter-spacing: -0.28px;
  }

  .hr-why {
    padding: 60px 24px;
  }

  .hr-why-header {
    margin-bottom: 32px;
  }

  .hr-why-grid {
    grid-template-columns: 1fr;
    gap: 16px;
  }

  .hr-why-card-inner {
    padding: 24px;
  }

  .hr-why-title {
    font-size: 20px;
    letter-spacing: -1.2px;
    line-height: 28px;
  }

  .hr-why-description {
    font-size: 14px;
    letter-spacing: -0.28px;
    line-height: 24px;
  }

  .hr-roles {
    padding: 60px 24px;
  }

  .hr-roles-header {
    margin-bottom: 32px;
  }

  .hr-role-card {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
    padding: 24px 0;
  }

  .hr-role-title {
    font-size: 20px;
    letter-spacing: -1.2px;
  }

  .hr-role-description {
    font-size: 14px;
    letter-spacing: -0.28px;
    line-height: 24px;
  }

  .hr-role-arrow {
    display: none;
  }

  .hr-culture {
    padding: 60px 24px;
  }

  .hr-culture-content {
    gap: 32px;
  }

  .hr-culture-image {
    height: 250px;
  }

  .hr-culture-heading {
    font-size: 28px;
    line-height: 36px;
    letter-spacing: -1.68px;
  }

  .hr-culture-body {
    font-size: 14px;
    line-height: 28px;
    letter-spacing: -0.28px;
  }

  .hr-apply {
    padding: 60px 24px;
    margin-top: 20px;
  }

  .hr-apply-content {
    flex-direction: column;
    gap: 32px;
  }

  .hr-apply-heading {
    font-size: 32px;
    line-height: 40px;
    letter-spacing: -1.92px;
  }

  .hr-apply-subheading {
    font-size: 14px;
    line-height: 24px;
    letter-spacing: -0.28px;
  }

  .hr-apply-note {
    font-size: 13px;
  }
}

/* ============================================
   404 ERROR PAGE
   ============================================ */

.error-page {
  margin: 0;
  min-height: 100vh;
  overflow: hidden;
  background: #f2f1f1;
  position: relative;
}

/* ---- Background ---- */
.error-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.error-bg-gradient-wrap {
  position: absolute;
  bottom: -15%;
  left: -15%;
  width: 130%;
  height: 80%;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.error-bg-gradient {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.55;
  filter: blur(80px);
}

.error-bg-blur {
  position: absolute;
  inset: 0;
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
}

/* ---- Large 404 SVG background text ---- */
.error-404-bg {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -45%);
  width: 95vw;
  max-width: 1600px;
  height: auto;
  z-index: 1;
  pointer-events: none;
  will-change: transform;
}

.error-404-svg {
  width: 100%;
  height: auto;
  display: block;
}

/* ---- Content layer ---- */
.error-content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
  padding: 40px var(--content-padding, 120px) 40px;
}

/* ---- Header / Logo ---- */
.error-header {
  flex-shrink: 0;
  width: 100%;
  display: flex;
  justify-content: center;
}

.error-logo {
  text-decoration: none;
  font-family: var(--font-primary, 'Geist', sans-serif);
  font-size: 16.84px;
  letter-spacing: -0.5px;
  color: var(--color-black);
}

.error-logo-bold {
  font-weight: 600;
}

.error-logo-dot {
  font-weight: 600;
}

.error-logo-light {
  font-weight: 300;
}

/* ---- Main content (centered) ---- */
.error-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  width: 100%;
}

.error-heading {
  font-family: var(--font-primary, 'Geist', sans-serif);
  font-size: 72px;
  font-weight: 500;
  line-height: 80px;
  letter-spacing: -4.32px;
  color: var(--color-black);
  margin: 0 0 35px;
  max-width: 900px;
}

.error-body {
  font-family: var(--font-primary, 'Geist', sans-serif);
  font-size: 16px;
  font-weight: 400;
  line-height: 28px;
  letter-spacing: -0.32px;
  color: var(--color-muted);
  margin: 0 0 32px;
  max-width: 787px;
}

.error-body-dark {
  color: var(--color-black);
}

/* CTA Button */
.error-cta {
  align-self: center;
}

/* ---- Footer ---- */
.error-footer {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding-bottom: 40px;
}

.error-footer-email {
  font-family: var(--font-primary, 'Geist', sans-serif);
  font-size: 14px;
  font-weight: 400;
  color: var(--color-muted);
  text-decoration: none;
}

.error-footer-email:hover {
  color: var(--color-black);
  text-decoration: underline;
}

.error-footer-links {
  display: flex;
  align-items: center;
  gap: 16px;
}

.error-footer-links a {
  font-family: var(--font-primary, 'Geist', sans-serif);
  font-size: 14px;
  font-weight: 400;
  color: var(--color-black);
  text-decoration: none;
}

.error-footer-links a:hover {
  opacity: 0.7;
}

.error-footer-dot {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--color-black);
  opacity: 0.3;
}

/* ---- 404 Responsive: Tablet ---- */
@media (max-width: 1200px) {
  .error-content {
    padding: 32px 80px 32px;
  }

  .error-404-bg {
    width: min(95vw, 1100px);
  }

  .error-heading {
    font-size: 56px;
    line-height: 64px;
    letter-spacing: -3.36px;
  }
}

/* ---- 404 Responsive: Mobile ---- */
@media (max-width: 767px) {
  .error-content {
    padding: 24px 24px 24px;
  }

  .error-404-bg {
    width: 100vw;
  }

  .error-heading {
    font-size: 36px;
    line-height: 42px;
    letter-spacing: -2.16px;
    margin-bottom: 24px;
  }

  .error-body {
    font-size: 15px;
    line-height: 24px;
  }

  .error-footer {
    gap: 12px;
  }
}

/* ============================================
   SERVICES PAGE — offmenu-inspired redesign
   Scoped under .services-page so it doesn't
   leak to the homepage's /services/ card.
   ============================================ */

/* ---------- HERO ---------- */
.services-page .services-hero {
  padding: 200px var(--content-padding) 120px;
}

.services-hero-inner {
  max-width: var(--content-max-width);
  margin: 0 auto;
}

.services-hero-heading {
  font-family: var(--font-primary);
  font-size: 72px;
  font-weight: 400;
  color: var(--color-dark);
  letter-spacing: -3.6px;
  line-height: 84px;
  max-width: 960px;
}

.how-we-work .badge {
  display: inline-flex;
  align-items: center;
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  background: var(--color-pill-bg);
  color: var(--color-dark);
  font-family: var(--font-primary);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.13px;
  line-height: 1;
}

/* ---------- CATEGORIES (light block) ---------- */
.services-page .service-categories {
  padding: 80px 0 120px;
  position: relative;
}

.service-categories-inner {
  max-width: var(--content-max-width);
  margin: 0 auto;
  padding: 0 var(--content-padding);
  display: flex;
  flex-direction: column;
  gap: 100px;
}

.service-category {
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.service-category-heading {
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 500;
  color: var(--color-muted);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  line-height: 1;
}

.service-category-rows {
  display: flex;
  flex-direction: column;
  border-top: 1px solid rgba(12, 21, 42, 0.1);
}

/* ---------- SERVICE ROW (clickable — clean: number + name + arrow) ---------- */
.service-row {
  display: grid;
  grid-template-columns: 56px 1fr 48px;
  align-items: center;
  gap: 24px;
  padding: 32px 16px;
  border-bottom: 1px solid rgba(12, 21, 42, 0.1);
  background-color: transparent;
  border-left: none;
  border-right: none;
  cursor: pointer;
  text-align: left;
  color: var(--color-dark);
  transition: background-color 0.35s ease;
  font-family: var(--font-primary);
  width: 100%;
}

.service-row:hover,
.service-row:focus-visible {
  background-color: rgba(12, 21, 42, 0.03);
}

.service-row:focus-visible {
  outline: 1px solid rgba(12, 21, 42, 0.25);
  outline-offset: -1px;
}

.service-row-num {
  font-family: var(--font-accent), var(--font-primary);
  font-style: italic;
  font-size: 14px;
  color: var(--color-muted);
  transition: color 0.3s ease;
}

.service-row:hover .service-row-num {
  color: var(--color-dark);
}

.service-row-name {
  font-family: var(--font-primary);
  font-size: 32px;
  font-weight: 400;
  color: var(--color-dark);
  letter-spacing: -1.28px;
  line-height: 40px;
}

.service-row-arrow {
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  color: var(--color-muted);
  transition: color 0.3s ease, transform 0.3s ease;
}

.service-row-arrow svg {
  width: 22px;
  height: 22px;
}

.service-row:hover .service-row-arrow {
  color: var(--color-dark);
  transform: translateX(6px);
}

/* ---------- CASE STUDY THUMBNAILS (between categories) ---------- */
.service-case-studies {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  margin-top: 16px;
}

.service-cs-thumb {
  position: relative;
  border-radius: 16px;
  overflow: hidden;
  display: block;
  aspect-ratio: 745 / 480;
  background: var(--color-card-bg);
  transition: transform 0.5s cubic-bezier(0.43, 0.98, 0.87, 1);
}

.service-cs-thumb:hover {
  transform: scale(1.015);
}

.service-cs-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.service-cs-label {
  position: absolute;
  bottom: 16px;
  left: 20px;
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 500;
  color: var(--color-dark);
  background: rgba(255, 255, 255, 0.7);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  letter-spacing: -0.14px;
}

/* ---------- HOW WE WORK ---------- */
.how-we-work {
  padding: 140px var(--content-padding) 100px;
}

.how-we-work-inner {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 56px;
}

.how-we-work-header {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.how-we-work-heading {
  font-family: var(--font-primary);
  font-size: 56px;
  font-weight: 500;
  color: var(--color-dark);
  letter-spacing: -2.8px;
  line-height: 64px;
  max-width: 780px;
}

.how-we-work-steps {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 32px;
}

.how-we-work-step {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 28px 24px;
  border-radius: var(--radius-lg);
  background: var(--color-white-50);
  border: 1px solid rgba(12, 21, 42, 0.06);
}

.how-we-work-num {
  font-family: var(--font-accent), var(--font-primary);
  font-style: italic;
  font-size: 14px;
  color: var(--color-muted);
}

.how-we-work-step-title {
  font-family: var(--font-primary);
  font-size: 22px;
  font-weight: 500;
  color: var(--color-dark);
  letter-spacing: -0.66px;
  line-height: 28px;
}

.how-we-work-step-copy {
  font-size: 15px;
  color: var(--color-muted);
  line-height: 22px;
  letter-spacing: -0.15px;
}

/* ---------- RESPONSIVE ---------- */
@media (max-width: 1200px) {
  .services-page .services-hero {
    padding: 140px 80px 80px;
  }
  .services-hero-heading {
    font-size: 56px;
    letter-spacing: -2.24px;
    line-height: 64px;
  }
  .service-categories-inner {
    padding: 0 80px;
    gap: 80px;
  }
  .service-row-name {
    font-size: 26px;
    line-height: 34px;
  }
  .how-we-work {
    padding: 100px 80px 80px;
  }
  .how-we-work-heading {
    font-size: 40px;
    letter-spacing: -2px;
    line-height: 48px;
  }
  .how-we-work-steps {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 767px) {
  .services-page .services-hero {
    padding: 100px 24px 60px;
  }
  .services-hero-heading {
    font-size: 36px;
    letter-spacing: -1.44px;
    line-height: 44px;
  }
  .services-page .service-categories {
    padding: 64px 0;
  }
  .service-categories-inner {
    padding: 0 24px;
    gap: 56px;
  }
  .service-category {
    gap: 24px;
  }
  .service-category-heading {
    font-size: 14px;
    letter-spacing: 1.4px;
  }
  .service-row {
    grid-template-columns: 36px 1fr 32px;
    gap: 12px;
    padding: 22px 8px;
  }
  .service-row-name {
    font-size: 20px;
    line-height: 28px;
    letter-spacing: -0.8px;
  }
  .service-case-studies {
    grid-template-columns: 1fr;
    gap: 16px;
  }
  .how-we-work {
    padding: 64px 24px;
  }
  .how-we-work-heading {
    font-size: 28px;
    letter-spacing: -1.12px;
    line-height: 36px;
  }
  .how-we-work-steps {
    grid-template-columns: 1fr;
    gap: 16px;
  }
}

/* ============================================
   SERVICE DRAWER
   Light panel sliding in from the right on a
   dimmed backdrop. On ≤767px becomes a bottom
   sheet sliding up.
   ============================================ */

.service-drawer {
  position: fixed;
  inset: 0;
  z-index: 9000;
  pointer-events: none;
  visibility: hidden;
}

.service-drawer.is-open {
  pointer-events: auto;
  visibility: visible;
}

.service-drawer-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(8, 10, 14, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.2s ease;
}

.service-drawer.is-open .service-drawer-backdrop {
  opacity: 1;
}

.service-drawer-panel {
  position: absolute;
  top: 0;
  right: 0;
  height: 100dvh;
  width: min(560px, 100vw);
  background: #f1f1f2;
  border-left: 1px solid rgba(21, 12, 64, 0.04);
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  will-change: transform;
  box-shadow: -24px 0 60px rgba(8, 10, 14, 0.18);
  outline: none;
}

.service-drawer.is-open .service-drawer-panel {
  transform: translateX(0);
}

.service-drawer-scroll {
  flex: 1;
  overflow-y: auto;
  scrollbar-width: thin;
  padding: 2px;
}

/* ---------- White header card (title + tagline + close + hero image) ---------- */
.service-drawer-card {
  background: #ffffff;
  border-radius: 30px;
  padding: 40px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  box-shadow: var(--shadow-card);
}

.service-drawer-card-top {
  display: flex;
  align-items: flex-start;
  gap: 24px;
}

.service-drawer-heading {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.service-drawer-title {
  font-family: var(--font-primary);
  font-size: 26px;
  font-weight: 500;
  color: var(--color-black);
  letter-spacing: -1.56px;
  line-height: 1.15;
  margin: 0;
}

.service-drawer-tagline {
  font-family: var(--font-primary);
  font-size: 16px;
  color: var(--color-muted);
  line-height: 23px;
  letter-spacing: -0.48px;
  margin: 0;
}

.service-drawer-close {
  flex-shrink: 0;
  width: 60px;
  height: 33px;
  border-radius: var(--radius-pill);
  background: #ffffff;
  border: 2px solid rgba(62, 44, 62, 0.05);
  color: var(--color-black);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: background-color 0.2s ease, color 0.2s ease;
  box-shadow: var(--shadow-card);
}

.service-drawer-close:hover,
.service-drawer-close:focus-visible {
  background-color: rgba(12, 21, 42, 0.04);
}

.service-drawer-hero {
  width: 100%;
  aspect-ratio: 476 / 280;
  background: #ebeced;
  border-radius: 16px;
}

/* ---------- Body sections ---------- */
.service-drawer-body {
  padding: 40px;
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.service-drawer-section {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.service-drawer-label {
  font-family: var(--font-primary);
  font-size: 16px;
  font-weight: 500;
  color: var(--color-black);
  letter-spacing: -0.96px;
  margin: 0;
}

.service-drawer-paragraph {
  font-family: var(--font-primary);
  font-size: 16px;
  color: var(--color-muted);
  line-height: 28px;
  letter-spacing: -0.32px;
  margin: 0;
}

/* Deliverables: check-circle icon + dashed underline */
.service-drawer-deliverables {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.service-drawer-deliverables li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-primary);
  font-size: 16px;
  color: #818a94;
  line-height: 28px;
  letter-spacing: -0.32px;
}

.service-drawer-deliverables li::before {
  content: '';
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z'/><path d='m9 12 2 2 4-4'/></svg>") center / contain no-repeat;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'><path d='M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z'/><path d='m9 12 2 2 4-4'/></svg>") center / contain no-repeat;
}

.service-drawer-deliverables li > span {
  border-bottom: 1px dashed rgba(129, 138, 148, 0.41);
  padding-bottom: 0;
}

/* ---------- Footer: price + CTA + note ---------- */
.service-drawer-footer {
  padding: 24px 40px 32px;
  background: #f1f1f2;
  display: flex;
  flex-direction: column;
  gap: 24px;
  border-top: 1px solid rgba(21, 12, 64, 0.04);
}

.service-drawer-footer-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.service-drawer-price {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.service-drawer-price-label {
  font-family: var(--font-primary);
  font-size: 16px;
  color: #818a94;
  letter-spacing: -0.32px;
  line-height: 28px;
}

.service-drawer-price-value {
  font-family: var(--font-primary);
  font-size: 24px;
  font-weight: 500;
  color: var(--color-black);
  letter-spacing: -1.44px;
  line-height: 1;
}

.service-drawer-footer-note {
  font-family: var(--font-primary);
  font-size: 12px;
  color: var(--color-muted);
  letter-spacing: -0.24px;
  line-height: 28px;
  text-align: center;
  margin: 0;
}

/* ---------- RESPONSIVE ---------- */
@media (max-width: 1200px) {
  .services-page .services-hero {
    padding: 140px 80px 80px;
  }
  .services-hero-heading {
    font-size: 64px;
    letter-spacing: -2.56px;
    line-height: 72px;
  }
  .service-categories-inner {
    padding: 0 80px;
    gap: 80px;
  }
  .service-category-heading {
    font-size: 48px;
    letter-spacing: -1.92px;
  }
  .service-row {
    grid-template-columns: 60px minmax(200px, 1fr) minmax(240px, 1.3fr) 40px;
    gap: 24px;
  }
  .service-row-name {
    font-size: 24px;
    line-height: 32px;
  }
  .how-we-work {
    padding: 100px 80px 80px;
  }
  .how-we-work-heading {
    font-size: 40px;
    letter-spacing: -2px;
    line-height: 48px;
  }
  .how-we-work-steps {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 767px) {
  .services-page .services-hero {
    padding: 100px 24px 60px;
  }
  .services-hero-inner {
    gap: 24px;
  }
  .services-hero-heading {
    font-size: 40px;
    letter-spacing: -1.6px;
    line-height: 48px;
  }
  .services-hero-sub {
    font-size: 16px;
    line-height: 24px;
  }
  .services-page .service-categories {
    padding: 80px 0;
  }
  .service-categories-inner {
    padding: 0 24px;
    gap: 64px;
  }
  .service-category {
    gap: 24px;
  }
  .service-category-heading {
    font-size: 32px;
    letter-spacing: -1.28px;
    gap: 12px;
  }
  .service-row {
    grid-template-columns: 40px 1fr 28px;
    grid-template-rows: auto auto;
    gap: 8px 16px;
    padding: 20px 8px;
  }
  .service-row-num {
    grid-column: 1;
    grid-row: 1;
  }
  .service-row-name {
    grid-column: 2;
    grid-row: 1;
    font-size: 20px;
    line-height: 28px;
    letter-spacing: -0.8px;
  }
  .service-row-tagline {
    grid-column: 1 / -1;
    grid-row: 2;
    font-size: 14px;
    line-height: 20px;
  }
  .service-row-arrow {
    grid-column: 3;
    grid-row: 1;
  }
  .how-we-work {
    padding: 64px 24px;
  }
  .how-we-work-heading {
    font-size: 28px;
    letter-spacing: -1.12px;
    line-height: 36px;
  }
  .how-we-work-steps {
    grid-template-columns: 1fr;
    gap: 16px;
  }

  /* Drawer → bottom sheet on mobile */
  .service-drawer-panel {
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    width: 100%;
    max-height: 92dvh;
    height: auto;
    border-left: none;
    border-top: 1px solid rgba(21, 12, 64, 0.04);
    border-radius: 24px 24px 0 0;
    transform: translateY(100%);
    box-shadow: 0 -24px 60px rgba(8, 10, 14, 0.25);
  }
  .service-drawer.is-open .service-drawer-panel {
    transform: translateY(0);
  }
  .service-drawer-card {
    padding: 24px;
    border-radius: 20px;
  }
  .service-drawer-title {
    font-size: 24px;
    letter-spacing: -1.2px;
  }
  .service-drawer-tagline {
    font-size: 15px;
    line-height: 22px;
  }
  .service-drawer-body {
    padding: 24px;
    gap: 28px;
  }
  .service-drawer-footer {
    padding: 16px 24px 24px;
    gap: 16px;
  }
}

@media (max-width: 1200px) {
  .component-hero,
  .component-section {
    grid-template-columns: 1fr;
    gap: 32px;
  }
}

@media (max-width: 767px) {
  .component-library {
    padding: 112px var(--content-padding) 72px;
  }

  .component-hero {
    padding-bottom: 44px;
  }

  .component-section {
    padding: 40px 0;
  }

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

  .component-panel {
    padding: 18px;
  }

  .component-inline {
    align-items: stretch;
  }

  .component-inline .btn-primary,
  .component-inline .btn-dark-pill,
  .component-inline .abt-pill-link {
    flex: 1 1 auto;
  }

  .component-type-row {
    grid-template-columns: 1fr;
    gap: 8px;
  }
}

/* ---------- REDUCED MOTION ---------- */
@media (prefers-reduced-motion: reduce) {
  .service-drawer-panel,
  .service-drawer-backdrop {
    transition: none !important;
  }
  .service-row-arrow,
  .service-row,
  .service-row-num {
    transition: none !important;
  }
}

/* ============================================
   CROSS-DOCUMENT VIEW TRANSITIONS
   Kept ONLY for the root cross-fade, which hides
   the swap frame between the old and new document
   while the js/home-morph.js portal flies. There
   are deliberately no named (per-slug) transitions
   anymore — see below. Browsers without support
   (Firefox, Safari <18.2) simply do a normal
   navigation; the portal carries the motion.
   ============================================ */
@view-transition {
  navigation: auto;
}

/* Tighten the default 0.4s root cross-fade so the surrounding document
   doesn't linger. Only affects case-study navigation in practice, but
   applies to any cross-document VT navigation. Keep it tasteful. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.35s;
  animation-timing-function: cubic-bezier(0.43, 0.98, 0.87, 1);
}

/* No ::view-transition-group(cs-<slug>) rules, and no matching inline
   style="view-transition-name: cs-<slug>" on the cards or heroes.
   The browser's named morph used to run on the /case-studies.html cards
   only, which is one of the reasons those pages felt different from the
   homepage. The portal now owns the card-to-hero motion everywhere, and a
   named pair would animate a second copy of the image underneath it. */

/* Card-to-hero morph arrival (js/home-morph.js). components.js sets this
   attribute synchronously before first paint when a fresh hand-off exists,
   and renders the portal at the card's viewport rect. Until the morph
   reveals them: main content and nav stay hidden (the JS fades them in
   around the traveling portal — no !important on opacity so GSAP's inline
   styles win), and the real hero image is hidden via visibility so it can't
   double under the portal. */
html[data-cs-morph-arrival] .csd-main {
  opacity: 0;
}
html[data-cs-morph-arrival] .nav {
  opacity: 0;
}
html[data-cs-morph-arrival] .csd-key-visual .csd-image-full img,
html[data-cs-morph-arrival] .csd-key-visual .csd-image-full video {
  visibility: hidden;
}

/* ==================== NEW CASE STUDY TEMPLATE (csb-*) ====================
   Shared by case-study-biomerieux.html (Figma node 629:1208) and
   case-study-treat.html (Figma node 636:27209).
   The pages reuse the csd-* template; csb-* covers what the new layout
   adds: services pills, meta columns, tile grid, quote, duo gallery and
   the post-CTA lightbox placeholder. Scope class: main.csb */

.csb-hero {
  padding-top: 216px;
  padding-bottom: 56px;
}

.csb-hero .csd-hero-content {
  gap: 56px;
}

.csb-services {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.csb-services-label {
  font-family: var(--font-primary);
  font-size: 16px;
  font-weight: 400;
  line-height: 28px;
  letter-spacing: -0.96px;
  color: rgba(42, 12, 19, 0.51);
}

.csb-services-tags {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.csb-tag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 7px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid #545454;
  background-image: linear-gradient(180deg, #5c5c5c 0%, #555555 50%, #5c5c5c 100%);
  box-shadow: var(--shadow-card), inset 0px 1px 0px 1px #7b7b7b;
  font-family: var(--font-primary);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: -0.24px;
  line-height: normal;
  color: var(--color-white);
  text-shadow: 0px 0px 27px rgba(255, 255, 255, 0.25);
  white-space: nowrap;
}

.csb-hero-heading {
  max-width: 1219px;
  color: #2a0c13;
}

/* SplitType marks the nested brand/main spans as inline-block, which turns
   each span into an atomic line box and forces the brand name onto its own
   line. Figma flows the whole heading as one paragraph, so restore inline
   flow (must beat SplitType's inline style, hence !important). */
.csb-hero-heading > span {
  display: inline !important;
}

.csb-hero-brand {
  color: rgba(42, 12, 19, 0.51);
}

/* Hero key-visual film: overlays the still image inside .csd-image-full and
   fades in only once playback has actually started (case-study-hero.js adds
   .is-playing on the video's "playing" event). The still <img> underneath
   remains the home-morph / view-transition target, so the card-to-hero
   interaction is identical to the image-only case studies. */
.csb-hero-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.6s ease;
  pointer-events: none;
}

.csb-hero-video.is-playing {
  opacity: 1;
}

/* Project meta (Industry / Stage / Timeframe) */
.csb-meta {
  width: 100%;
  max-width: var(--content-max-width);
  display: flex;
  gap: 40px;
}

/* Figma (1030:14157, 636:27209) puts the three columns on a 240px pitch,
   200px wide plus the row's 40px gap, bunched at the left. flex: 1 spread
   them across the full 1530px content width, 483px apart.
   200px is a floor, not a fixed width: several pages still carry draft copy
   longer than that ("AI incident management for engineering teams"), and a
   hard 200px wrapped it onto two lines. Columns hug wider content instead
   and only shrink-then-wrap if the row ever outgrows the container. */
.csb-meta-item {
  flex: 0 1 auto;
  min-width: 200px;
  display: flex;
  flex-direction: column;
}

.csb-meta-label {
  font-family: var(--font-primary);
  font-size: 16px;
  font-weight: 500;
  line-height: 28px;
  letter-spacing: -0.96px;
  color: rgba(42, 12, 19, 0.51);
}

.csb-meta-value {
  font-family: var(--font-primary);
  font-size: 16px;
  font-weight: 400;
  line-height: 28px;
  letter-spacing: -0.96px;
  color: #2a0c13;
}

/* This design rounds images to 24px (Figma), template default is 16px */
.csb .csd-image-full,
.csb .csd-image-full img,
.csb .csd-image-block,
.csb .csd-image-block img {
  border-radius: 24px;
}

/* Video key visual (Figma video fill) gets the exact same treatment as imgs.
 * .csd-image-block covers the in-page product film; .csd-image-full the hero. */
.csb .csd-image-full video,
.csb .csd-image-block video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 24px;
}

/* Wide (1530px) image sections */
.csb-wide {
  width: 100%;
  max-width: var(--content-max-width);
}

/* UI-details tile grid: two rows, Figma widths 400/650/400 and 500/475/475 */
.csb-tiles {
  width: 100%;
  max-width: var(--content-max-width);
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.csb-tiles-row {
  display: grid;
  gap: 40px;
}

.csb-tiles-row-1 {
  grid-template-columns: 400fr 650fr 400fr;
}

.csb-tiles-row-2 {
  grid-template-columns: 500fr 475fr 475fr;
}

.csb-tile {
  border-radius: 24px;
  overflow: hidden;
}

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

.csb-tile-a { aspect-ratio: 400 / 320; }
.csb-tile-b { aspect-ratio: 650 / 320; }
.csb-tile-c { aspect-ratio: 500 / 320; }
.csb-tile-d { aspect-ratio: 475 / 320; }

/* Opt-in reorder: some designs centre the wide tile, 475 / 500 / 475, rather
   than the template's 500 / 475 / 475. Scoped to >=768px so it cannot
   out-specify the shared single-column stacking rule on mobile. */
@media (min-width: 768px) {
  .csb-tiles-row-2.csb-tiles-row-2--center-wide {
    grid-template-columns: 475fr 500fr 475fr;
  }
}

/* Testimonial quote */
.csb-quote {
  width: 100%;
  max-width: var(--content-max-width);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 40px;
}

.csb-quote-text {
  font-family: var(--font-primary);
  font-size: 48px;
  font-weight: 400;
  line-height: 64px;
  letter-spacing: -2.88px;
  color: #2a0c13;
  max-width: 1219px;
  margin: 0;
}

.csb-quote-author {
  font-family: var(--font-primary);
  font-size: 32px;
  font-weight: 400;
  line-height: 40px;
  letter-spacing: -0.32px;
  color: #2a0c13;
}

.csb-quote-role {
  color: rgba(42, 12, 19, 0.51);
}

/* Two-up gallery */
.csb-duo {
  width: 100%;
  max-width: var(--content-max-width);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
}

.csb-duo-item {
  aspect-ratio: 745 / 600;
  border-radius: 24px;
  overflow: hidden;
}

.csb-duo-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Three-up gallery, Figma 483 / 483 / 483 across the 1530 content column */
.csb-trio {
  width: 100%;
  max-width: var(--content-max-width);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 40px;
}

.csb-trio-item {
  aspect-ratio: 483 / 600;
  border-radius: 24px;
  overflow: hidden;
}

.csb-trio-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Full-width showcase block, Figma 1530 x 900. Unlike .csd-image-block (1200
 * wide, 4:3) this spans the whole content column, because the showcase frames
 * in these designs are exported whole: section background plus the product
 * screen inset inside it. So the block is just a clipped 1530:900 box. */
.csb-showcase {
  width: 100%;
  max-width: var(--content-max-width);
  aspect-ratio: 1530 / 900;
  border-radius: 24px;
  overflow: hidden;
  position: relative;
}

/* `> picture > img` as well as `> img`: scripts/write-srcset.mjs wraps every
 * responsive image in a <picture>, and while `picture { display: contents }`
 * removes the wrapper's box it does NOT remove it from selector matching — a
 * bare child selector silently stops applying the moment a page gets srcset.
 * Still a child selector rather than a descendant one, so it cannot reach into
 * .csh-compare's panes, which style their own imgs. */
.csb-showcase > img,
.csb-showcase > video,
.csb-showcase > picture > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Film variant. Figma rounds this frame to 26px and paints it #404040, which
 * is what shows while the video is still fetching. */
.csb-showcase--film {
  border-radius: 26px;
  /* Matches the film's own plate, sampled from its first frame, so the block
     reads right in the moment before the video paints. Was #404040, which came
     from the placeholder frame colour of the old draft cut. */
  background: #0d0d0d;
}

/* --- bioMérieux case study: tablet --- */
@media (max-width: 1200px) {
  .csb-hero {
    padding-top: 160px;
    padding-bottom: 48px;
  }

  .csb-hero .csd-hero-content {
    gap: 40px;
  }

  .csb-hero-heading {
    max-width: 100%;
  }

  /* The 200px floor is a 1920-frame spec: three of those plus the 40px gaps
     is 680px, wider than the 608px of content a 768px viewport has. */
  .csb-meta-item {
    min-width: 160px;
  }

  .csb .csd-image-full,
  .csb .csd-image-full img,
  .csb .csd-image-full video,
  .csb .csd-image-block,
  .csb .csd-image-block img,
  .csb-showcase,
  .csb-showcase--film,
  .csb-tile,
  .csb-duo-item,
  .csb-trio-item {
    border-radius: 16px;
  }

  .csb-quote-text {
    font-size: 36px;
    line-height: 48px;
    letter-spacing: -2.16px;
  }

  .csb-quote-author {
    font-size: 24px;
    line-height: 32px;
    letter-spacing: -0.24px;
  }
}

/* --- bioMérieux case study: mobile --- */
@media (max-width: 767px) {
  .csb-hero {
    padding-top: 120px;
    padding-bottom: 32px;
  }

  .csb-hero .csd-hero-content {
    gap: 24px;
  }

  .csb-services-tags {
    gap: 8px;
  }

  .csb-meta {
    flex-direction: column;
    gap: 16px;
  }

  /* flex-direction: column makes the 200px floor irrelevant and a basis a
     HEIGHT rather than a width. */
  .csb-meta-item {
    flex: none;
    min-width: 0;
  }

  .csb-tiles {
    gap: 24px;
  }

  .csb-tiles-row,
  .csb-tiles-row-1,
  .csb-tiles-row-2 {
    grid-template-columns: 1fr;
    gap: 24px;
  }

  /* Stacked tiles keep their OWN ratio, the rule .csh-tile-* already follows.
     Flattening all four to 400/320 here bought uniform height and paid for it
     in crop: every tile image is exported at exactly its tile's ratio, so a
     1.25:1 box cover-cropped the wide ones side to side. .csb-tile-b (2.03:1)
     showed 61% of its width, .csb-tile-c 80%, .csb-tile-d 84%, which on
     bioMérieux cut the View button and two of the four availability stats off
     the BIOFIRE TORCH card. Fitting to width instead shows the whole panel in
     a shorter box, and nothing crops at any width now. .csb-tile-a is 1.25:1
     natively, so it is unchanged. */

  .csb-quote {
    gap: 24px;
  }

  .csb-quote-text {
    font-size: 24px;
    line-height: 34px;
    letter-spacing: -1.2px;
  }

  .csb-quote-author {
    font-size: 18px;
    line-height: 26px;
    letter-spacing: -0.18px;
  }

  .csb-duo {
    grid-template-columns: 1fr;
    gap: 24px;
  }

  .csb-trio {
    grid-template-columns: 1fr;
    gap: 24px;
  }

}

/* ==================== HOCKEYSTACK CASE STUDY (csh-*) ====================
   Page-specific extras for case-study-hockeystack.html on top of the shared
   csb-* template.

   Figma 1130:39047 — a dark-mode / light-mode comparison. Two copies of the
   same screen sit in a 1530 x 900 frame and a draggable divider wipes between
   them, frame background included, so the whole block changes theme at once.

   The control is a range input stretched over the block: that buys pointer
   drag, click-to-jump, keyboard arrows and the slider role from the platform,
   leaving js/theme-compare.js only one job, mirroring value into --csh-pos.

   Figma places the two screens 264px apart (4.6% from the left edge in dark
   mode, 21.8% in light), which reads as a rough mock rather than intent. Both
   are centred here so the wipe reads as one screen changing theme instead of
   two images sliding past each other. */

.csh-compare {
  --csh-pos: 50%;
  background: #0d0d0d;
}

.csh-compare-pane {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The screen is 1060 of the frame's 1530 = 69.28%, vertically centred. */
.csh-compare-pane img {
  width: 69.28%;
  height: auto;
  display: block;
}

/* Figma's drop shadows were left out of the exports on purpose: they bleed past
   the frame bounds, so baking them in would have made the two images different
   sizes and broken the wipe. Reinstated here at Figma's own values — and, as in
   Figma, on the DARK half only. The light window sits flush on its light plate
   with no shadow of its own; carrying the dark half's shadow across read as a
   heavy double edge against near-white. */
.csh-compare-pane--dark img {
  filter:
    drop-shadow(0 1px 2px rgba(0, 0, 0, 0.35))
    drop-shadow(0 7px 15px rgba(0, 0, 0, 0.35))
    drop-shadow(0 20px 40px rgba(0, 0, 0, 0.4));
}

.csh-compare-pane--light {
  background: #ebeced;
  /* Clips away everything left of the divider, so light reveals rightwards. */
  clip-path: inset(0 0 0 var(--csh-pos));
}

.csh-compare-line {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--csh-pos);
  width: 2px;
  margin-left: -1px;
  background: #c0c0c0;
  pointer-events: none;
}

.csh-compare-handle {
  position: absolute;
  top: 50%;
  left: var(--csh-pos);
  width: 56px;
  height: 56px;
  margin: -28px 0 0 -28px;
  border-radius: 1000px;
  background: rgba(192, 192, 192, 0.4);
  display: grid;
  place-items: center;
  pointer-events: none;
}

.csh-compare-handle svg {
  display: block;
}

/* The actual control: transparent and full bleed, above both panes.
   touch-action leaves vertical panning to the browser so the block cannot trap
   a phone scroll, while horizontal drags still move the divider. */
.csh-compare-range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: ew-resize;
  touch-action: pan-y;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
}

/* A hairline thumb keeps value and pointer position in step; a default-width
   thumb offsets the divider from the cursor by half a thumb at each end. */
.csh-compare-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 1px;
  height: 100%;
  background: transparent;
}

.csh-compare-range::-moz-range-thumb {
  width: 1px;
  height: 100%;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.csh-compare-range:focus-visible ~ .csh-compare-handle {
  outline: 2px solid var(--color-white);
  outline-offset: 4px;
}

@media (max-width: 1200px) {
  .csh-compare-handle {
    width: 44px;
    height: 44px;
    margin: -22px 0 0 -22px;
  }
}

@media (max-width: 767px) {
  .csh-compare-handle {
    width: 36px;
    height: 36px;
    margin: -18px 0 0 -18px;
  }

  .csh-compare-handle svg {
    width: 14px;
    height: 14px;
  }
}

/* This design's tile rows are 400/708/400 and 560/400/546, not the shared
   template's 400/650/400 and 500/475/475. Only the COLUMN RATIOS are this
   page's own; the gutter is the site's 40, brought in line with every other
   grid on 28 Aug 2026. It was 12px, straight off the HockeyStack frame, and
   that made this the one page whose images sat closer together than the rest.
   The columns are fr, so the wider gutter is taken out of the tiles rather
   than the row: all three shrink by the same factor and the row stays even.
   Scoped to .csh and to >=768px so it cannot out-specify the shared
   single-column stacking rule on mobile. */
@media (min-width: 768px) {
  .csh .csb-tiles.csh-tiles,
  .csh .csh-tiles .csb-tiles-row {
    gap: 40px;
  }

  .csh .csh-tiles .csb-tiles-row-1 {
    grid-template-columns: 400fr 708fr 400fr;
  }

  .csh .csh-tiles .csb-tiles-row-2 {
    grid-template-columns: 560fr 400fr 546fr;
  }
}

.csh-tile-wide { aspect-ratio: 708 / 320; }
.csh-tile-mid  { aspect-ratio: 560 / 320; }
.csh-tile-mid2 { aspect-ratio: 546 / 320; }

/* The design system board is taller than the 1530x900 showcase block.
   1530 x 1325 in Figma; the export is that at 2x. */
.csh-designsys {
  aspect-ratio: 1530 / 1325;
}

/* Product gallery: the four 1200x900 frames Figma lays out in a row running off
   the right edge of the page frame, i.e. one card with the next peeking.

   It is no longer a scroller under the pointer. `overflow-x: auto` here meant
   the block ate any wheel or trackpad gesture with a horizontal component, so
   scrolling the *page* past it fought the carousel for the whole height of the
   section — the reported bug. A finger never had that problem (touch panning
   is direction-locked by the browser), so touch keeps the native scroller and
   desktop loses it: on a hover-capable pointer .is-enhanced clamps the track to
   overflow:hidden, which is still a scroll container for scrollTo() and for
   scroll snapping, but is unreachable from wheel, trackpad or scrollbar.
   `.is-enhanced` is added by js/project-cursor.js, so with JS off the native
   scroller is left alone rather than leaving a dead, uncrossable carousel.

   Snapping stays mandatory AND scroll-snap-stop: always so the touch scroller
   can still only come to rest on a card edge. */
.csh-gallery {
  position: relative;
  width: 100%;
  max-width: var(--content-max-width);
}

.csh-gallery-track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 78.43%; /* 1200 of the 1530 content width */
  /* 40 like every other gutter on the site. Safe to change: the carousel step
     is measured (items[1].offsetLeft - items[0].offsetLeft in
     js/project-cursor.js), never assumed from this number. */
  gap: 40px;
  overflow-x: auto;
  /* keep a horizontal overscroll from turning into page/history navigation */
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.csh-gallery-track::-webkit-scrollbar {
  display: none;
}

/* It takes focus (arrow keys are bound to it in JS), so it must show it. */
.csh-gallery-track:focus-visible {
  outline: 2px solid var(--color-white);
  outline-offset: 6px;
}

/* Default shape, and the only one a device without a hoverable pointer gets:
   two small circles pinned inside the edges, clear of the middle so they never
   sit under a swipe. Sized past the 44px tap-target floor. */
.csh-gallery-nav {
  display: none;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 44px;
  height: 44px;
  padding: 0;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-dark-border);
  border-radius: 50%;
  /* Deliberately a flat translucent fill and not the puck's frosted glass:
     a backdrop-filter here would be a third animated blur on the page for a
     control that is only ever shown where the GPU is weakest. */
  background: rgba(98, 96, 112, 0.55);
  color: var(--color-white);
  transition: opacity 0.2s ease;
}

.csh-gallery.is-enhanced .csh-gallery-nav {
  display: flex;
}

.csh-gallery-nav--prev { left: 16px; }
.csh-gallery-nav--next { right: 16px; }

/* At either end the half it points into is dead: no click, and — because it
   stops being hit-tested — no puck and the OS cursor back. */
.csh-gallery-nav:disabled {
  opacity: 0;
  pointer-events: none;
}

/* With a real pointer the button is an invisible full-height half of the
   gallery and the frosted puck is the whole affordance, exactly like the
   case-study cards: no visible chrome over the artwork. */
@media (hover: hover) and (pointer: fine) {
  .csh-gallery.is-enhanced .csh-gallery-track {
    overflow-x: hidden;
  }

  .csh-gallery.is-enhanced .csh-gallery-nav {
    top: 0;
    bottom: 0;
    transform: none;
    width: 50%;
    height: auto;
    border: 0;
    border-radius: 0;
    background: none;
    cursor: none;
  }

  .csh-gallery.is-enhanced .csh-gallery-nav--prev { left: 0; }
  .csh-gallery.is-enhanced .csh-gallery-nav--next { right: 0; }

  .csh-gallery.is-enhanced .csh-gallery-nav svg { display: none; }

  /* The puck already vanished; fading the invisible box would do nothing. */
  .csh-gallery.is-enhanced .csh-gallery-nav:disabled { opacity: 1; }
}

.csh-gallery-item {
  margin: 0;
  aspect-ratio: 1200 / 900;
  border-radius: 24px;
  overflow: hidden;
  scroll-snap-align: start;
  /* one gesture moves exactly one card, and cannot stop between two */
  scroll-snap-stop: always;
}

.csh-gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

@media (max-width: 767px) {
  /* These deliberately do NOT join the shared "one ratio when stacked" rule.
     The shared tiles are all near 400/320 so squaring them up costs nothing,
     but these run to 708/320, and cover-cropping a 2.2:1 chart into 1.25:1
     cuts its own axis labels off. Stacked full-width, uniform height buys
     little, so each keeps its native ratio and stays whole. */
  .csh-tile-wide { aspect-ratio: 708 / 320; }
  .csh-tile-mid  { aspect-ratio: 560 / 320; }
  .csh-tile-mid2 { aspect-ratio: 546 / 320; }

  .csh-gallery-track {
    grid-auto-columns: 88%;
    gap: 16px;
  }

  .csh-gallery-nav--prev { left: 8px; }
  .csh-gallery-nav--next { right: 8px; }

  .csh-gallery-item {
    border-radius: 16px;
  }
}

/* ==================== BLUEGROUND CASE STUDY (csg-*) ====================
   Page-specific extras for case-study-blueground.html on top of the shared
   csb-* template. */

/* Figma orders this row 475 / 500 / 475 (search, workforce, comparison)
   rather than the template's 500 / 475 / 475. The search tile is a
   deliberate left-bleed crop that only reads correctly in the first
   column, flush with the content edge. Scoped to >=768px so it cannot
   out-specify the shared single-column stacking rule on mobile. */
@media (min-width: 768px) {
  .csb-tiles-row-2.csg-tiles-row-2 {
    grid-template-columns: 475fr 500fr 475fr;
  }
}

/* ==================== COOKIE BANNER ====================
   Non-modal consent prompt, injected by js/shared.js. Consent state and the
   analytics gate live at the top of js/components.js.

   Figma 1034:31361. The design reuses the nav pill component, so the values
   here intentionally mirror .nav-pill / .nav-pill-item: 14px/560/-0.28px
   labels, 14px 22px pill padding, and the active-item white fill + shadow on
   Accept. */

.cookie-banner {
  position: fixed;
  left: 24px;
  bottom: 24px;
  /* Above .service-drawer (9000) and .nav (100); below .preloader (9999) and
     .cal-modal (10000), so a booking overlay always wins. */
  z-index: 9500;
  max-width: calc(100vw - 48px);
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.4s cubic-bezier(0.43, 0.98, 0.87, 1),
              transform 0.4s cubic-bezier(0.43, 0.98, 0.87, 1);
}

/* The blurred surface lives here rather than on .cookie-banner, which carries
   the entry transform. Same split as .service-drawer: a backdrop-filter on a
   transformed node gets dropped mid-animation by some browsers. */
.cookie-banner-inner {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 32px;
  padding: 8px 9px;
  border-radius: var(--radius-lg);
  background: rgba(62, 44, 62, 0.05);
  backdrop-filter: blur(25px);
  -webkit-backdrop-filter: blur(25px);
  overflow: clip; /* clips the blur to the rounded corners, as .nav-pill does */
}

/* [hidden] would otherwise lose to the descendant display:flex above. */
.cookie-banner[hidden] {
  display: none;
}

.cookie-banner.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.cookie-banner-body {
  /* Asymmetric by design: 8px left aligns the copy with the Accept pill's
     label rather than its pill edge. */
  padding: 14px 22px 14px 8px;
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 560;
  line-height: normal;
  letter-spacing: -0.28px;
  color: var(--color-dark);
  white-space: nowrap;
}

.cookie-banner-actions {
  display: flex;
  gap: 4px;
}

/* Accept and Deny are identical in geometry and type; only the fill differs
   (Figma 1034:31357 vs 1034:31354). Keep the padding, font and hit area equal:
   under GDPR refusing must stay exactly as easy as accepting. */
.cookie-banner-btn {
  padding: 14px 22px;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  font-family: var(--font-primary);
  font-size: 14px;
  font-weight: 560;
  line-height: normal;
  letter-spacing: -0.28px;
  color: var(--color-dark);
  cursor: pointer;
  transition: background-color 0.2s, box-shadow 0.2s;
}

.cookie-banner-btn--accept {
  background: var(--color-white);
  box-shadow: var(--shadow-card);
}

.cookie-banner-btn:hover {
  background-color: rgba(255, 255, 255, 0.45);
}

.cookie-banner-btn--accept:hover {
  background-color: var(--color-white);
  box-shadow: 0px 2px 6px var(--shadow-color-near), 0px 43px 80px var(--shadow-color-far);
}

.cookie-banner-btn:focus-visible {
  outline: 2px solid var(--color-blue-start);
  outline-offset: 2px;
}

/* "Cookie settings" is a <button> (an action, not navigation) styled to sit
   flush with its sibling footer links. */
.footer-legal-link--btn {
  font-family: inherit;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  .cookie-banner,
  .cookie-banner.is-visible {
    transform: none;
    transition: opacity 0.2s linear;
  }
}

@media (max-width: 767px) {
  .cookie-banner {
    left: 24px;
    right: 24px;
    bottom: 24px;
  }

  .cookie-banner-inner {
    gap: 20px;
  }

  /* The single-line desktop measure does not fit a phone. */
  .cookie-banner-body {
    white-space: normal;
  }

  .cookie-banner-actions {
    width: 100%;
  }

  .cookie-banner-btn {
    flex: 1;
  }
}

/* Responsive images are wrapped in <picture> by scripts/write-srcset.mjs so a
   WebP source can carry a type. display: contents removes the wrapper's box
   entirely, so every existing rule written against `.csd-image-full img`,
   `.cs-card-image img` etc. — including the `height: 100%` ones, which would
   otherwise resolve against an inline wrapper — keeps behaving exactly as it
   did when the <img> was the direct child. */
picture {
  display: contents;
}

/* ==================== RELATED CASE STUDIES (csr-*) ====================
   Figma 1105:6357. The last block of every case study, between the body and
   the CTA. Two cards, each a different pair per page, so no study ever
   points at itself and no two pages recommend the same two.

   It is the LAST child of .csd-page on purpose: the container's 120px gap is
   exactly the space Figma leaves above the heading, so the block needs no
   top padding and no horizontal padding of its own. The 120px bottom padding
   is Figma's own (40px under the cards + 80px section padding) and lands the
   CTA at the same rhythm as the rest of the page.

   The cards deliberately reuse .cs-card-link / .cs-card-image: that is the
   pair js/home-morph.js binds to, so these cards get the site-wide
   card-to-hero portal morph for free. Only the box is re-specified here. */

.csr {
  width: 100%;
  max-width: var(--content-max-width);
  padding-bottom: 120px;
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.csr-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.csr-heading {
  font-family: var(--font-primary);
  font-size: 32px;
  font-weight: 500;
  color: var(--color-dark);
  line-height: 40px;
  letter-spacing: -1.92px;
}

.csr-see-all {
  flex-shrink: 0;
  font-family: var(--font-primary);
  font-size: 21px;
  font-weight: 400;
  color: var(--color-muted);
  line-height: 32px;
  letter-spacing: -0.63px;
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color 0.25s ease;
}

.csr-see-all:hover {
  color: var(--color-dark);
}

.csr-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
}

/* .cs-card-link is display:contents on the /case-studies listing, where the
   row is the grid. Here the link itself has to be the grid item. */
.csr-grid .cs-card-link {
  display: block;
  text-decoration: none;
  color: inherit;
}

.csr-card {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* Figma draws the fill 745x419 inside a 745x390 window, centred — which is
   what object-fit: cover on a 1.777 source in a 1.910 box already does. */
.csr-grid .cs-card-image {
  height: auto;
  aspect-ratio: 745 / 390;
  border-radius: 16px;
  background-color: #ededed;
}

/* Same hover as every other case-study card on the site: no lift, no dim, no
   scale — the frosted arrow puck from js/project-cursor.js is the whole
   affordance, so the OS cursor gets out of its way. Hover-capable pointers
   only: on touch there is nothing to replace it with. The .cs-card-link stays
   a real <a>, so keyboard focus is unaffected. */
@media (hover: hover) and (pointer: fine) {
  .csr-grid .cs-card-image {
    cursor: none;
  }
}

.csr-info {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.csr-client {
  font-family: var(--font-primary);
  font-size: 16px;
  font-weight: 400;
  color: var(--color-muted);
  line-height: 28px;
  letter-spacing: -0.32px;
}

.csr-title {
  font-family: var(--font-primary);
  font-size: 24px;
  font-weight: 500;
  color: var(--color-dark);
  line-height: 40px;
  letter-spacing: -0.96px;
}

@media (max-width: 1200px) {
  .csr {
    padding-bottom: 80px;
    gap: 32px;
  }

  .csr-title {
    font-size: 22px;
    line-height: 32px;
    letter-spacing: -0.88px;
  }
}

@media (max-width: 767px) {
  .csr {
    padding-bottom: 48px;
    gap: 24px;
  }

  .csr-head {
    align-items: baseline;
  }

  .csr-heading {
    font-size: 24px;
    line-height: 32px;
    letter-spacing: -1.2px;
  }

  .csr-see-all {
    font-size: 16px;
    line-height: 24px;
    letter-spacing: -0.32px;
  }

  .csr-grid {
    grid-template-columns: 1fr;
    gap: 32px;
  }

  .csr-card {
    gap: 16px;
  }

  .csr-title {
    font-size: 20px;
    line-height: 28px;
    letter-spacing: -0.6px;
  }
}
