/* ==========================================================================
   DaveDoesDesigns — Design tokens (from Figma Color collection)
   ========================================================================== */
:root {
  /* Color/bg */
  --bg-default: #0e011c;
  --bg-surface: #1e0c32;
  --bg-surface-2: #3d3350;

  /* Color/fg */
  --fg-default: #ffffff;
  --fg-on-light: #1e0c32;
  --fg-subtle: #978aaa;
  --fg-muted: #b3a0c8;

  /* Color/primary */
  --primary-default: #8200ff;
  --primary-contrast: #ffffff;

  /* Accents */
  --amber: #ffc20a;
  --amber-700: #a87a00;
  --rose: #f43f5e;
  --neutral-200: #ded7e6;
  --neutral-700: #3d3350;

  /* Color/hero (gradient overlay endpoints) */
  --hero-from: rgba(14, 1, 28, 0.49);
  --hero-to: rgba(14, 1, 28, 0);

  /* Theme-dependent chrome */
  --divider: rgba(151, 138, 170, 0.25);
  --divider-strong: rgba(151, 138, 170, 0.35);
  --ghost-border: rgba(255, 255, 255, 0.5);
  --input-bg: rgba(0, 0, 0, 0.2);

  /* Accent: amber in dark mode, primary purple in light mode */
  --accent: var(--amber);
  --accent-contrast: #1e0c32;
  --stat-icon: var(--amber-700);
  --tooltip-bg: rgba(255, 194, 10, 0.75);

  /* Type */
  --font-display: 'Gloock', serif;
  --font-body: 'Hanken Grotesk', sans-serif;

  /* Motion */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --cascade-duration: 600ms;
  --cascade-step: 200ms;

  --nav-height: 96px;
  --content-max: 1200px;
}

/* --- Light mode (values from the Figma Desktop - Light frame) ------------- */
:root[data-theme='light'] {
  --bg-default: #fbfafd;
  --bg-surface: #ffffff;
  --bg-surface-2: #f3f0f8;

  --fg-default: #1e0c32;
  --fg-muted: #5e5072;

  --primary-default: #6a00d1;

  --neutral-200: #3d3350;
  --neutral-700: #e9e5f0;

  --divider: rgba(30, 12, 50, 0.12);
  --divider-strong: rgba(30, 12, 50, 0.2);
  --ghost-border: rgba(30, 12, 50, 0.4);
  --input-bg: rgba(30, 12, 50, 0.04);

  /* Light mode: everything amber becomes primary purple */
  --accent: #8200ff;
  --accent-contrast: #ffffff;
  --stat-icon: #978aaa; /* solid muted purple (Figma light stat icons) */
  --tooltip-bg: rgba(130, 0, 255, 0.75);
}

/* --- Light-mode content card: Impact onwards sits in a white rounded
   shell spanning the full viewport width; invisible in dark mode -------- */
.content-shell {
  background: var(--bg-default);
}

:root[data-theme='light'] .content-shell {
  margin: 0 0 16px;
  border-radius: 30px;
  background: var(--bg-default);
  box-shadow: 0 24px 48px rgba(30, 12, 50, 0.18);
}

/* Light mode frame: the footer's near-black purple fills the gaps between
   the card edges, the footer, and the viewport so the rounded transition
   into the footer reads as one continuous surface */
html[data-theme='light'],
html[data-theme='light'] body {
  background: #0e011c;
}

/* Contrast fix for cards that would blend into the white shell */
:root[data-theme='light'] .cs-card { box-shadow: 0 16px 32px rgba(30, 12, 50, 0.08); }

/* Impact in light mode (Figma light frame): white stat cards with the
   border token, light-primary stat values, muted subheading */
:root[data-theme='light'] .stat { background: var(--bg-surface); }
:root[data-theme='light'] .stat__value { color: var(--primary-default); }
:root[data-theme='light'] .impact__sub { color: var(--fg-muted); }

/* Brief cross-fade while the theme switches (class added by JS) */
html.theme-fade,
html.theme-fade body,
html.theme-fade *:not(video):not(img) {
  transition: background-color 300ms ease, color 300ms ease,
    border-color 300ms ease, fill 300ms ease !important;
}

/* ==========================================================================
   Page transitions — a fixed veil over everything fades internal
   navigations out and page loads in (driven by main.js). --bg-default
   makes it white-ish in light mode and near-black in dark mode.
   ========================================================================== */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 4000;
  background: var(--bg-default);
  opacity: 0;
  pointer-events: none;
  transition: opacity 600ms ease;
}

/* Pre-paint state from the inline head script: the page arrives fully
   veiled; main.js drops the class after first paint to fade in */
html.page-enter body::after {
  opacity: 1;
  transition: none;
}

/* Exit: set on internal link clicks; swallows input while fading */
html.page-leave body::after {
  opacity: 1;
  pointer-events: auto;
}

@media (prefers-reduced-motion: reduce) {
  body::after { display: none; }
}

/* ==========================================================================
   Base
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

/* Anchor scrolling is handled by an eased rAF scroller in main.js
   (no CSS scroll-behavior, which would fight it) */

/* All font sizes are in rem, so scaling the root bumps every text size
   proportionally on smaller screens — friendlier reading on tablet/mobile
   without touching px-based layout (paddings, heights, gaps stay put) */
html { background: var(--bg-default); font-size: 16px; }

@media (max-width: 1024px) {
  html { font-size: 17px; } /* tablet: +6.25% */
}

@media (max-width: 640px) {
  html { font-size: 17.5px; } /* mobile: +9.4% */
}

body {
  margin: 0;
  background: var(--bg-default);
  color: var(--fg-default);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
}

body.menu-open { overflow: hidden; }

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

/* Every interactive element gets a pointer: nav links, CTAs, cards,
   tiles, social links, copy-email, hamburger, resume download.
   No mobile tap-highlight flash on any of them. */
a, button {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Native hash jumps mirror the JS scroller: 96px landing offset
   (Case Studies / Select Work land 24px deeper — no previous-section peek) */
section[id], footer[id] { scroll-margin-top: 96px; }
#case-studies, #select-work { scroll-margin-top: 72px; }

/* ==========================================================================
   Sticky navigation
   ========================================================================== */
.site-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* viewport-fit=cover pages (the homepage): the nav starts below the
     status bar / Dynamic Island; its scrims still paint behind it.
     env() is 0 everywhere else, so this is inert on other pages. */
  padding-top: env(safe-area-inset-top, 0px);
  z-index: 100;
  opacity: 0;
  transform: translateY(-12px);
  transition: opacity 400ms ease-out, transform 400ms ease-out;
  pointer-events: none;
}

/* Readability scrims ride with the nav (they are part of it, so they hide
   and return with the nav's own show/hide animation). Absolutely positioned
   and bleeding 56px past the nav's bottom edge for a soft fade-out — the
   nav's height, padding, and layout are untouched. Two layers crossfade so
   the intensity shift over light sections animates smoothly. */
.site-nav::before,
.site-nav::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: calc(100% + 56px);
  z-index: -1;
  pointer-events: none;
  transition: opacity 400ms ease;
}

/* Dark scrim (darkest at the very top): keeps white text readable over
   dark or busy backgrounds */
.site-nav::before {
  background: linear-gradient(180deg, var(--hero-from) 0%, var(--hero-to) 100%);
}

/* Light-section scrim: same plum, low alpha — enough separation for dark
   text without muddying the section behind */
.site-nav::after {
  background: linear-gradient(180deg, rgba(30, 12, 50, 0.12) 0%, rgba(30, 12, 50, 0) 100%);
  opacity: 0;
}

/* Fade in from top on page load, fade back in on scroll up */
.site-nav.is-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* After the load animation, scroll show/hide runs at 600ms both ways */
.site-nav.is-loaded {
  transition: opacity 600ms ease-out, transform 600ms ease-out;
}

/* The readability scrims look odd over the full-screen menu panel */
body.menu-open .site-nav::before,
body.menu-open .site-nav::after { opacity: 0; }

/* Scroll down: slide up and out while fading (reverse of the load animation).
   Removing this class slides it back down from the top while fading in. */
.site-nav.is-hidden {
  opacity: 0;
  transform: translateY(-100%);
  pointer-events: none;
}

.site-nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 32px;
  height: var(--nav-height);
  padding: 0 48px;
}

/* Bare gradient D in its own frosted tile — the glass adapts to the
   backdrop (via .on-light below), so one mark serves both themes. Squared
   off at 16px against the pill-shaped capsules, and flex centering keeps
   the D on the same midline as the links. */
.site-nav__logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 68px;
  height: 68px;
  flex-shrink: 0;
  border-radius: 16px;
  background: rgba(30, 12, 50, 0.35);
  border: 1px solid rgba(255, 255, 255, 0.12);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  backdrop-filter: blur(18px) saturate(1.4);
  transition: background 400ms ease, border-color 400ms ease,
    backdrop-filter 400ms ease, -webkit-backdrop-filter 400ms ease;
}

.site-nav__logo img {
  display: block;
  height: 38px;
  width: auto;
  transition: height 400ms ease, filter 400ms ease;
}

/* Logo far left, links centered, Contact far right */
/* Frosted glass capsules: a 999px radius (greater than the height, so it
   renders as a full pill) plus backdrop blur keeps the text legible over
   busy backdrops. The links group hugs its content — the inner flexbox's
   space-between splits the leftover space evenly, so it stays centered
   between the logo and the actions cluster. */
.site-nav__links,
.site-nav__actions {
  border-radius: 999px;
  background: rgba(30, 12, 50, 0.35);
  border: 1px solid rgba(255, 255, 255, 0.12);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  backdrop-filter: blur(18px) saturate(1.4);
  transition: background 400ms ease, border-color 400ms ease,
    backdrop-filter 400ms ease, -webkit-backdrop-filter 400ms ease;
}

/* Frosted white over light sections — same signal as the link colors */
.site-nav.on-light .site-nav__logo,
.site-nav.on-light .site-nav__links,
.site-nav.on-light .site-nav__actions,
.site-nav.on-light .nav-toggle {
  background: rgba(255, 255, 255, 0.55);
  border-color: rgba(30, 12, 50, 0.1);
}

/* Over a hero marked data-nav-bare (the home page only) the nav rides bare,
   as it did before the glass: the hero art carries the composition and the
   capsules would compete with it. main.js toggles .is-over-hero from the
   same section probe that drives .on-light, so the glass returns the moment
   content scrolls under the nav. Only paint properties change — the border
   goes transparent rather than away — so nothing shifts on the toggle.
   Must stay after the .on-light block: equal specificity, source order wins. */
.site-nav.is-over-hero .site-nav__logo,
.site-nav.is-over-hero .site-nav__links,
.site-nav.is-over-hero .site-nav__actions,
.site-nav.is-over-hero .nav-toggle {
  background: transparent;
  border-color: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Without its tile the mark has to hold that corner on its own, so it
   sizes up over the hero. It still fits inside the 68px tile box, so
   gaining the tile on scroll rescales the mark without moving anything. */
.site-nav.is-over-hero .site-nav__logo img { height: 46px; }

/* Light mode, dark tile (a case study hero, say): the gradient mark goes
   solid white so it reads as cleanly as the white links beside it. The
   art is an external SVG, so recoloring goes through a filter — flatten
   to black, then invert to white. Bare and .on-light states keep the
   gradient, since there is no dark tile under it in either. */
:root[data-theme='light'] .site-nav:not(.on-light):not(.is-over-hero) .site-nav__logo img {
  filter: brightness(0) invert(1);
}

.site-nav__links {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 44px;
  height: 56px;
  padding: 0 36px;
}

/* Theme toggle + Contact share one capsule */
.site-nav__actions {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 5px 5px 9px; /* 44px controls + 10px + 2px border = the links capsule's 56px */
  flex-shrink: 0;
}

/* --- Nav link with mask-reveal hover ------------------------------------ */
/* Nav color follows what's BEHIND the nav (hero video is always dark),
   not the global theme — .on-light flips it over light sections */
.nav-link {
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.4;
  color: #ffffff;
  text-decoration: none;
  transition: color 300ms ease;
}

.nav-link__mask {
  display: inline-flex;
  flex-direction: column;
  height: 1.4em;
  overflow: hidden;
}

.nav-link__text {
  display: block;
  transition: transform 350ms var(--ease-out);
}

/* Current text slides up and out; duplicate slides up from below */
.nav-link:hover .nav-link__text,
.nav-link:focus-visible .nav-link__text {
  transform: translateY(-100%);
}

.nav-link__text--hover {
  color: var(--amber);
}

/* Contact styled as an outlined button, per the design */
.nav-link--contact {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  padding: 0 24px;
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 999px; /* capsule, nests concentric inside the actions pill */
  flex-shrink: 0;
  transition: border-color 300ms ease, color 300ms ease;
}

/* Button-style hover: text slides up like the other links (revealed copy
   goes amber like the rest of the nav), border brightens */
.nav-link--contact:hover { border-color: var(--fg-default); }

/* --- Active section link ------------------------------------------------- */
/* The link for the section currently in view (clicked or scrolled to)
   holds purple; Home holds amber. Placed after the .on-light overrides
   below would tie on specificity, so both backdrops are covered here. */
.site-nav .nav-link.is-active,
.site-nav.on-light .nav-link.is-active { color: var(--primary-default); }
.site-nav .nav-link.is-active[href='#hero'],
.site-nav.on-light .nav-link.is-active[href='#hero'] { color: var(--amber); }
.site-nav .nav-link--contact.is-active,
.site-nav.on-light .nav-link--contact.is-active { border-color: var(--primary-default); }

/* --- Nav on light backgrounds: dark plum text --------------------------- */
.site-nav.on-light .nav-link { color: var(--fg-on-light); }
.site-nav.on-light .nav-link__text--hover { color: var(--accent); }
.site-nav.on-light .nav-link--contact { border-color: rgba(30, 12, 50, 0.5); }
.site-nav.on-light .nav-toggle__bar { background: var(--fg-on-light); }
.site-nav.on-light::before { opacity: 0; }
.site-nav.on-light::after { opacity: 1; }

/* Open mobile menu in light mode has a white panel behind the X — the
   theme toggle beside it needs the same flip or it goes white-on-white */
:root[data-theme='light'] .nav-toggle.is-open .nav-toggle__bar {
  background: var(--fg-on-light);
}

:root[data-theme='light'] body.menu-open .theme-toggle {
  color: var(--fg-on-light);
}

/* --- Theme toggle -------------------------------------------------------- */
/* Sits beside Contact on desktop; when the links and Contact drop out at
   1024 it lands beside the hamburger, and the icon grows to match its 26px */
.theme-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: #ffffff; /* over dark backgrounds; .on-light flips it */
  cursor: pointer;
  flex-shrink: 0;
  transition: color 300ms ease, transform 200ms var(--ease-out);
}

.site-nav.on-light .theme-toggle { color: var(--fg-on-light); }

.theme-toggle:hover,
.theme-toggle:focus-visible {
  color: var(--amber);
  transform: scale(1.08);
}

.site-nav.on-light .theme-toggle:hover,
.site-nav.on-light .theme-toggle:focus-visible { color: var(--primary-default); }

.theme-toggle__icon { width: 22px; height: 22px; }

/* Show the icon of the mode you would switch TO */
.theme-toggle__icon--sun { display: none; }
:root[data-theme='dark'] .theme-toggle__icon--sun { display: block; }
:root[data-theme='dark'] .theme-toggle__icon--moon { display: none; }

/* Tooltip hangs below the button, since the nav is pinned to the top */
.theme-toggle[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  top: calc(100% - 2px);
  right: 0;
  padding: 7px 10px;
  border-radius: 6px;
  background: #0e011c;
  color: #ffffff;
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  box-shadow: 0 4px 12px rgba(14, 1, 28, 0.35);
  opacity: 0;
  transform: translateY(-4px);
  pointer-events: none;
  transition: opacity 160ms ease, transform 160ms var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .theme-toggle:hover::after,
  .theme-toggle:focus-visible::after {
    opacity: 1;
    transform: translateY(0);
    transition-delay: 300ms; /* same dwell as the gallery chips */
  }
}

/* --- Hamburger toggle ---------------------------------------------------- */
/* Frosted glass disc matching the nav capsules (mobile only — display:none
   here, flex at the 1024 breakpoint) */
.nav-toggle {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 999px;
  background: rgba(30, 12, 50, 0.35);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  backdrop-filter: blur(18px) saturate(1.4);
  cursor: pointer;
  flex-shrink: 0;
  transition: background 400ms ease, border-color 400ms ease,
    backdrop-filter 400ms ease, -webkit-backdrop-filter 400ms ease;
}

/* No color shifts on tap/focus/hover — only the hamburger <-> X motion */
.nav-toggle:hover,
.nav-toggle:focus,
.nav-toggle:active {
  outline: none;
}

.nav-toggle__bar {
  display: block;
  width: 26px;
  height: 2px;
  border-radius: 2px;
  background: #ffffff; /* over dark backgrounds; .on-light flips it */
  /* Closed-state transition = the CLOSING leg: un-rotate the X first,
     then glide the bars back apart (middle bar fades in at the end) */
  transition: rotate 200ms var(--ease-out), translate 200ms var(--ease-out) 160ms,
    opacity 160ms ease 200ms, background-color 300ms ease;
}

/* Hamburger -> X in two beats: outer bars glide to center, THEN rotate.
   translate/rotate are separate properties (not one transform) so each leg
   runs on its own clock — a combined transform swings the bars on a curve. */
.nav-toggle.is-open .nav-toggle__bar {
  transition: translate 200ms var(--ease-out), rotate 200ms var(--ease-out) 160ms,
    opacity 160ms ease, background-color 300ms ease;
}

.nav-toggle.is-open .nav-toggle__bar:nth-child(1) { translate: 0 9px; rotate: 45deg; }
.nav-toggle.is-open .nav-toggle__bar:nth-child(2) { opacity: 0; }
.nav-toggle.is-open .nav-toggle__bar:nth-child(3) { translate: 0 -9px; rotate: -45deg; }

/* ==========================================================================
   Mobile menu — full-screen plum, slides down from top
   ========================================================================== */
.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--bg-surface);
  transform: translateY(-100%);
  visibility: hidden;
  transition: transform 450ms var(--ease-out), visibility 0s linear 450ms;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Bleed the panel past the viewport top/bottom so iOS Safari's translucent
   status bar and URL bar sample the panel, not the page behind it. Rides
   inside the panel (moves with the slide), behind its own background.
   main.js pairs this with a theme-color swap for the solid chrome tint. */
.mobile-menu::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -120px;
  bottom: -120px;
  z-index: -1;
  background: var(--bg-surface);
}

.mobile-menu.is-open {
  transform: translateY(0);
  visibility: visible;
  transition: transform 450ms var(--ease-out), visibility 0s;
}

.mobile-menu__nav {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
}

.mobile-menu__link {
  font-family: var(--font-display);
  font-size: 2.125rem;
  letter-spacing: 0.8px;
  color: var(--fg-default);
  text-decoration: none;
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 400ms ease-out, transform 400ms var(--ease-out), color 200ms ease;
}

/* Cascade in, staggered top to bottom */
.mobile-menu.is-open .mobile-menu__link {
  opacity: 1;
  transform: translateY(0);
  transition-delay: calc(150ms + var(--i) * 70ms);
}

.mobile-menu__link:hover { color: var(--accent); }

/* ==========================================================================
   Hero
   ========================================================================== */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 100vh;
  min-height: 100svh;
  overflow: hidden;
  /* The hero stays dark in both themes (dark video background);
     light mode only applies from the Impact section onwards */
  --bg-default: #0e011c;
  --bg-surface: #1e0c32;
  --fg-default: #ffffff;
  --fg-muted: #b3a0c8;
  --fg-subtle: #978aaa;
  --ghost-border: rgba(255, 255, 255, 0.5);
  --primary-default: #8200ff;
  --accent: #ffc20a;
  --accent-contrast: #1e0c32;
}

.hero__media {
  position: absolute;
  /* Bleed 1px past both side edges: iOS Safari can rasterize the layer a
     hair short of the viewport at fractional DPRs, leaving a bare hairline
     on the right. The hero's overflow:hidden clips the bleed, so this has
     zero visual effect beyond closing that seam. */
  inset: 0 -1px;
  background: var(--bg-default);
}

.hero__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Bottom gradient keeps hero text readable (values from Figma
   Color/hero/from -> Color/hero/to); the top gradient lives on the nav */
.hero__gradient {
  position: absolute;
  /* Same 1px side bleed as .hero__media: the gradient is the visible
     purple, so it must be the layer that reaches past the rounding. */
  inset: 0 -1px;
  background: linear-gradient(180deg, var(--hero-to) 50%, var(--bg-default) 93.7%);
}

/* Figma hero (1920-768): a fluid wrapping row anchored to the bottom.
   .hero__main (name + copy, each 560px) takes the remaining space and
   wraps internally; the 546px CTA row wraps below it on narrow widths */
.hero__content {
  position: relative;
  width: 100%;
  padding: calc(var(--nav-height) + 24px + env(safe-area-inset-top, 0px)) 48px 48px;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  align-content: flex-end;
  gap: 36px 64px;
}

.hero__main {
  flex: 1 1 560px;
  min-width: min(560px, 100%);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 24px 164px;
}

.hero__name {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 7rem;
  line-height: 1;
  letter-spacing: 0.018em;
  color: var(--fg-default);
  white-space: nowrap;
  width: 560px;
  max-width: 100%;
}

/* Copy column: baseline of the body copy aligns with the name's baseline.
   flex-end aligns box bottoms; the margin compensates for the difference
   between the name's descender depth and the copy's descender+half-leading */
.hero__copy {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 560px;
  max-width: 100%;
  /* Raise the copy so its last baseline matches the name's baseline:
     the name's descender (~0.21em of the clamped name size) reaches
     deeper than the copy's descender + half-leading (~8px) */
  margin-bottom: clamp(8px, 0.7vw, 13px);
}

.hero__sub {
  margin: 0;
  font-size: 1.625rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--fg-default);
}

.hero__desc {
  margin: 0;
  max-width: 640px;
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--fg-subtle);
}

/* Desktop wrapped state (copy stacked under the name, CTA to the right):
   the copy centers in its band — 24px from the name above, 24px to the
   button line below. Above ~1750px the copy sits beside the name on the
   shared baseline instead, so the nudge doesn't apply there. */
@media (min-width: 769px) and (max-width: 1750px) {
  .hero__copy { margin-bottom: 24px; }
}

/* Buttons + scroll hint share one 546px row that wraps as a unit:
   buttons side-by-side at Figma's fixed 180px, hint inline to the right */
.hero__actions {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 36px;
  width: max-content;
  max-width: 100%;
}

.hero__ctas {
  display: flex;
  flex-wrap: nowrap;
  gap: 16px;
}

.hero__ctas .btn {
  width: 180px;
  min-width: 0;
  padding: 0 8px;
  white-space: nowrap;
}

/* --- Buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  min-width: 180px;
  padding: 0 24px;
  border-radius: 8px;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.4;
  text-decoration: none;
  transition: background 250ms ease, border-color 250ms ease, transform 250ms var(--ease-out);
}

.btn:hover { transform: translateY(-2px); }

.btn--primary {
  background: var(--primary-default);
  color: var(--primary-contrast);
}

.btn--primary:hover { background: #9226ff; }

.btn--ghost {
  border: 1px solid var(--ghost-border);
  color: var(--fg-default);
}

.btn--ghost:hover { border-color: var(--fg-default); }

/* --- Scroll prompt -------------------------------------------------------- */
.hero__scroll {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  white-space: nowrap;
  gap: 16px;
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--amber);
  text-decoration: none;
  transition: color 250ms ease;
}

/* Scale lives on the span: the link itself and the arrow both have
   animations pinning their transform (cascade fill / bounce) */
.hero__scroll span {
  display: inline-block;
  transition: transform 250ms var(--ease-out);
}

.hero__scroll:hover,
.hero__scroll:focus-visible {
  color: var(--accent);
}

.hero__scroll:hover span,
.hero__scroll:focus-visible span {
  transform: scale(1.04);
}

.hero__scroll-arrow {
  color: var(--amber);
  animation: arrow-bounce 1s ease-in-out infinite;
}

@keyframes arrow-bounce {
  0%, 100% { transform: translateY(-3px); }
  50% { transform: translateY(3px); }
}

/* ==========================================================================
   Cascade-in on page load (fade up from below, overlapping stagger)
   ========================================================================== */
.cascade {
  opacity: 0;
  transform: translateY(20px);
  animation: cascade-in var(--cascade-duration) ease-out forwards;
  animation-delay: calc(200ms + var(--i) * var(--cascade-step));
}

@keyframes cascade-in {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ==========================================================================
   Impact section
   ========================================================================== */
.impact {
  padding: 144px 48px 72px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 72px;
  scroll-margin-top: var(--nav-height);
}

.impact__card {
  width: 100%;
  max-width: var(--content-max);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 48px;
  /* Fades in first when the section enters the viewport */
  opacity: 0;
  transition: opacity 600ms ease-out;
}

.impact.in-view .impact__card { opacity: 1; }

.impact__header {
  max-width: 680px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
}

.impact__eyebrow {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 1.56px;
  text-transform: uppercase;
  color: var(--accent);
}

.impact__headline {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.875rem, 4vw, 2.75rem);
  line-height: 1.08;
  letter-spacing: 0.8px;
  color: var(--fg-default);
}

.impact__sub {
  margin: -8px 0 0;
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--fg-default);
}

.impact__grid {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 24px;
}

.stat {
  background: var(--neutral-700);
  border: 1px solid var(--neutral-700);
  border-radius: 8px;
  padding: 20px;
  min-height: 152px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.stat__icon {
  width: 28px;
  height: 28px;
  color: var(--stat-icon);
  margin-bottom: 4px;
}

.stat__value {
  margin: 0;
  font-size: 1.625rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--accent);
}

.stat__desc {
  margin: 0;
  font-size: 1rem;
  line-height: 1.4;
  color: var(--fg-default);
}

/* --- Scroll-triggered cascade (delays set per element via --d) ----------- */
[data-cascade] {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 600ms ease-out, transform 600ms ease-out;
  transition-delay: var(--d, 0ms);
}

[data-cascade-fade] {
  opacity: 0;
  transition: opacity 600ms ease-out;
  transition-delay: var(--d, 0ms);
}

.in-view [data-cascade] {
  opacity: 1;
  transform: translateY(0);
}

.in-view [data-cascade-fade],
.in-view[data-cascade-fade] { opacity: 1; }

/* ==========================================================================
   Case Studies section
   ========================================================================== */
.case-studies {
  padding: 72px 0;
  display: flex;
  flex-direction: column;
  gap: 48px;
  scroll-margin-top: var(--nav-height);
}

.cs-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
  padding: 0 48px;
}

.cs-header__eyebrow {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 1.56px;
  text-transform: uppercase;
  color: var(--accent);
}

.cs-header__headline {
  margin: 0;
  max-width: 680px;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.875rem, 4vw, 2.75rem);
  line-height: 1.08;
  letter-spacing: 0.8px;
  color: var(--fg-default);
}

.cs-header__sub {
  margin: -8px 0 0;
  max-width: 680px;
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--fg-subtle);
}

/* --- Sticky stack --------------------------------------------------------- */
.cs-stack {
  display: flex;
  flex-direction: column;
  gap: 48px;
  padding: 0 48px;
  /* Cards cap at 1600px on wide screens (1600 + 2*48 side padding, border-box) */
  width: 100%;
  max-width: 1696px;
  margin-inline: auto;
}

/* Card wrapper: fades in on first view (kept separate from the parallax
   styles, which are applied to .cs-card by JS) */
.cs-item {
  opacity: 0;
  transition: opacity 600ms ease-out;
}

.cs-item.in-view { opacity: 1; }

.cs-card {
  display: flex;
  border-radius: 20px;
  overflow: hidden;
  background: var(--bg-surface);
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  will-change: transform, filter;
  transform-origin: center top;
}

/* Every breakpoint: cards pin and get covered by the next one
   (scale/blur recession applied by JS, tied to scroll position) */
.cs-item {
  position: sticky;
  top: 16px;
}

/* Client / info / stats sit as one centered group, matching the case study
   hero's .csp-hero__details. `center` (not space-between) is what keeps the
   stats tight under the category tags instead of pinned to the card floor —
   see the desktop block for the 36px gap that completes the match. On
   tablet/mobile the panel is content-height, so this is a no-op there. */
.cs-card__details {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 24px;
  padding: 36px;
  background: var(--bg-surface);
}

.cs-card__client {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--fg-muted);
}

.cs-card__info {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.cs-card__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(2.125rem, 3.5vw, 3.5rem);
  line-height: 1.05;
  letter-spacing: 1px;
  color: var(--fg-default);
}

.cs-card__desc {
  margin: 0;
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--neutral-200);
}

.cs-card__tags {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.cs-card__tags li {
  border: 1.5px solid var(--neutral-700);
  border-radius: 64px;
  padding: 4px 8px;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.5;
  color: var(--fg-subtle);
  white-space: nowrap;
}

.cs-card__stats {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.cs-card__stats li {
  position: relative;
  padding-left: 16px;
  font-size: 1rem;
  line-height: 1.4;
  color: var(--fg-subtle);
}

.cs-card__stats li::before {
  content: '\2022';
  position: absolute;
  left: 0;
  color: var(--primary-default);
}

.cs-card__media {
  flex: 1;
  min-width: 0;
  position: relative;
}

.cs-card__media img,
.cs-card__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* --- Hero carousel: stacked slides cross-fade via .is-active (JS-timed).
   All slides are absolute, so the media container keeps its own size
   (desktop stretch / mobile aspect-ratio) and the card footprint is
   unchanged --------------------------------------------------------------- */
.cs-carousel img {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1000ms ease;
}

.cs-carousel img.is-active { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  /* JS never rotates slides; the first stays visible with no fade */
  .cs-carousel img { transition: none; }
}

/* --- Mouse-tracking tooltip (created by JS, desktop pointers only) -------- */
.cs-tooltip {
  position: fixed;
  z-index: 200;
  padding: 8px 14px;
  border-radius: 999px;
  background: var(--tooltip-bg);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: var(--accent-contrast);
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  /* Position is driven by a rAF lerp in JS via transform (compositor-only,
     no per-frame layout) — only opacity transitions in CSS */
  left: 0;
  top: 0;
  will-change: transform;
  transition: opacity 200ms ease;
}

.cs-tooltip.is-shown { opacity: 1; }

/* --- Desktop: alternating layout + sticky stack --------------------------- */
@media (min-width: 1025px) {
  .cs-card { height: 768px; }

  /* Details panel LEFT / image RIGHT; odd cards flip, so the stack opens
     image-left (1st: image left, 2nd: image right, 3rd: image left) */
  .cs-item:nth-child(odd) .cs-card { flex-direction: row-reverse; }

  /* 420px wide on a 36px gap — the same panel metrics as the case study
     hero's .csp-hero__details, so a card and the hero it opens read alike */
  .cs-card__details {
    width: 420px;
    flex-shrink: 0;
    gap: 36px;
  }

  /* Fixed-height cards center in the viewport when pinned */
  .cs-item { top: max(24px, calc(50vh - 384px)); }
}

/* --- Tablet / mobile: single column, tap feedback, no sticky -------------- */
@media (max-width: 1024px) {
  .cs-stack { padding: 0 32px; }
  .cs-header { padding: 0 32px; }

  .cs-card { flex-direction: column; }

  /* Image on top, details below */
  .cs-card__media {
    order: -1;
    aspect-ratio: 16 / 10;
    flex: none;
  }

  /* Category tags are desktop-only */
  .cs-card__tags { display: none; }
}

/* Tap feedback is instant (no transition — it would fight the per-frame
   parallax transforms now applied on every breakpoint) */
@media (hover: none) {
  .cs-card:active { transform: scale(0.98); opacity: 0.9; }
}

/* ==========================================================================
   Select Work section
   ========================================================================== */
.select-work {
  padding: 72px 48px 144px;
  scroll-margin-top: var(--nav-height);
}

.sw-inner {
  max-width: var(--content-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 48px;
}

.sw-header {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
  max-width: 680px;
}

.sw-header__eyebrow {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 1.56px;
  text-transform: uppercase;
  color: var(--accent);
}

.sw-header__headline {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.875rem, 4vw, 2.75rem);
  line-height: 1.08;
  letter-spacing: 0.8px;
  color: var(--fg-default);
}

.sw-header__sub {
  margin: -8px 0 0;
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--fg-subtle);
}

/* --- Tile grid ------------------------------------------------------------ */
.sw-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
}

/* Tiles are <button>s while the work is unpublished, so reset the UA
   button chrome (border, font, centering) back to the card look */
.sw-tile {
  --pill-offset: 48px; /* pill height + gap; how far the text slides up */
  --fg-default: #ffffff; /* text sits on a darkened image in both themes */
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  height: 300px;
  padding: 12px;
  border: 0;
  border-radius: 12px;
  overflow: hidden;
  background: var(--bg-surface);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  filter: drop-shadow(0 16px 8px rgba(0, 0, 0, 0.1));
}

.sw-tile__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.sw-tile__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0) 65%, rgba(0, 0, 0, 0.6) 100%);
}

.sw-tile__content {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}

.sw-tile__client {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--fg-default);
}

.sw-tile__title {
  margin: 0;
  font-size: 1.625rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--fg-default);
}

/* "Coming soon" badge — pinned to the tile's top-right, above image + scrim */
.sw-tile__badge {
  position: absolute;
  top: 12px;
  right: 12px;
  padding: 4px 10px;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 64px;
  background: rgba(30, 12, 50, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: var(--fg-default);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.4px;
  line-height: 1.5;
  white-space: nowrap;
}

/* "View work" pill — greyed out while the work is still coming soon
   (was translucent amber; restore --amber when the tiles go live) */
.sw-tile__pill {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 16px;
  border-radius: 24px;
  background: rgba(255, 255, 255, 0.18);
  color: #ded7e6;
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.4;
}

.sw-tile__pill svg {
  width: 16px;
  height: 16px;
}

/* Desktop: text sits at the tile bottom; on hover it slides up and the
   pill fades into the space below it */
@media (hover: hover) and (pointer: fine) {
  .sw-tile__content {
    transition: transform 300ms var(--ease-out);
  }

  .sw-tile__pill {
    opacity: 0;
    transition: opacity 200ms ease-out;
  }

  .sw-tile:hover .sw-tile__content,
  .sw-tile:focus-visible .sw-tile__content {
    transform: translateY(calc(-1 * var(--pill-offset)));
  }

  .sw-tile:hover .sw-tile__pill,
  .sw-tile:focus-visible .sw-tile__pill {
    opacity: 1;
  }
}

/* Touch: pill always visible, so the text permanently sits above it */
@media (hover: none) {
  .sw-tile__content {
    transform: translateY(calc(-1 * var(--pill-offset)));
  }

  .sw-tile {
    transition: transform 150ms ease, opacity 150ms ease;
  }

  .sw-tile:active {
    transform: scale(0.98);
    opacity: 0.9;
  }
}

/* --- Responsive ------------------------------------------------------------ */
@media (max-width: 1024px) {
  .select-work { padding: 72px 32px 144px; }
  .sw-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sw-tile { height: 260px; }
}

@media (max-width: 640px) {
  .select-work { padding: 72px 16px 96px; }
  .sw-inner { gap: 32px; }
  .sw-grid { gap: 16px; }
  .sw-tile { height: 200px; --pill-offset: 38px; }
  .sw-tile__title { font-size: 1.0625rem; }
  .sw-tile__pill { font-size: 0.8125rem; padding: 6px 12px; }
  .sw-tile__badge { top: 10px; right: 10px; font-size: 0.6875rem; padding: 3px 8px; }
  .sw-header__sub { font-size: 1.0625rem; }
}

/* Phone widths: with no hover, badge + client + title + pill all stacked on
   the image at once cover most of it. Keep only the title (and the badge
   where it applies) — the whole tile is the tap target, so the pill's
   affordance is redundant, and the existing bottom scrim keeps the title
   readable. Must stay after the (hover: none) block: it undoes that
   block's permanent pill-offset translate. */
@media (max-width: 480px) {
  .sw-tile__pill,
  .sw-tile__client { display: none; }
  .sw-tile__content { transform: none; }
}

/* ==========================================================================
   About + Skills section
   ========================================================================== */
.about {
  padding: 0 48px 72px;
  scroll-margin-top: var(--nav-height);
}

.about-inner {
  max-width: var(--content-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 144px;
}

/* --- About: image left, details right ------------------------------------- */
.about-main {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 48px;
  align-items: stretch;
}

.about-media {
  overflow: hidden;
  border-radius: 12px;
  box-shadow: 0 16px 16px rgba(0, 0, 0, 0.1);
}

.about-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.about-details {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
}

.about-eyebrow {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 1.56px;
  text-transform: uppercase;
  color: var(--accent);
}

.about-headline {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.875rem, 4vw, 2.75rem);
  line-height: 1.08;
  letter-spacing: 0.8px;
  color: var(--fg-default);
}

.about-sub {
  margin: -8px 0 0;
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--fg-subtle);
}

.about-bio {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 20px;
}

.about-bio p {
  margin: 0;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--fg-subtle);
}

.about-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 20px;
}

/* --- Skills ---------------------------------------------------------------- */
.skills {
  display: flex;
  flex-direction: column;
  gap: 64px;
  scroll-margin-top: var(--nav-height);
}

.skills-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
  max-width: 720px;
  margin: 0 auto;
}

.skills-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 48px;
}

.skill-card__name {
  margin: 0;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--divider-strong);
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--fg-subtle);
}

.skill-card ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.skill-card li {
  padding: 8px 0;
  border-bottom: 1px solid var(--divider);
  font-size: 1rem;
  line-height: 1.4;
  color: var(--fg-subtle);
}

/* --- Responsive ------------------------------------------------------------ */
@media (max-width: 1024px) {
  .about { padding: 0 32px 96px; }
  .about-inner { gap: 144px; }

  /* Single column: image on top, details below.
     Figma tablet frame: full-width x 362px (wide rectangle) */
  .about-main { grid-template-columns: 1fr; }
  .about-media { height: 362px; }
  /* Letterbox the square portrait so the whole face fits the 362px band,
     flush to the left edge. transform: none disables the parallax zoom
     here — it would re-crop the contained image; main.js skips the band
     in this range. */
  .about-media {
    position: relative;
    background: #000;
  }
  .about-media img {
    object-fit: contain;
    object-position: left center;
    transform: none;
  }

  /* Black veil over the empty right side, fading out leftward: solid to
     the 50% mark, gone by 70%, so it clears the face before the nose. */
  .about-media::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(to left, #000 50%, rgba(0, 0, 0, 0) 70%);
    pointer-events: none;
  }

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

@media (max-width: 640px) {
  .about { padding: 0 16px 96px; }
  .about-inner { gap: 96px; }
  .about-sub { font-size: 1.0625rem; }

  /* Figma mobile frame: full-width square */
  .about-media { height: auto; aspect-ratio: 1 / 1; }
  /* Square crop again: undo the tablet band's letterbox (contain, flush-left,
     black veil and no transform all cascade down). No parallax on phones
     (main.js skips ≤1024 too), so no scale-up bleed — the square portrait
     sits static at its natural crop. */
  .about-media { background: none; }
  .about-media::after { content: none; }
  .about-media img {
    object-fit: cover;
    object-position: 50% 50%;
    transform: none;
  }
  .skills-grid { grid-template-columns: 1fr; }
  .about-ctas { width: 100%; }
  /* max-content keeps each button at least as wide as its label, so
     "Download Resume" never breaks across two lines; the pair sits side by
     side while both fit and wraps to a stacked full-width pair when it
     doesn't (roughly below 430px) */
  .about-ctas .btn {
    flex: 1 1 auto;
    min-width: max-content;
    white-space: nowrap;
  }
}

/* ==========================================================================
   Trusted By logo cloud
   ========================================================================== */
/* Top padding completes the 144px section rhythm against .about's 72px
   bottom padding; the larger bottom padding is the content shell's closing
   inset, giving the footer room to read as a separate block */
.trusted {
  padding: 72px 48px 144px;
  scroll-margin-top: var(--nav-height);
}

.trusted-inner {
  max-width: var(--content-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 64px;
}

.trusted-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
  max-width: 720px;
  margin: 0 auto;
}

/* --- Marquee: two identical groups slide left; when the first group has
   fully exited (-50% of the track) the loop restarts invisibly ----------- */
.logo-marquee {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}

.logo-marquee__track {
  display: flex;
  width: max-content;
  will-change: transform;
}

/* Only scroll once JS has cloned the group; see main.js */
.logo-marquee.is-looping .logo-marquee__track {
  animation: logo-marquee 45s linear infinite;
}

/* Touch devices: main.js drives the transform instead (same speed, but a
   finger can drag the strip); the keyframe animation would fight it.
   pan-y leaves vertical page scrolling native while we own horizontal. */
.logo-marquee.is-touch-ticker { touch-action: pan-y; }
.logo-marquee.is-touch-ticker .logo-marquee__track { animation: none; }

.logo-marquee:hover .logo-marquee__track {
  animation-play-state: paused;
}

.logo-marquee__group {
  display: flex;
  align-items: center;
  gap: 72px;
  padding: 0 36px;
  margin: 0;
  list-style: none;
}

/* Grayscale always, in both themes. Works on SVG and PNG alike.

   grayscale() rather than brightness(0), because a flat silhouette erases
   any detail inside the mark: knockouts fill in and two-part logos merge
   into a blob. Grayscale keeps the tonal separation, so a knocked-out
   letterform still reads.

   Most logos ship as dark art for light backgrounds, so dark mode adds
   invert() to flip them light while keeping those internal relationships
   intact (a dark mark with a white knockout becomes a light mark with a
   dark knockout, which is the same drawing). Light mode needs no flip and
   uses opacity to settle the art at mid gray without crushing blacks. */
.logo-marquee img {
  display: block;
  /* Per-logo optical sizing: square marks need more height than wide
     wordmarks to carry equal weight. Override with style="--logo-h: 54px"
     on the <li>. */
  height: var(--logo-h, 44px);
  width: auto;
  filter: grayscale(1) invert(1) brightness(1.08);
  opacity: 0.72;
}

:root[data-theme='light'] .logo-marquee img {
  filter: grayscale(1);
  opacity: 0.55;
}

/* Escape hatch for artwork that is already white/light: it needs the
   flip in light mode instead of dark. Set data-art="light" on the img */
.logo-marquee img[data-art='light'] {
  filter: grayscale(1);
}

:root[data-theme='light'] .logo-marquee img[data-art='light'] {
  filter: grayscale(1) invert(1) brightness(0.92);
}

/* Empty slot: declared in markup, or applied by main.js when a slot's
   file cannot be found in any supported format */
.logo-marquee li.is-empty {
  display: grid;
  place-items: center;
  width: 160px;
  height: 44px;
  border: 2px dashed var(--divider-strong);
  border-radius: 8px;
}

.logo-marquee li.is-empty img { display: none; }

.logo-marquee li.is-empty::after {
  content: 'SLOT ' attr(data-slot);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 2.5px;
  color: var(--fg-subtle);
}

@keyframes logo-marquee {
  to { transform: translateX(-50%); }
}

@media (max-width: 1024px) {
  /* .about's bottom padding grows to 96px here, so 48px completes the 144px */
  .trusted { padding: 48px 32px 120px; }
}

@media (max-width: 640px) {
  /* Mobile rhythm is 96px, already covered by .about's bottom padding */
  .trusted { padding: 0 16px 96px; }
  .trusted-inner { gap: 40px; }
  .logo-marquee img { height: 36px; }
  .logo-marquee__group { gap: 48px; padding: 0 24px; }
}

/* ==========================================================================
   Contact footer
   ========================================================================== */
.contact {
  padding: 72px 48px;
  background: var(--bg-default);
  scroll-margin-top: var(--nav-height);
  /* The footer stays dark in both themes */
  --bg-default: #0e011c;
  --bg-surface: #1e0c32;
  --bg-surface-2: #3d3350;
  --fg-default: #ffffff;
  --fg-muted: #b3a0c8;
  --fg-subtle: #978aaa;
  --primary-default: #8200ff;
  --accent: #ffc20a;
  --accent-contrast: #1e0c32;
  --input-bg: rgba(0, 0, 0, 0.2);
  --divider: rgba(151, 138, 170, 0.25);
}


.contact-inner {
  max-width: 720px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
}

/* Footer logo doubles as a home link (fade-exit on case pages, scroll-to-top
   on the home page); the margin rides on the link so the hit area stays the
   logo itself */
.contact__logo-link {
  display: block;
  line-height: 0;
  margin-bottom: 24px;
}

.contact__logo {
  width: 53px;
  height: 64px;
}

.contact__eyebrow {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 1.56px;
  text-transform: uppercase;
  color: var(--accent);
}

.contact__headline {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.875rem, 4vw, 2.75rem);
  line-height: 1.08;
  letter-spacing: 0.8px;
  color: var(--fg-default);
}

.contact__desc {
  margin: -8px 0 0;
  max-width: 720px; /* room to break naturally (was 620) */
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--fg-muted);
  orphans: 2;
  widows: 2;
  /* orphans/widows only bind in fragmented contexts (print/columns);
     text-wrap: pretty is what stops a lone word on the last line here */
  text-wrap: pretty;
}

/* --- Email box with copy button ------------------------------------------- */
.contact-email {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 44px;
  width: 300px;
  max-width: 100%;
  margin-top: 24px;
  padding: 0 12px;
  border: 2px solid var(--primary-default);
  border-radius: 4px;
  background: var(--input-bg);
}

.contact-email input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  color: var(--fg-default);
  font-family: var(--font-body);
  font-size: 1rem;
  outline: none;
}

.contact-email__copy {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  border: 0;
  background: transparent;
  color: var(--fg-default);
  transition: color 200ms ease;
}

.contact-email__copy:hover { color: var(--accent); }

.contact-email__copy svg {
  width: 18px;
  height: 18px;
}

.contact-email__toast {
  position: absolute;
  top: -38px;
  left: 50%;
  transform: translateX(-50%) translateY(6px);
  padding: 4px 12px;
  border-radius: 6px;
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 0.8125rem;
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms ease, transform 200ms ease;
}

.contact-email__toast.is-shown {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.contact__note {
  margin: 16px 0 0;
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--fg-muted);
}

/* --- Social links + resume -------------------------------------------------- */
.contact-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 24px;
  margin-top: 16px;
}

.contact-social {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: var(--primary-default);
  color: var(--primary-contrast);
  transition: transform 200ms var(--ease-out), background 200ms ease;
}

.contact-social:hover {
  transform: scale(1.05);
  background: #9226ff;
}

.contact-social svg {
  width: 20px;
  height: 20px;
}

.contact__copyright {
  margin: 32px 0 0;
  font-size: 0.75rem;
  font-weight: 500;
  line-height: 1.5;
  color: var(--fg-muted);
}

@media (max-width: 640px) {
  .contact { padding: 72px 16px; }
  .contact__desc { font-size: 1.0625rem; }
  .contact-email { width: 100%; }
  .contact-actions .btn { width: 100%; }
}

/* ==========================================================================
   Copyright tooltip — shown near the cursor when right-clicking an image.
   pointer-events: none so it never blocks the browser context menu.
   ========================================================================== */
.copyright-tip {
  position: fixed;
  /* above everything, including the case-study gallery lightbox (500) */
  z-index: 600;
  max-width: 320px;
  padding: 10px 14px;
  border-radius: 10px;
  border: 1px solid rgba(130, 0, 255, 0.55); /* purple accent */
  background: rgba(30, 12, 50, 0.92);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: #ffffff;
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.45;
  box-shadow: 0 12px 32px rgba(14, 1, 28, 0.35);
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 250ms ease, transform 250ms ease;
}

.copyright-tip.is-shown {
  opacity: 1;
  transform: translateY(0);
}

/* ==========================================================================
   Resume download modal
   ========================================================================== */
.resume-modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.resume-modal[hidden] { display: none; }

.resume-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(14, 1, 28, 0.6);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  opacity: 0;
  transition: opacity 200ms ease-out;
}

.resume-modal__card {
  position: relative;
  width: 100%;
  max-width: 480px;
  padding: 48px 40px 40px;
  background: var(--bg-surface);
  border: 1px solid var(--neutral-700);
  border-radius: 12px;
  opacity: 0;
  transform: scale(0.96);
  transition: opacity 200ms ease-out, transform 200ms ease-out;
}

.resume-modal.is-open .resume-modal__backdrop { opacity: 1; }

.resume-modal.is-open .resume-modal__card {
  opacity: 1;
  transform: scale(1);
}

.resume-modal__close {
  position: absolute;
  top: 12px;
  right: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--fg-default);
  transition: color 200ms ease;
}

.resume-modal__close:hover { color: var(--accent); }

.resume-modal__close svg {
  width: 20px;
  height: 20px;
}

.resume-modal__title {
  margin: 0 0 28px;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.625rem;
  line-height: 1.2;
  color: var(--fg-default);
  text-align: center;
}

.resume-modal__options {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.resume-modal__option {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.resume-modal__option .btn { width: 100%; }

.resume-modal__hint {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.4;
  color: var(--fg-subtle);
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .resume-modal__card {
    transform: none;
    transition: opacity 200ms ease;
  }
}

/* --- Select Work "coming soon" variant ------------------------------------
   Shares the resume modal's shell; only the stacked eyebrow/title/date and
   the single dismiss button differ */
.soon-modal__eyebrow {
  margin: 0 0 8px;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--accent);
  text-align: center;
}

.soon-modal__eyebrow[hidden] { display: none; }

.soon-modal__title { margin: 0 0 12px; }

.soon-modal__ok {
  width: 100%;
  margin-top: 28px;
}

/* ==========================================================================
   Responsive
   ========================================================================== */
@media (max-width: 1024px) {
  .site-nav__links,
  .site-nav__actions > .nav-link--contact { display: none; }

  .impact { padding: 96px 32px 72px; }
  .impact__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nav-toggle { display: flex; }
  /* Contact drops out, so the actions capsule tightens into a 56px glass
     disc around the toggle, pulled right to pair with the hamburger disc.
     The negative margin trims the 32px flex gap between the two. */
  .theme-toggle__icon { width: 26px; height: 26px; }
  .site-nav__actions { margin-left: auto; margin-right: -16px; padding: 5px; }
  .site-nav__inner { padding: 0 32px; }

  /* Hero shares the nav's 32px gutters from tablet down, so the name, copy
     and CTA row start on the logo tile's left edge and the scroll hint ends
     on the hamburger's right edge. The actions row spans the full width to
     put the hint there (the <=768 block already does this). */
  .hero__content { padding-left: 32px; padding-right: 32px; }
  .hero__actions { width: 100%; justify-content: space-between; }

  /* Logo drops back to the 56px the toggle and hamburger discs use, so the
     three controls stay a matched set on small screens. The radius scales
     with it (13/56 matches desktop's 16/68) to keep the same corner. */
  .site-nav__logo { width: 56px; height: 56px; border-radius: 13px; }
  .site-nav__logo img { height: 30px; }
  /* Same bare-hero step-up, scaled to the smaller tile (needs to repeat
     here: the .is-over-hero rule outranks the line above on specificity) */
  .site-nav.is-over-hero .site-nav__logo img { height: 36px; }
}

/* Tablet hero (Figma tablet frame, <=768): stacked column — single-line
   72px name, copy below, then buttons left / scroll hint right. Above
   this the fluid 1920-768 desktop layout applies. */
@media (max-width: 768px) {
  .hero__content {
    flex-direction: column;
    align-items: stretch;
    padding: calc(var(--nav-height) + 20px + env(safe-area-inset-top, 0px)) 32px 36px;
    gap: 16px;
  }

  .hero__main {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 16px;
    width: 100%;
    min-width: 0;
    flex: 0 0 auto; /* don't stretch vertically in the stacked column */
  }

  .hero__name {
    font-size: 4.5rem;
    letter-spacing: 1.3px;
    white-space: normal;
    width: 100%;
  }

  .hero__name-break { display: none; }

  .hero__copy {
    width: 100%;
    margin-bottom: 0;
  }

  .hero__desc { max-width: 640px; }

  /* No orphans in the stacked hero copy: the subhead balances into two even
     lines instead of dropping "Strategist" alone, and the body copy pulls its
     last word up. The &nbsp; pairs in the markup cover browsers without
     text-wrap support. */
  .hero__sub { text-wrap: balance; }
  .hero__desc { text-wrap: pretty; }

  .hero__actions {
    width: 100%;
    justify-content: space-between;
    margin-top: 8px;
  }

  .hero__gradient {
    background: linear-gradient(180deg, var(--hero-to) 50%, var(--bg-default) 79.5%);
  }
}

@media (max-width: 640px) {
  :root { --nav-height: 76px; }

  .site-nav__inner { padding: 0 16px; }

  /* Phones drop the nav's readability scrims (both the dark and the
     light-section layer) — the bar sits bare over whatever it overlaps.
     Desktop/tablet keep them. */
  .site-nav::before,
  .site-nav::after { background: none; }

  /* Menu controls at Apple's 44x44 minimum tap target, with the glyphs
     sized down inside so the controls read lighter than the old 56px
     discs. The X-state translate shrinks with the bar gap (5px gap +
     2px bar = 7px from the outer bars to center). */
  .nav-toggle {
    width: 44px;
    height: 44px;
    gap: 5px;
  }

  .nav-toggle__bar { width: 20px; }

  .nav-toggle.is-open .nav-toggle__bar:nth-child(1) { translate: 0 7px; }
  .nav-toggle.is-open .nav-toggle__bar:nth-child(3) { translate: 0 -7px; }

  /* Theme toggle: the button already is the 44px target; the capsule
     tightens to hug it and the icon steps down to match the hamburger */
  .site-nav__actions { padding: 0; }
  .theme-toggle__icon { width: 20px; height: 20px; }

  /* Figma mobile hero (name stepped down from Figma's 44px — see below),
     buttons side-by-side at half width, gradient runs to solid plum by ~90%.
     Gutters match the nav's 20px so the full-width CTA lines up with the
     logo and hamburger, and the whole block centers on itself. */
  /* iOS Safari: the hero spans the LARGE viewport (100lvh) so its plum
     gradient keeps running behind the collapsed URL-bar zone — otherwise
     the bar's blur samples the next (white, in light mode) section. The
     copy still anchors to the SMALL viewport: (100lvh - 100dvh) equals the
     retractable-bar height while the bars are out and collapses to 0 once
     they slide away, so the block rides just above the URL bar in both
     states. vh first as the fallback for browsers without lvh/dvh. The
     env() insets keep the copy clear of the home indicator. */
  .hero {
    min-height: 100vh;
    min-height: 100lvh;
  }

  .hero__content {
    padding: calc(var(--nav-height) + 16px + env(safe-area-inset-top, 0px))
      16px calc(20px + env(safe-area-inset-bottom, 0px));
    padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px)
      + (100lvh - 100dvh));
    align-items: center;
  }

  .hero__main { text-align: center; }
  .hero__desc { margin-inline: auto; }

  /* Gradient sits 96px higher than the tablet ramp so the plum reaches
     further up behind the taller stacked copy */
  .hero__gradient {
    background: linear-gradient(180deg,
      rgba(30, 12, 50, 0) calc(50% - 96px),
      var(--bg-surface) calc(90.6% - 96px));
  }

  /* One notch down from the 2026-08 sizes (44/20/17): the shorter block +
     the lower anchor above clear more of the video behind the copy */
  .hero__name {
    font-size: 2.5rem;
    line-height: 1.08;
    letter-spacing: 0.8px;
  }

  .hero__sub {
    font-size: 1.125rem;
    line-height: 1.3;
  }

  .hero__desc {
    font-size: 1rem;
    line-height: 1.4;
  }

  /* Actions ride one row: the CTA takes the full remaining width with the
     bouncing scroll arrow beside it on the right. The word "Scroll" is
     dropped — the arrow alone is the cue (the link keeps its name via
     aria-label), and it still rides the same #impact anchor scroll. */
  .hero__actions {
    flex-direction: row;
    align-items: center;
    gap: 16px;
    margin-top: 16px;
  }

  .hero__ctas {
    flex: 1;
    min-width: 0;
    flex-wrap: nowrap;
    gap: 16px;
  }

  .hero__ctas .btn {
    flex: 1;
    min-width: 0;
    padding: 0 8px;
    white-space: nowrap;
  }

  .hero__scroll { display: flex; }

  .hero__scroll span { display: none; }

  /* 44px square tap target, centered on the button's midline */
  .hero__scroll {
    justify-content: center;
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    gap: 0;
  }

  .mobile-menu__link { font-size: 1.75rem; }

  .impact { padding: 96px 16px 72px; gap: 64px; }
  .impact__grid { grid-template-columns: 1fr; }
  .impact__headline { font-size: 2.125rem; line-height: 1.12; letter-spacing: 0.6px; }
  .impact__sub { color: var(--fg-subtle); }

  .cs-stack { padding: 0 16px; gap: 32px; }
  .cs-header { padding: 0 16px; }
  .cs-header__sub { font-size: 1.0625rem; }
  .cs-card__details { padding: 24px; gap: 20px; }

  /* Mobile reads left-aligned: the centered section headers flush left.
     Only the hero (above) and the contact footer stay centered. */
  .impact__header,
  .cs-header,
  .skills-header,
  .trusted-header {
    align-items: flex-start;
    text-align: left;
    width: 100%;
  }
}

/* Phone-width cards (~480 down): the 640 sizes read oversized and the
   details panel runs taller than the image, so the whole scale steps down
   proportionally — hierarchy intact, card meaningfully shorter */
@media (max-width: 480px) {
  .cs-card__details { padding: 20px; gap: 14px; }
  .cs-card__info { gap: 10px; }
  .cs-card__client { font-size: 0.9375rem; }
  .cs-card__title { font-size: 1.625rem; }
  .cs-card__desc { font-size: 0.9375rem; }
  .cs-card__stats { gap: 6px; }
  .cs-card__stats li { font-size: 0.875rem; line-height: 1.35; }
}

/* ==========================================================================
   Reduced motion
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .cascade {
    animation: none;
    opacity: 1;
    transform: none;
  }

  .hero__scroll-arrow { animation: none; }

  .impact__card,
  .cs-item,
  [data-cascade],
  [data-cascade-fade] {
    transition: none;
    opacity: 1;
    transform: none;
  }

  .cs-tooltip { display: none; }

  /* Marquee stands still; the strip stays browsable via horizontal scroll */
  .logo-marquee__track { animation: none; }
  .logo-marquee { overflow-x: auto; }

  .copyright-tip { transition: opacity 200ms ease; transform: none; }

  .site-nav,
  .site-nav::before,
  .site-nav::after,
  .nav-link,
  .nav-link--contact,
  .nav-link__text,
  .mobile-menu,
  .mobile-menu__link,
  .nav-toggle__bar,
  .theme-toggle,
  .theme-toggle::after { transition: none; }

  .theme-toggle:hover,
  .theme-toggle:focus-visible { transform: none; }
}
