/* =========================================================================
   ELIPS 2026 — styles
   Tokens and geometry taken from Figma "ELIPS 2026 REDESIGN" (89:1568).
   The Figma frame is 1512 wide; every fixed value below is that frame's
   value, made fluid with clamp() where it needs to survive a phone.
   ========================================================================= */

/* ---------- tokens ------------------------------------------------------ */
:root {
  --purple: #360357;          /* Figma: Text/Headline purple, Button primary */
  --ink: #000000;             /* Text/Paragraph 1 */
  --paper: #ffffff;           /* Background/Background 1 */
  --mist: #f6f8fb;            /* Background/Background 2 */
  --muted: #d9d9d9;           /* clients caption */

  --font-serif: "Source Serif Pro", "Source Serif 4", Georgia, serif;
  --font-sans: "Radio Canada Big", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --shell: 1392px;            /* 1512 − 60px gutters */
  --gutter: clamp(20px, 4vw, 60px);

  --r-card: 16px;
  --r-pill: 50px;
  --r-shape: clamp(40px, 6.6vw, 100px);   /* 100px corner on the hero stage */
  --r-footer: clamp(32px, 4.6vw, 70px);

  --nav-h: 98px;
  --nav-h-stuck: 72px;

  --ease: cubic-bezier(.22, .61, .36, 1);
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-serif);
  font-size: 20px;
  line-height: 1.2;
  letter-spacing: -0.04em;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, p, figure { margin: 0; }
/* height:auto matters: an <img> keeps its height attribute when CSS only
   changes the width, which silently stretches the artwork. Rules that set a
   height on purpose are all class-based and win on specificity. */
img, svg { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: absolute;
  left: 16px;
  top: -100px;
  z-index: 100;
  padding: 12px 18px;
  background: var(--purple);
  color: #fff;
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: 14px;
  transition: top .2s var(--ease);
}
.skip-link:focus { top: 16px; }

/* .shell sits inside sections that already pad by --gutter, so it only
   caps the measure — subtracting the gutter again would double it. */
.shell {
  width: min(100%, var(--shell));
  margin-inline: auto;
}

/* =========================================================================
   Navigation — sticky + animated (Figma 92:1768)
   Shrinks once the page scrolls, hides on the way down, returns on the way
   up, and slides a pill behind whichever section you're reading.
   ========================================================================= */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--gutter);
  background: rgba(255, 255, 255, 0);
  backdrop-filter: blur(32px);
  -webkit-backdrop-filter: blur(32px);
  transition:
    height .35s var(--ease),
    background-color .35s var(--ease),
    box-shadow .35s var(--ease),
    transform .4s var(--ease);
  will-change: transform;
}
.nav.is-stuck {
  height: var(--nav-h-stuck);
  background: rgba(255, 255, 255, .78);
  box-shadow: 0 1px 0 rgba(54, 3, 87, .08), 0 12px 32px -24px rgba(54, 3, 87, .5);
}
.nav.is-hidden { transform: translateY(-100%); }

/* The header carries both marks. The icon is what sits there at rest; the
   full logo takes over while the page is being scrolled and hands back when
   it stops. The wrapper animates between the two widths — derived from each
   mark's own aspect ratio, so the artwork is never stretched. */
.nav__logo {
  --logo-h: 58px;
  --iso-ratio: 1.176;   /* 127 / 108 */
  --full-ratio: 3.278;  /* 354 / 108 */

  position: relative;
  display: block;
  height: var(--logo-h);
  width: calc(var(--logo-h) * var(--iso-ratio));
  transition: width .4s var(--ease), height .35s var(--ease);
}
.nav.is-stuck .nav__logo { --logo-h: 44px; }

.nav__mark {
  position: absolute;
  inset: 0 auto 0 0;
  height: 100%;
  width: auto;
  transition: opacity .3s var(--ease);
}
.nav__mark--full { opacity: 0; }

/* away from the top of the page */
.nav.is-stuck .nav__logo { width: calc(var(--logo-h) * var(--full-ratio)); }
.nav.is-stuck .nav__mark--icon { opacity: 0; }
.nav.is-stuck .nav__mark--full { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .nav__logo, .nav__mark { transition: none; }
}

.nav__items {
  position: relative;
  display: flex;
  align-items: center;
  gap: 20px;
}

/* the pill that tracks the current section */
.nav__indicator {
  position: absolute;
  left: 0;
  top: 50%;
  height: 34px;
  border-radius: var(--r-pill);
  background: rgba(54, 3, 87, .09);
  transform: translate(var(--x, 0), -50%);
  width: 0;
  opacity: 0;
  transition: transform .45s var(--ease), width .45s var(--ease), opacity .3s var(--ease);
  pointer-events: none;
}
.nav__indicator.is-on { opacity: 1; }

.nav__link {
  position: relative;
  z-index: 1;
  padding: 8px 12px;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 16px;
  line-height: 1.2;
  letter-spacing: 0;
  color: var(--purple);
  white-space: nowrap;
  transition: opacity .2s var(--ease);
}
.nav__link:hover { opacity: .65; }
.nav__link--arrow { display: inline-flex; align-items: center; gap: 6px; }
.nav__link--arrow img { width: 12px; color: var(--purple); }

/* language toggle (Figma 143:1703) */
.lang {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: #fff;
  border: 1px solid #dbeafe;
  border-radius: 999px;
  filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, .1)) drop-shadow(0 1px 1px rgba(0, 0, 0, .1));
}
.lang__btn {
  padding: 4px 12px;
  border-radius: 999px;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 12px;
  line-height: 16px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: #90a1b9;
  transition: background-color .25s var(--ease), color .25s var(--ease);
}
.lang__btn[aria-current="true"] { background: var(--purple); color: #fff; }
.lang__btn:hover { color: var(--purple); }

/* menu button — only ever visible on narrow screens */
.nav__toggle {
  display: none;
  width: 44px;
  height: 44px;
  place-items: center;
  border-radius: 999px;
  color: var(--purple);
}
.nav__bars, .nav__bars::before, .nav__bars::after {
  display: block;
  width: 22px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  transition: transform .3s var(--ease), opacity .2s var(--ease);
}
.nav__bars::before, .nav__bars::after { content: ""; position: absolute; }
.nav__bars::before { transform: translateY(-7px); }
.nav__bars::after { transform: translateY(7px); }
.nav__toggle[aria-expanded="true"] .nav__bars { background: transparent; }
.nav__toggle[aria-expanded="true"] .nav__bars::before { transform: rotate(45deg); }
.nav__toggle[aria-expanded="true"] .nav__bars::after { transform: rotate(-45deg); }

/* =========================================================================
   Buttons (Figma "Button primary" 95:9215)
   ========================================================================= */
.btn-row { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; }

.btn {
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 16px 30px;
  border-radius: var(--r-pill);
font-family: var(--font-sans);
  font-weight: 500;
  font-size: 14px;
  line-height: 1;
  letter-spacing: 0;
  white-space: nowrap;
  transition: transform .25s var(--ease), box-shadow .25s var(--ease), background-color .25s var(--ease);
}
.btn__dot { width: 4px; height: 4px; flex: none; background: currentColor; }
.btn:hover { transform: translateY(-2px); }

.btn--solid { background: var(--purple); color: #fff; }
.btn--solid:hover { box-shadow: 0 14px 30px -18px rgba(54, 3, 87, .9); }

/* secondary: the same shape and weight, carried by an outline rather than a
   fill, so the pair reads as one primary and one alternative */
.btn--outline {
  background: transparent;
  color: var(--purple);
  box-shadow: inset 0 0 0 1.5px var(--purple);
}
.btn--outline:hover {
  background: rgba(54, 3, 87, .06);
  box-shadow: inset 0 0 0 1.5px var(--purple), 0 14px 30px -20px rgba(54, 3, 87, .7);
}

.btn--light { background: #fff; color: var(--purple); }
.btn--light:hover { box-shadow: 0 14px 30px -18px rgba(0, 0, 0, .6); }

.btn--ghost { border: 1px solid #fff; color: #fff; }
.btn--ghost:hover { background: rgba(255, 255, 255, .12); }

/* =========================================================================
   Hero (Figma "Intro content" 89:1569 + stage 89:1593)
   ========================================================================= */
.hero {
  position: relative;
  padding-top: clamp(120px, 10.6vw, 160px);
  text-align: center;
}

.hero__intro {
  width: min(100% - var(--gutter) * 2, 1030px);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
}

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

.hero__title {
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: clamp(40px, 5.3vw, 80px);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--purple);
}
.hero__subtitle {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: clamp(27px, 3.5vw, 53px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--purple);
}
.hero__lede {
  font-size: clamp(17px, 1.32vw, 20px);
  color: var(--ink);
}

/* the rounded illustration card */
.stage {
  position: relative;
  z-index: 2;
  width: min(100% - var(--gutter) * 2, 1379px);
  margin: clamp(48px, 5.4vw, 82px) auto 0;
  aspect-ratio: 1379 / 638;
  border-radius: var(--r-shape) 0 var(--r-shape) 0; /* tl + br, per Figma */
  overflow: hidden;
  background: #2a0146;
  box-shadow: 0 40px 90px -60px rgba(54, 3, 87, .8);
}
.stage__art { position: absolute; inset: 0; }
.stage__art svg { width: 100%; height: 100%; display: block; }

/* =========================================================================
   Clients band (Figma 89:1594) — Vimeo background video
   ========================================================================= */
.video-band { position: relative; isolation: isolate; overflow: hidden; }

.video-bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
  background: #000000;
}
/* The iframe is sized by script.js so the 16:9 player always covers the
   band, whatever the band's own aspect ratio is. */
.video-bg iframe {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border: 0;
  pointer-events: none;
}
/* The veil over the background videos. One knob: --video-veil scales the
   whole gradient, so lower it to let more of the footage through and raise it
   to mute the footage in favour of the text on top.

   The stops are deliberately close together. The original gradient was
   lightest at 45%, which is exactly where the Special Solutions heading sits,
   so raising the veil alone was fighting its own shape: over bright footage
   that heading only reached 2.7:1 even at full strength. Flattening the
   middle takes the worst case to 5.6:1 for the headings, 5.2:1 for the
   clients caption and 8.9:1 for the marquee — measured against pure white
   frames, which real footage rarely is. */
.video-bg {
  --video-veil: 1;
}
.video-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(10, 0, 18, calc(var(--video-veil) * .66)),
    rgba(10, 0, 18, calc(var(--video-veil) * .58)) 45%,
    rgba(10, 0, 18, calc(var(--video-veil) * .80)));
}

.clients {
  min-height: 633px;
  /* the hero stage overlaps this band by 359px in the Figma frame */
  margin-top: clamp(-359px, -23.7vw, -140px);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 29px;
  padding: clamp(300px, 28.4vw, 429px) 0 51px;
  text-align: center;
}

.clients__caption {
  color: var(--muted);
  font-size: clamp(17px, 1.32vw, 20px);
  line-height: 1;
  max-width: 60ch;
  padding-inline: var(--gutter);
}

/* marquee */
.marquee {
  width: 100%;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee__track {
  display: flex;
  align-items: center;
  gap: 38px;
  width: max-content;
  animation: marquee 42s linear infinite;
}
.marquee:hover .marquee__track { animation-play-state: paused; }
@keyframes marquee { to { transform: translateX(-50%); } }

.marquee__item {
  flex: none;
  display: grid;
  place-items: center;
  width: 186px;
  height: 76px;
  padding: 8px 14px;
}
.marquee__item img { max-height: 100%; width: auto; object-fit: contain; }
/* fallback while a client logo file is missing */
.marquee__item span {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: 22px;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .8);
}

/* =========================================================================
   Featured project (Figma 92:1811) — Lottie replaces the still image
   ========================================================================= */
.featured {
  width: min(100% - var(--gutter) * 2, 1240px);
  margin: clamp(64px, 6.4vw, 96px) auto 0;
  display: flex;
  align-items: center;
  gap: 16px;
}

/* a second panel on its own band; the band supplies the colour and the
   vertical rhythm, so the panel inside keeps its own margin at zero */
.featured-band {
  margin-top: clamp(64px, 6.4vw, 96px);
  padding: clamp(48px, 5.3vw, 80px) 0 clamp(56px, 6vw, 90px);
  background: #edfdff;
}
.featured-band .featured { margin-top: 0; }

/* the band already ends in its own padding; the next section's top margin on
   top of that left a white strip between the two colours */
.featured-band + .services { margin-top: 0; }

/* add this to a .featured to put the artwork on the right instead */
.featured--flip { flex-direction: row-reverse; }

.featured__media {
  position: relative;
  flex: 0 0 639px;
  max-width: 52%;
  aspect-ratio: 639 / 536;
  border-radius: 0 var(--r-shape) 0 var(--r-shape); /* tr + bl, per Figma */
  overflow: hidden;
  background: linear-gradient(150deg, #2b1f6b, #360357 55%, #7c2f6d);
}
.featured__media > * { position: absolute; inset: 0; }
.featured__media svg { width: 100%; height: 100%; display: block; border: 0; }

/* Once the animation is up the panel stops being a panel.
   The rounded frame + overflow:hidden below exist for the Figma still image;
   against a Lottie they were doing two visible kinds of damage: clipping the
   artwork (the laptop lost its left edge) and drawing that clip as a hairline
   along the rounded corners. There is no fill left to contain, so the panel
   drops its background, its radius and its clip, and the SVG root drops its
   own viewBox clip so layers that sit outside the composition bounds still
   render. */
.featured__media.is-live {
  background: none;
  border-radius: 0;
  overflow: visible;
  /* the panel takes the composition's aspect ratio, so a portrait animation
     — a phone mockup, say — would otherwise run taller than the screen. The
     cap holds the height and the renderer's "meet" fitting scales the
     artwork down inside it rather than cropping. */
  max-height: min(72vh, 660px);
}
.featured__media.is-live svg { overflow: visible; }
.featured__fallback { background: inherit; }

.featured__quote {
  position: relative;
  z-index: 1;               /* the panel now overflows; text stays on top */
  flex: 1 1 0;
  min-width: 0;
  padding-inline: clamp(24px, 6.9vw, 105px);
}
.quote { display: flex; flex-direction: column; gap: 20px; max-width: 480px; }
.quote__kicker {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: clamp(28px, 2.65vw, 40px);
  line-height: 1;
  letter-spacing: -0.03em;
}
.quote__kicker b { font-weight: 700; }
.quote__title {
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: clamp(22px, 1.79vw, 27px);
  letter-spacing: -0.04em;
}
.quote__body { font-size: clamp(17px, 1.32vw, 20px); }
.quote .btn { align-self: flex-start; }

/* =========================================================================
   What we do (Figma 92:1720)
   ========================================================================= */
.services {
  margin-top: clamp(64px, 6.4vw, 96px);
  padding: clamp(72px, 7.9vw, 120px) var(--gutter);
  background: var(--purple);
  color: #fff;
  text-align: center;
}
.services__header { display: flex; flex-direction: column; gap: 20px; margin-bottom: 40px; }
.services__title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(36px, 3.97vw, 60px);
  line-height: 1;
  letter-spacing: -0.04em;
}
.services__title b { font-weight: 700; }
.services__lede {
  font-weight: 300;
  font-size: clamp(17px, 1.32vw, 20px);
}

.cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
  text-align: left;
}
.card {
  display: flex;
  flex-direction: column;
  gap: 24px;
  min-height: 246px;
  padding: 40px;
  background: #fff;
  color: var(--ink);
  border-radius: var(--r-card);
  transition: transform .4s var(--ease), box-shadow .4s var(--ease);
}
.card:hover { transform: translateY(-6px); box-shadow: 0 30px 50px -34px rgba(0, 0, 0, .75); }
.card__icon { width: 99px; height: 99px; flex: none; }
.card__icon svg { width: 100%; height: 100%; overflow: hidden; }

/* -------------------------------------------------------------------------
   Service icon motion (Figma 92:1900, 97:9351, 97:9505, 97:9654)

   The icons are inlined so their parts can be reached from here. Parts are
   tagged by tools/tag_icons.py. Every animated element sets transform-box:
   fill-box so a transform rotates around the shape itself rather than the
   SVG origin — without it, a 2deg rotation throws the piece off the canvas.

   Motion only runs once the card has scrolled in, so four looping icons are
   not burning frames above and below the fold.
   ------------------------------------------------------------------------- */
.card__icon .ic-badge,
.card__icon .ic-sheen,
.card__icon .ic-accent,
.card__icon .ic-device,
.card__icon .ic-bar,
.card__icon .ic-tile,
.card__icon .ic-line,
.card__icon .ic-dot,
.card__icon .ic-panel {
  transform-box: fill-box;
  transform-origin: center;
}

.card.is-in .ic-badge { animation: ic-breathe 5s ease-in-out infinite; }
.card.is-in .ic-sheen { animation: ic-sheen 5s ease-in-out infinite; }

/* branding — the pen dips to the page, tiles blink in turn */
[data-icon="branding"] .ic-accent { transform-origin: 50% 15%; }
.card.is-in [data-icon="branding"] .ic-accent { animation: ic-dip 3.2s ease-in-out infinite; }
.card.is-in [data-icon="branding"] .ic-bar { animation: ic-stretch 3.2s ease-in-out infinite; }
.card.is-in [data-icon="branding"] .ic-tile {
  animation: ic-blink 2.4s ease-in-out infinite;
  animation-delay: calc(var(--i) * .25s);
}

/* strategy — the phone tilts, the cursor circles it */
.card.is-in [data-icon="strategy"] .ic-device { animation: ic-tilt 4.5s ease-in-out infinite; }
.card.is-in [data-icon="strategy"] .ic-accent { animation: ic-drift 3.4s ease-in-out infinite; }

/* webdesign — code lines type in, the play button pulses, dots blink */
.card.is-in [data-icon="webdesign"] .ic-line {
  transform-origin: left center;
  animation: ic-type 4.4s ease-in-out infinite;
  animation-delay: calc(var(--i) * .09s);
}
.card.is-in [data-icon="webdesign"] .ic-accent { animation: ic-pulse 2.6s ease-in-out infinite; }
.card.is-in [data-icon="webdesign"] .ic-dot {
  animation: ic-blink 2.1s ease-in-out infinite;
  animation-delay: calc(var(--i) * .18s);
}

/* ux — the heart beats, the stacked panels lift in sequence */
.card.is-in [data-icon="ux"] .ic-accent { animation: ic-beat 2.2s ease-in-out infinite; }
.card.is-in [data-icon="ux"] .ic-device { animation: ic-tilt 5.2s ease-in-out infinite; }
.card.is-in [data-icon="ux"] .ic-panel {
  animation: ic-lift 4s ease-in-out infinite;
  animation-delay: calc(var(--i) * .2s);
}

@keyframes ic-breathe {
  50% { transform: scale(1.035); }
}
@keyframes ic-sheen {
  0%, 100% { opacity: .32; transform: translateX(-6%); }
  50%      { opacity: .55; transform: translateX(6%); }
}
@keyframes ic-dip {
  35% { transform: translate(1%, 5%) rotate(-3deg); }
  55% { transform: translate(1%, 5%) rotate(-3deg); }
  85% { transform: none; }
}
@keyframes ic-stretch {
  40%, 60% { transform: scaleX(1.06); }
}
@keyframes ic-blink {
  0%, 100% { opacity: 1; }
  45%      { opacity: .35; }
}
@keyframes ic-tilt {
  33%  { transform: rotate(1.5deg) translateY(-1.5%); }
  66%  { transform: rotate(-1deg) translateY(.5%); }
}
@keyframes ic-drift {
  30% { transform: translate(-8%, -6%); }
  60% { transform: translate(4%, -2%); }
}
@keyframes ic-type {
  0%, 8%   { transform: scaleX(0); opacity: 0; }
  22%, 78% { transform: scaleX(1); opacity: 1; }
  92%, 100% { transform: scaleX(0); opacity: 0; }
}
@keyframes ic-pulse {
  50% { transform: scale(1.08); }
}
@keyframes ic-beat {
  20% { transform: scale(1.18); }
  35% { transform: scale(1); }
  50% { transform: scale(1.12); }
}
@keyframes ic-lift {
  40%, 60% { transform: translate(-1.5%, -2.5%); }
}

/* the whole icon leans in on hover, on top of whatever it is already doing */
.card__icon svg { transition: transform .5s var(--ease); }
.card:hover .card__icon svg { transform: translateY(-5px) scale(1.05); }
.card__title {
    font-family: var(--font-serif);
      font-weight: 700;
  font-size: clamp(22px, 1.85vw, 28px);
  line-height: 1;
  letter-spacing: -0.02em;
}
.card__title span { font-weight: 400; }
.card__body { font-size: clamp(17px, 1.32vw, 20px); }

/* =========================================================================
   Special Solutions (Figma 97:9260) — second Vimeo background
   ========================================================================= */
/* this band carries the most text of the three, and its heading sits where
   the gradient is lightest, so it gets a heavier veil than the clients strip */
.special .video-bg { --video-veil: 1.2; }

.special {
  min-height: 793px;
  display: grid;
  place-items: center;
  padding: clamp(90px, 10vw, 150px) var(--gutter);
  color: #fff;
  text-align: center;
}
.special__inner {
  width: min(100%, 1030px);
  display: flex;
  flex-direction: column;
  align-items: center;
  /* the subtitle and the lede belong together, so they sit close; the buttons
     keep the original breathing room via their own margin */
  gap: 16px;
}
.special__inner .btn-row { margin-top: 16px; }
.special__head { display: flex; flex-direction: column; gap: 16px; }
.special__title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(36px, 3.97vw, 60px);
  line-height: 1;
  letter-spacing: -0.04em;
}
.special__subtitle {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: clamp(21px, 1.98vw, 30px);
  line-height: 1.05;
  letter-spacing: -0.05em;
}
.special__lede { font-size: clamp(17px, 1.32vw, 20px); }

/* =========================================================================
   Call to action (Figma 97:9305)
   ========================================================================= */
.cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  padding: clamp(72px, 7.9vw, 120px) var(--gutter);
  background: var(--mist);
  text-align: center;
}
.cta__title {
font-family: var(--font-serif);
  font-weight: 500;
  font-size: clamp(26px, 2.65vw, 40px);
  line-height: 1;
  letter-spacing: -0.03em;
}
.cta__title b { font-weight: 700; }

/* =========================================================================
   Footer (Figma 146:1786 + 146:1863)
   ========================================================================= */
.site-footer {
  position: relative;
  background: var(--purple);
  color: #fff;
  border-radius: var(--r-footer) var(--r-footer) 0 0;
  padding: 50px var(--gutter) 40px;
  overflow: hidden;              /* keeps the video inside the 70px top corners */
}
.site-footer .shell, .site-footer .footer__nav { position: relative; z-index: 1; }

/* Figma paints black -> #360357 over the footer video. Taken literally the
   top is pure black, and on a phone — where the footer is far taller than its
   contents — that swallowed the top third of the band and the footage only
   appeared halfway down. The veil now starts tinted rather than black, so the
   illustration reads across the whole footer while the type keeps its
   contrast. */
.video-bg--footer::after {
  background: linear-gradient(180deg, rgba(18, 2, 26, .66), rgba(54, 3, 87, .86));
  opacity: 1;
}
.footer__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
  align-items: center;
}
/* the mark is 354x108 (3.28:1) where the old wordmark was 223x76 (2.93:1),
   so the width is set from the height it should occupy rather than carried
   over, which would have made it noticeably taller */
.footer__brand img { width: clamp(140px, 13vw, 196px); }
.footer__socials { display: flex; flex-direction: column; align-items: center; gap: 12px; }
.footer__socials-label {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .6);
}

.footer__social { display: flex; justify-content: center; gap: 10px; }
.footer__social a {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 1px solid rgba(255, 255, 255, .45);
  border-radius: 999px;
  color: #fff;
  transition: transform .3s var(--ease), border-color .3s var(--ease),
              background-color .3s var(--ease);
}
.footer__social a:hover {
  transform: translateY(-4px);
  border-color: #fff;
  background: rgba(255, 255, 255, .12);
}
.footer__social svg { width: 16px; height: 16px; }
.footer__legal {
  text-align: right;
  font-size: 14px;
  line-height: 1.45;
  color: rgba(255, 255, 255, .75);
  font-family: var(--font-sans);
}

.footer__nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px 28px;
  margin-top: clamp(48px, 6vw, 90px);
}
.footer__nav a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 16px;
  line-height: 1.2;
  letter-spacing: 0;
  opacity: .85;
  transition: opacity .2s var(--ease);
}
.footer__nav a:hover { opacity: 1; }
.footer__nav img { width: 12px; }

.footer__bar {
  display: grid;
  place-items: center;
  min-height: 66px;
  padding: 21px var(--gutter);
  background: var(--mist);
  color: var(--purple);
  font-size: 17px;
  text-align: center;
}

/* =========================================================================
   Scroll motion: parallax + reveal
   ========================================================================= */
[data-parallax] { will-change: transform; }

[data-reveal] {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity .8s var(--ease), transform .8s var(--ease);
}
[data-reveal].is-in { opacity: 1; transform: none; }
[data-reveal-delay="1"] { transition-delay: .08s; }
[data-reveal-delay="2"] { transition-delay: .16s; }
[data-reveal-delay="3"] { transition-delay: .24s; }

/* =========================================================================
   Responsive
   ========================================================================= */
@media (max-width: 1100px) {
  .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .featured { flex-direction: column; align-items: stretch; gap: 32px; }
  .featured__media { flex: none; max-width: none; width: 100%; }
  .featured__quote { padding-inline: 0; }
  .quote { max-width: none; }
  .footer__grid { grid-template-columns: 1fr; gap: 32px; justify-items: center; text-align: center; }
  .footer__legal { text-align: center; }
  .footer__nav { justify-content: center; }
}

/* =========================================================================
   Collapsed navigation

   At the top of a page the bar shows the isotipo and the links in a row. Once
   the page has been scrolled it becomes the full logo and a single menu
   button, with the links moving into a panel underneath — the arrangement in
   the Aerolab reference. Narrow screens are always in this state.

   The rules live on `.nav.is-collapsed` rather than inside a media query, so
   the same markup and the same panel serve both cases; script.js sets the
   class from either condition.
   ========================================================================= */
.nav.is-collapsed .nav__toggle { display: grid; position: relative; }
.nav.is-collapsed .nav__indicator { display: none; }

/* The menu is a full-screen sheet that sweeps down from under the bar with a
   curved leading edge, after the Aerolab reference. The curve is a clip-path
   ellipse anchored above the viewport: animating its height alone gives the
   sweep and the bow in one property. */
.nav.is-collapsed .nav__items {
  position: fixed;
  inset: 0;
  z-index: -1;                    /* under the bar's own logo and button */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0;
  padding: calc(var(--nav-h) + 6vh) var(--gutter) 8vh;
  background: #fff;
  clip-path: ellipse(140% 0% at 50% 0%);
  visibility: hidden;
  transition: clip-path .62s var(--ease), visibility .62s;
}
.nav.is-collapsed .nav__items.is-open {
  /* past the bottom of the viewport: an ellipse that ends exactly at 100%
     leaves the two bottom corners uncovered, and the page shows through them */
  clip-path: ellipse(150% 150% at 50% 0%);
  visibility: visible;
}

/* The sheet covers the page, so the bar stops being a translucent strip over
   it. Dropping the blur is also load-bearing: `backdrop-filter`, `transform`
   and `will-change: transform` each make the bar a containing block, and any
   one of them leaves the sheet's `position: fixed` resolving against the bar
   instead of the viewport — which is why it opened 231px tall on the first
   attempt. All three are released while the menu is open. */
.nav.is-menu-open {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
  will-change: auto;
  transform: none;
}

.nav.is-collapsed .nav__link {
  padding: 4px 0;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: clamp(38px, 6.4vw, 86px);
  line-height: 1.08;
  letter-spacing: -0.03em;
  color: var(--purple);
  /* each line arrives just after the one above it */
  opacity: 0;
  transform: translateY(24px);
  transition: opacity .45s var(--ease), transform .45s var(--ease), color .2s var(--ease);
}
.nav.is-collapsed .nav__items.is-open .nav__link { opacity: 1; transform: none; }
.nav.is-collapsed .nav__items.is-open .nav__link:nth-child(2) { transition-delay: .20s; }
.nav.is-collapsed .nav__items.is-open .nav__link:nth-child(3) { transition-delay: .27s; }
.nav.is-collapsed .nav__items.is-open .nav__link:nth-child(4) { transition-delay: .34s; }
.nav.is-collapsed .nav__items.is-open .nav__link:nth-child(5) { transition-delay: .41s; }
.nav.is-collapsed .nav__items.is-open .nav__link:nth-child(6) { transition-delay: .48s; }
.nav.is-collapsed .nav__link:hover { color: #485C66; opacity: 1; }
.nav.is-collapsed .nav__link--arrow img { display: none; }

.nav.is-collapsed .lang {
  align-self: flex-start;
  margin-top: 32px;
  opacity: 0;
  transform: translateY(24px);
  transition: opacity .45s var(--ease) .55s, transform .45s var(--ease) .55s;
}
.nav.is-collapsed .nav__items.is-open .lang { opacity: 1; transform: none; }

/* the button becomes a cross while the sheet is open */
.nav__toggle[aria-expanded="true"] { color: var(--purple); }

@media (prefers-reduced-motion: reduce) {
  .nav.is-collapsed .nav__items,
  .nav.is-collapsed .nav__link,
  .nav.is-collapsed .lang { transition: none; }
}

@media (max-width: 780px) {
  /* the hero card overlaps the band far less once it is only ~160px tall */
  .clients {
    min-height: 0;
    margin-top: -60px;
    padding-top: 120px;
    gap: 24px;
  }
  .special { min-height: 620px; }
}

@media (max-width: 620px) {
  .cards { grid-template-columns: minmax(0, 1fr); }
  .card { padding: 28px; }
  .btn-row { justify-content: center; }
}

/* =========================================================================
   Reduced motion
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .marquee__track { animation: none; }
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  .nav, .card, .btn { transition: none; }
  .card__icon [class^="ic-"] { animation: none !important; }
  .card:hover .card__icon svg { transform: none; }
}

/* =========================================================================
   About page (Figma 109:9977)
   Shares the nav, footer, buttons, cards and CTA with the home page; only
   the sections below are new.
   ========================================================================= */

/* ---------- intro (109:10215) ------------------------------------------ */
.about-hero { padding-top: clamp(120px, 12vw, 182px); }

.about-hero__title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(30px, 3.3vw, 50px);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--purple);
}
.about-hero__body {
  display: flex;
  flex-direction: column;
  gap: 1.2em;
  font-size: clamp(17px, 1.32vw, 20px);
}

/* the shape is 558 tall here rather than the 638 on the home page */
.about-stage { aspect-ratio: 1379 / 558; margin-top: clamp(40px, 5vw, 75px); }
/* Figma sets this fill to FILL (cover), so the 1500x704 source is cropped
   top and bottom rather than letterboxed into the wider shape */
.stage__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
/* the illustration's canvas was widened to the shape's own ratio, so it fills
   it exactly — nothing cropped, and no plate showing at the sides */
.stage__photo--art { object-fit: cover; }
/* the same element as an inlined <svg> under reduced motion */
svg.stage__photo--art { object-fit: unset; }

/* ---------- clients wall (109:10969 + 153:2385) ------------------------ */
.clients-wall {
  /* the stage overlaps the purple band by 295px in the Figma frame */
  margin-top: clamp(-295px, -19.5vw, -110px);
  padding: clamp(300px, 26.5vw, 400px) var(--gutter) clamp(90px, 11vw, 167px);
  background: var(--purple);
  color: #fff;
  text-align: center;
}

.wall__header { display: flex; flex-direction: column; gap: 20px; margin-bottom: 39px; }
.wall__title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(36px, 3.97vw, 60px);
  line-height: 1;
  letter-spacing: -0.04em;
}
.wall__lede { font-weight: 300; font-size: clamp(17px, 1.32vw, 20px); }

/* ---------- logo grids (clients + technologies) ------------------------ */
.logo-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  /* the row gap is the generous one: these are logos of different shapes and
     they need the space between lines more than between columns */
  gap: 64px clamp(20px, 4.4vw, 67px);
  margin: 0;
  padding: 0;
  list-style: none;
}
.logo-grid__cell {
  display: grid;
  place-items: center;
  height: 76px;
  padding: 6px;
}
.logo-grid__cell img { max-height: 100%; width: auto; object-fit: contain; }

/* Clients wall.

   The height is in pixels, not a percentage: the cell is itself a grid
   container whose row is auto-sized, so `max-height: 100%` resolves against
   an indefinite track and is quietly dropped — three of the twenty marks were
   overflowing their 76px cell at up to 86px tall.

   The marks rest slightly held back and come up to full strength under the
   pointer, so the wall reads as one texture until you look at a name. */
.clients-wall .logo-grid { --client-h: 58px; }
.clients-wall .logo-grid__cell { height: var(--client-h); padding: 0 7%; }
.clients-wall .logo-grid__cell img {
  width: 100%;
  height: var(--client-h);
  max-height: none;
  object-fit: contain;
  opacity: .72;
  transition: opacity .28s var(--ease), transform .28s var(--ease);
}
.clients-wall .logo-grid__cell img:hover {
  opacity: 1;
  transform: scale(1.08) translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
  .clients-wall .logo-grid__cell img { transition: none; }
  .clients-wall .logo-grid__cell img:hover { transform: none; }
}

/* a slot whose file has not been dropped in yet, per script.js */
.logo-grid__cell span {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: clamp(13px, 1.1vw, 17px);
  letter-spacing: .02em;
  text-transform: uppercase;
  text-align: center;
  color: rgba(255, 255, 255, .75);
}
/* Four across rather than the clients wall's five, and the marks sit smaller
   inside their cell — a wide wordmark reaching the full 200px read heavier
   than the section's heading. The cell keeps its own size so the rows stay
   even whatever shape the mark is. */
.logo-grid--tech {
  --tech-h: 54px;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 66px clamp(24px, 4vw, 60px);
}
.logo-grid--tech .logo-grid__cell { height: var(--tech-h); padding: 0 13%; }
/* The height is set in pixels, not as a percentage. The cell is itself a grid
   container whose single row is auto-sized, so a percentage height — or
   max-height — resolves against an indefinite track and is quietly dropped:
   that is why the marks kept overflowing their cell at 85px tall. */
.logo-grid--tech .logo-grid__cell img {
  width: 100%;
  height: var(--tech-h);
  object-fit: contain;
  opacity: .62;
  filter: grayscale(1);
  transition: opacity .25s var(--ease), transform .25s var(--ease);
}
.logo-grid--tech .logo-grid__cell img:hover { opacity: 1; transform: scale(1.06); }

@media (prefers-reduced-motion: reduce) {
  .logo-grid--tech .logo-grid__cell img { transition: none; }
  .logo-grid--tech .logo-grid__cell img:hover { transform: none; }
}

/* ---------- our values (245:1953) -------------------------------------- */
.values {
  padding: clamp(72px, 7.9vw, 120px) var(--gutter);
  background: #e9eaeb;
  text-align: center;
}
.values__title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(36px, 3.97vw, 60px);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--ink);
  margin-bottom: 40px;
}
.values__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  text-align: left;
}
.value-card { gap: 8px; }
.value-card__icon {
  height: 87px;
  margin-bottom: 16px;
  display: flex;
  align-items: flex-end;
}
.value-card__icon svg { width: auto; height: 100%; overflow: visible; }

/* the three marks are line drawings, so they draw themselves in */
.value-card__icon path {
  transform-box: fill-box;
  transform-origin: center;
}
.card.is-in .value-card__icon svg { animation: value-in 1.1s var(--ease) both; }
@keyframes value-in {
  from { opacity: 0; transform: translateY(10px) scale(.96); }
  to   { opacity: 1; transform: none; }
}
.value-card:hover .value-card__icon svg { animation: value-nudge 1.6s var(--ease); }
@keyframes value-nudge {
  35% { transform: translateY(-6px) rotate(-2deg); }
  70% { transform: translateY(0) rotate(1deg); }
}

/* ---------- technologies (254:2588) ------------------------------------ */
.tech {
  padding: clamp(72px, 7.9vw, 110px) var(--gutter);
  text-align: center;
}
.tech__title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(26px, 2.65vw, 40px);
  line-height: 1.1;
  letter-spacing: -0.04em;
  color: var(--ink);
  margin-bottom: 39px;
}

/* ---------- responsive -------------------------------------------------- */
@media (max-width: 1100px) {
  .values__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .logo-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .logo-grid--tech { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 780px) {
  .clients-wall { margin-top: -60px; padding-top: 120px; }
  .logo-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 44px 16px; }
  .logo-grid--tech { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .values__grid { grid-template-columns: minmax(0, 1fr); }
  .logo-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (prefers-reduced-motion: reduce) {
  .card.is-in .value-card__icon svg,
  .value-card:hover .value-card__icon svg { animation: none; }
}

/* the nav/footer link pointing at the page you are already on */
.nav__link.is-page { color: var(--purple); font-weight: 700; }
.footer__nav a.is-page { opacity: 1; text-decoration: underline; text-underline-offset: 4px; }

/* =========================================================================
   What we do page (Figma 109:11018)
   Reuses the nav, hero shape, cards, CTA and footer; only the two sections
   below are new.
   ========================================================================= */

/* ---------- services band (109:11019 + 109:11475) ---------------------- */
.services-wall {
  /* the hero shape overlaps the grey band by 299px in the Figma frame */
  margin-top: clamp(-299px, -19.8vw, -110px);
  padding: clamp(300px, 27.3vw, 413px) var(--gutter) clamp(80px, 8.4vw, 127px);
  background: #e9eaeb;
}

/* two columns of 680 with an 18px gutter, per the Figma frame */
.services-wall__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px 18px;
}

/* the cards carry more copy here than on the home page, so they set their
   own rhythm rather than inheriting the four-across spacing */
.service-card { gap: 24px; }
.service-card .card__title { font-size: clamp(22px, 1.85vw, 28px); }

/* ---------- Amazon Web Services (109:11710 + 109:11733) ---------------- */
.aws {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(40px, 5vw, 70px);
  padding: clamp(80px, 9.4vw, 142px) var(--gutter) clamp(70px, 8vw, 121px);
  text-align: center;
}
.aws__intro {
  width: min(100%, 1030px);
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* the supplied mark is the AWS Partner Network lockup at 3.6:1, wider than
   the 2.3:1 badge the slot was drawn for, so it is sized on its own
   proportions rather than the slot's */
.aws__badge img {
  width: clamp(190px, 17.5vw, 265px);
  height: auto;
}

/* ---------- responsive -------------------------------------------------- */
@media (max-width: 900px) {
  .services-wall__grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 780px) {
  .services-wall { margin-top: -60px; padding-top: 120px; }
}

/* artwork not yet supplied — see assets/img/README.txt */
.stage.is-pending { background: linear-gradient(150deg, #2b1f6b, #360357 55%, #7c2f6d); }
.aws__badge.is-pending {
  width: clamp(210px, 19.8vw, 300px);
  aspect-ratio: 300 / 130;
  border: 1px dashed rgba(0, 0, 0, .2);
  border-radius: 10px;
}

/* =========================================================================
   Contact page (Figma 113:12144)
   ========================================================================= */

.contact__subtitle {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: clamp(21px, 1.98vw, 30px);
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--purple);
}
.contact__mail {
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.contact__mail:hover { color: var(--purple); }

/* ---------- offices (113:12145 band + 113:12146) ----------------------- */
.offices {
  /* the hero shape overlaps the band by 234px in the Figma frame */
  margin-top: clamp(-234px, -15.5vw, -90px);
  padding: clamp(260px, 24vw, 363px) var(--gutter) clamp(70px, 8vw, 121px);
  background: var(--mist);
}
.offices__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.office-card { gap: 15px; }
.office-card__icon { height: 66px; display: flex; align-items: flex-end; }
.office-card__icon svg { width: auto; height: 100%; }
.card.is-in .office-card__icon svg { animation: value-in 1.1s var(--ease) both; }
.office-card:hover .office-card__icon svg { animation: value-nudge 1.6s var(--ease); }
.office-card .card__body { text-transform: uppercase; letter-spacing: -0.02em; }

/* =========================================================================
   Contact form
   Not in the Figma frame — it fills the white space the design leaves
   between the offices and the call to action.
   ========================================================================= */
/* Glass only reads if there is something behind it to blur, and the section
   was flat white. These two soft washes give the panel something to pick up
   without competing with the form itself. */
.contact-form {
  position: relative;
  isolation: isolate;
  padding: clamp(72px, 8.4vw, 127px) var(--gutter);
  background: linear-gradient(165deg, #edfdff 0%, #f7fbfd 45%, #f6f2fb 100%);
  overflow: hidden;
}
.contact-form::before,
.contact-form::after {
  content: "";
  position: absolute;
  z-index: -1;
  border-radius: 50%;
  filter: blur(90px);
  pointer-events: none;
}
.contact-form::before {
  width: 42vw;
  height: 42vw;
  top: -20vw;
  left: -16vw;
  background: rgba(94, 40, 140, .13);
}
.contact-form::after {
  width: 40vw;
  height: 40vw;
  right: -18vw;
  bottom: -22vw;
  background: rgba(70, 190, 210, .16);
}
.contact-form__shell { width: min(100%, 860px); }

.contact-form__intro {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-bottom: clamp(32px, 3.5vw, 48px);
  text-align: center;
}
.contact-form__title {
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: clamp(30px, 3.3vw, 50px);
  line-height: 1;
  letter-spacing: -0.04em;
  color: var(--purple);
}
.contact-form__lede { font-size: clamp(17px, 1.32vw, 20px); }

.form {
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: clamp(24px, 3.2vw, 44px);
  background: rgba(255, 255, 255, .55);
  backdrop-filter: blur(22px) saturate(150%);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, .9);
  border-radius: clamp(18px, 2vw, 28px);
  /* the outer ring is what actually gives the panel an edge — a white border
     alone disappears against a light backdrop */
  box-shadow: 0 0 0 1px rgba(54, 3, 87, .05),
              0 30px 70px -40px rgba(54, 3, 87, .4),
              inset 0 1px 0 rgba(255, 255, 255, .85);
}
.form__row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
}

.field { display: flex; flex-direction: column; gap: 8px; min-width: 0; border: 0; padding: 0; margin: 0; }
.field__label {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 16px;
  letter-spacing: 0;
  color: var(--purple);
  padding: 0;
}
.field__label span { color: #b9506b; }

.field__input {
  width: 100%;
  padding: 14px 18px;
  font-family: var(--font-serif);
  font-size: 18px;
  letter-spacing: -0.02em;
  color: var(--ink);
  background: rgba(255, 255, 255, .55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(54, 3, 87, .12);
  border-radius: 12px;
  box-shadow: inset 0 1px 2px rgba(54, 3, 87, .05);
  transition: border-color .25s var(--ease), box-shadow .25s var(--ease),
              background-color .25s var(--ease);
}
/* the placeholder sits on a translucent field, so it needs to be darker than
   it would on flat white to stay legible */
.field__input::placeholder { color: #787d8a; }
.field__input:hover {
  background: rgba(255, 255, 255, .75);
  border-color: rgba(54, 3, 87, .28);
}
.field__input:focus {
  outline: none;
  background: rgba(255, 255, 255, .85);
  border-color: var(--purple);
  box-shadow: 0 0 0 3px rgba(54, 3, 87, .16);
}
.field__input--area { resize: vertical; min-height: 150px; line-height: 1.4; }
.field__input--select {
  appearance: none;
  /* the caret is drawn here rather than shipped as an asset: it is a plain
     chevron, not artwork from the design system */
  background-image: linear-gradient(45deg, transparent 50%, var(--purple) 50%),
                    linear-gradient(135deg, var(--purple) 50%, transparent 50%);
  background-position: calc(100% - 22px) 50%, calc(100% - 15px) 50%;
  background-size: 7px 7px, 7px 7px;
  background-repeat: no-repeat;
  padding-right: 44px;
}

.field__input[aria-invalid="true"] { border-color: #b9506b; }
.field__error {
  font-family: var(--font-sans);
  font-size: 14px;
  letter-spacing: 0;
  color: #b9506b;
}

.field--choices { gap: 12px; }
.choices { display: flex; flex-wrap: wrap; gap: 10px; }
.choice {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border: 1px solid rgba(54, 3, 87, .12);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .5);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  font-family: var(--font-sans);
  font-size: 15px;
  letter-spacing: 0;
  cursor: pointer;
  transition: border-color .2s var(--ease), background-color .2s var(--ease), color .2s var(--ease);
}
.choice input { accent-color: var(--purple); width: 16px; height: 16px; }
.choice:hover { background: rgba(255, 255, 255, .78); border-color: var(--purple); }
.choice:has(input:checked) { background: var(--purple); border-color: var(--purple); color: #fff; }
.choice:has(input:focus-visible) { outline: 2px solid var(--purple); outline-offset: 2px; }

/* honeypot — off screen rather than display:none, which some bots detect */
.form__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 20px;
  margin-top: 8px;
}
.form__actions .btn { border: 0; }
.form__actions .btn[disabled] { opacity: .6; pointer-events: none; }

.form__status {
  font-size: 17px;
  letter-spacing: -0.02em;
  min-height: 1.2em;
}
.form__status[data-state="error"] { color: #b9506b; }
.form__status[data-state="ok"] { color: var(--purple); font-weight: 600; }

@media (max-width: 720px) {
  .form__row { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 620px) {
  .offices__grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 780px) {
  .offices { margin-top: -60px; padding-top: 120px; }
}

/* =========================================================================
   Work page (Figma 113:12688)
   The Figma frame leaves ~2000px of white between the intro and the call to
   action; the mosaic below fills it.
   ========================================================================= */

.work { padding: clamp(48px, 5.3vw, 80px) var(--gutter) clamp(72px, 7.9vw, 120px); }

/* A six-column grid with a repeating 4+2 / 2+4 / 3+3 rhythm. Every row is the
   same height, so tiles differ in width rather than in line — no masonry
   library, no ragged bottom edge, and it stays whole at any project count. */
/* Four columns on a short row unit, with every tile two rows tall. The cell
   shapes are chosen to sit close to the mockups' own proportions — the wide
   device renders are about 1.7:1, the phone ones about 1:1 — because the
   artwork is fitted with `contain` and any mismatch shows as dead grey. */
.work__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  /* rows size themselves, and items are not stretched to match their
     neighbours — see .work-card below */
  align-items: start;
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Each tile takes the proportions of its own render, so the artwork fills it
   exactly: nothing is cropped and there is no letterbox. That matters because
   the renders carry a soft vignette, so any flat colour behind a letterboxed
   image met it at a visible seam along the bottom — which read as the mockup
   being cut off. */
.work-card {
  aspect-ratio: var(--ar, 16 / 10);
  min-width: 0;
}
.work-card[data-shape="wide"] { grid-column: span 2; }
.work-card[data-shape="tall"] { grid-column: span 1; }

.work-card__tile {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  overflow: hidden;
  border-radius: var(--r-card);
  /* each mockup was rendered on its own grey — #E7E7E7 for the desktop ones,
     #D0D0D0 for the phones — so the cell takes that colour from the tile's
     own custom property. Any other value and the letterboxing below shows up
     as a seam around the artwork. */
  background: var(--tile-bg, #e9eaeb);
  cursor: pointer;
  text-align: left;
  transition: transform .5s var(--ease), box-shadow .5s var(--ease);
}
.work-card__tile img {
  width: 100%;
  height: 100%;
  /* the tile already has the image's aspect ratio, so cover fills it exactly
     — it has nothing to crop */
  object-fit: cover;
  transition: transform .7s var(--ease), filter .5s var(--ease);
}

.work-card__veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(200deg, rgba(54, 3, 87, .82), rgba(24, 1, 40, .92));
  opacity: 0;
  transition: opacity .45s var(--ease);
}

.work-card__label {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: clamp(18px, 2vw, 28px);
  color: #fff;
}
.work-card__kicker {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: .02em;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity .4s var(--ease) .05s, transform .4s var(--ease) .05s;
}
.work-card__name {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: clamp(18px, 1.6vw, 24px);
  line-height: 1.1;
  letter-spacing: -0.02em;
  transform: translateY(14px);
  opacity: 0;
  transition: opacity .4s var(--ease) .1s, transform .4s var(--ease) .1s;
}
.work-card__cue {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 13px;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .4s var(--ease) .16s, transform .4s var(--ease) .16s;
}

/* rollover — and the same state on keyboard focus, so the tile is not a
   mouse-only affordance */
.work-card__tile:hover,
.work-card__tile:focus-visible { transform: translateY(-6px); box-shadow: 0 30px 60px -34px rgba(54, 3, 87, .75); }
.work-card__tile:hover img,
.work-card__tile:focus-visible img { transform: scale(1.06); }
.work-card__tile:hover .work-card__veil,
.work-card__tile:focus-visible .work-card__veil { opacity: 1; }
.work-card__tile:hover .work-card__kicker,
.work-card__tile:hover .work-card__name,
.work-card__tile:hover .work-card__cue,
.work-card__tile:focus-visible .work-card__kicker,
.work-card__tile:focus-visible .work-card__name,
.work-card__tile:focus-visible .work-card__cue { opacity: 1; transform: none; }

/* =========================================================================
   Project detail modal
   ========================================================================= */
.modal {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  /* an implicit auto row is sized by its content, so the panel's
     max-height:100% resolved against its own height and never capped
     anything. A definite 1fr row is what gives that percentage something
     real to measure against. */
  grid-template-rows: minmax(0, 1fr);
  place-items: center;
  /* the extra top padding is what keeps the panel clear of the close button */
  padding: clamp(70px, 7vw, 92px) clamp(16px, 3vw, 40px) clamp(16px, 3vw, 40px);
}
.modal[hidden] { display: none; }

.modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(16, 1, 27, .72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity .35s var(--ease);
}

.modal__panel {
  position: relative;
  width: min(100%, 940px);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  /* glass, in step with the controls around it: light enough that the dark
     type on it keeps its contrast, and blurred so the page behind reads as
     depth rather than detail */
  background: rgba(255, 255, 255, .82);
  backdrop-filter: blur(26px) saturate(140%);
  -webkit-backdrop-filter: blur(26px) saturate(140%);
  border: 1px solid rgba(255, 255, 255, .55);
  border-radius: clamp(16px, 2vw, 28px);
  box-shadow: 0 60px 120px -50px rgba(0, 0, 0, .7),
              inset 0 1px 0 rgba(255, 255, 255, .6);
  opacity: 0;
  transform: translateY(24px) scale(.97);
  transition: opacity .4s var(--ease), transform .4s var(--ease);
}

.modal.is-open .modal__backdrop { opacity: 1; }
.modal.is-open .modal__panel { opacity: 1; transform: none; }

/* Same glass as the arrows, and positioned against the overlay rather than
   the panel — on the panel's white surface a translucent button has nothing
   to pick up and just looks washed out. */
.modal__close {
  position: absolute;
  top: clamp(12px, 2.2vw, 30px);
  right: clamp(10px, 2.4vw, 34px);
  z-index: 3;
  display: grid;
  place-items: center;
  width: clamp(42px, 3.2vw, 50px);
  aspect-ratio: 1;
  border-radius: 999px;
  color: #fff;
  background: rgba(255, 255, 255, .12);
  border: 1px solid rgba(255, 255, 255, .35);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transition: transform .3s var(--ease), background-color .25s var(--ease),
              border-color .25s var(--ease);
}
.modal__close:hover {
  transform: rotate(90deg);
  background: rgba(255, 255, 255, .22);
  border-color: #fff;
}
.modal__close:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
.modal__close span, .modal__close span::before {
  content: "";
  display: block;
  width: 15px;
  height: 1.5px;
  background: currentColor;
  border-radius: 2px;
  transform: rotate(45deg);
}
.modal__close span::before { transform: rotate(90deg); }

.modal__body {
  /* min-height:0 is what actually lets this shrink. A flex child refuses to
     go below its content size without it, so a tall project pushed the panel
     past its max-height and carried the Previous/Next bar off screen. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: clamp(24px, 3.2vw, 44px);
  padding-bottom: clamp(16px, 2vw, 24px);
}

.work-detail__kicker {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: .02em;
  color: #7b6a88;
  margin-bottom: 8px;
}
.work-detail__title {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: clamp(24px, 2.4vw, 36px);
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--purple);
  margin-bottom: 20px;
}
.work-detail__shot {
  width: 100%;
  height: auto;
  /* the tall phone mockups would otherwise fill the dialog and push the
     description out of sight; contain keeps the whole mockup visible rather
     than cropping it to fit */
  max-height: 56vh;
  object-fit: contain;
  border-radius: var(--r-card);
  background: var(--shot-bg, #e9eaeb);
  margin-bottom: 24px;
}
.work-detail__body { font-size: clamp(17px, 1.32vw, 20px); }

.work-detail__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 40px;
  margin: 24px 0 0;
  padding-top: 20px;
  border-top: 1px solid rgba(54, 3, 87, .16);
}
.work-detail__meta div { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.work-detail__meta dt {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #8a8a93;
}
.work-detail__meta dd { margin: 0; font-size: 17px; overflow-wrap: anywhere; }
.work-detail__meta a { color: var(--purple); text-decoration: underline; text-underline-offset: 3px; }

/* Round arrows flanking the panel, in place of the bar that used to sit under
   it. They are positioned against .modal, not the panel, so the panel keeps
   its full width and the arrows never overlap the artwork. */
.modal__arrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  display: grid;
  place-items: center;
  width: clamp(44px, 3.6vw, 56px);
  aspect-ratio: 1;
  border-radius: 999px;
  color: #fff;
  background: rgba(255, 255, 255, .12);
  border: 1px solid rgba(255, 255, 255, .35);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transform: translateY(-50%);
  transition: background-color .25s var(--ease), border-color .25s var(--ease),
              transform .25s var(--ease);
}
.modal__arrow--prev { left: clamp(10px, 2.4vw, 34px); }
.modal__arrow--next { right: clamp(10px, 2.4vw, 34px); }
.modal__arrow svg { width: 42%; height: 42%; }

.modal__arrow:hover {
  background: rgba(255, 255, 255, .22);
  border-color: #fff;
}
.modal__arrow--prev:hover { transform: translateY(-50%) translateX(-3px); }
.modal__arrow--next:hover { transform: translateY(-50%) translateX(3px); }
.modal__arrow:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* the counter keeps its place under the panel, on the backdrop */
.modal__count {
  position: absolute;
  left: 50%;
  bottom: clamp(10px, 2vw, 22px);
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 13px;
  color: rgba(255, 255, 255, .75);
}

/* Between about 700 and 1100px the panel is wide enough to sit under the
   arrows, leaving only a sliver of them showing. Narrowing the panel is the
   fix that keeps the flanking arrangement intact. */
@media (max-width: 1100px) {
  .modal__panel { width: min(100% - 132px, 940px); }
}

/* On a phone there is no width to give away, so the arrows drop to the bottom
   corners either side of the counter and the panel takes the full width. */
@media (max-width: 700px) {
  .modal__panel { width: min(100%, 940px); }
  .modal__arrow { top: auto; bottom: clamp(6px, 2vw, 16px); transform: none; }
  .modal__arrow--prev { left: 16px; }
  .modal__arrow--next { right: 16px; }
  .modal__arrow--prev:hover, .modal__arrow--next:hover { transform: none; }
  /* the panel must stop short of them */
  .modal { padding-bottom: 74px; }
}

/* the page must not scroll behind an open modal */
body.is-locked { overflow: hidden; }

/* ---------- responsive -------------------------------------------------- */
@media (max-width: 1000px) {
  /* two up: one wide tile per row, phones paired */
  .work__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .work-card[data-shape="wide"] { grid-column: span 2; }
  .work-card[data-shape="tall"] { grid-column: span 1; }
}
@media (max-width: 620px) {
  .work__grid { grid-template-columns: minmax(0, 1fr); gap: 30px; }
  .work-card[data-shape="wide"], .work-card[data-shape="tall"] { grid-column: span 1; }

  /* the first tile was sitting right under the filter. The space goes on the
     grid, not on the count: an empty element self-collapses and throws its
     own margins away. */
  .work__grid { margin-top: 26px; }

  /* There is no hover on touch, so the label is always up — and laid over the
     artwork it hid the very mockup it is describing. On small screens the
     card becomes a plain stack instead: artwork, then its label underneath. */
  .work-card { aspect-ratio: auto; }
  /* the label is a child of the button, so the button has to stop clipping —
     with overflow hidden it swallowed the text entirely. The ratio moves to
     the image, which is now the only thing that needs it. */
  .work-card__tile {
    aspect-ratio: auto;
    height: auto;
    overflow: visible;
    /* the button's own grey ran behind the label and ended flush under the
       last line, which read as the card being cut off. The colour belongs to
       the artwork, so it moves to the image. */
    background: none;
    border-radius: 0;
  }
  .work-card__tile img {
    aspect-ratio: var(--ar, 16 / 10);
    height: auto;
    background: var(--tile-bg, #e9eaeb);
    border-radius: var(--r-card);
  }
  .work-card__veil { display: none; }
  .work-card__label {
    position: static;
    /* flush with the artwork's own left edge — a few pixels of inset here
       reads as a misalignment rather than as padding */
    padding: 18px 0 0;
    gap: 6px;
    color: var(--ink);
  }
  .work-card__kicker, .work-card__name, .work-card__cue { opacity: 1; transform: none; }
  .work-card__kicker { color: #7b6a88; }
  .work-card__name { color: var(--purple); }
  .work-card__cue { color: var(--purple); }

}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .modal__panel { background: #fff; }
}
@media (prefers-reduced-transparency: reduce) {
  .modal__panel {
    background: #fff;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .work-card__tile, .work-card__tile img, .work-card__veil,
  .work-card__kicker, .work-card__name, .work-card__cue,
  .modal__backdrop, .modal__panel, .modal__close, .modal__arrow { transition: none; }
  .modal__panel { transform: none; }
}

/* stepping between projects — the panel slides out the way it is going and
   the next one arrives from the other side */
.modal__body { transition: opacity .2s var(--ease), transform .2s var(--ease); }
.modal__body.is-out-left  { opacity: 0; transform: translateX(-28px); }
.modal__body.is-out-right { opacity: 0; transform: translateX(28px); }
.modal__body.is-in-left,
.modal__body.is-in-right  { transition: none; opacity: 0; }
.modal__body.is-in-left   { transform: translateX(-28px); }
.modal__body.is-in-right  { transform: translateX(28px); }

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


/* =========================================================================
   Go to top
   Lives in the footer partial, so it is on every page. Hidden until there is
   something to go back up to.
   ========================================================================= */
.to-top {
  position: fixed;
  right: clamp(16px, 2.4vw, 32px);
  bottom: clamp(16px, 2.4vw, 32px);
  z-index: 60;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 999px;
  background: var(--purple);
  color: #fff;
  box-shadow: 0 14px 30px -14px rgba(54, 3, 87, .8);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .35s var(--ease), transform .35s var(--ease),
              background-color .25s var(--ease);
  pointer-events: none;
}
.to-top[hidden] { display: none; }
.to-top.is-on { opacity: 1; transform: none; pointer-events: auto; }
.to-top:hover { background: #4a0a74; transform: translateY(-3px); }

/* a chevron, drawn rather than shipped as an asset — it is two rules of CSS
   and no extra request */
.to-top span {
  display: block;
  width: 11px;
  height: 11px;
  border-left: 2px solid currentColor;
  border-top: 2px solid currentColor;
  transform: rotate(45deg) translate(1px, 1px);
}

@media (prefers-reduced-motion: reduce) {
  .to-top { transition: opacity .2s linear; transform: none; }
  .to-top.is-on { transform: none; }
  .to-top:hover { transform: none; }
}

@media (max-width: 620px) {
  .to-top { width: 42px; height: 42px; }
  /* the button is fixed to the bottom right and the bar is the last thing on
     the page, so the bar needs room or the two sit on top of each other */
  .footer__bar { padding-bottom: 56px; }
}

/* Where the browser cannot blur a backdrop, translucent panels just look
   washed out — so fall back to solid surfaces rather than shipping a
   half-finished effect. Same for anyone who has asked for less transparency. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .form { background: #fff; }
  .field__input, .choice { background: #fff; border-color: #d7d9e0; }
}
@media (prefers-reduced-transparency: reduce) {
  .form { background: #fff; backdrop-filter: none; -webkit-backdrop-filter: none; }
  .field__input, .choice {
    background: #fff;
    border-color: #d7d9e0;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .contact-form::before, .contact-form::after { display: none; }
}

/* =========================================================================
   Work filter
   ========================================================================= */
.work__filter {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-bottom: clamp(28px, 3vw, 44px);
}
.work__tab {
  padding: 11px 24px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(54, 3, 87, .18);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 13px;
  color: var(--purple);
  transition: background-color .25s var(--ease), color .25s var(--ease),
              border-color .25s var(--ease);
}
.work__tab:hover { background: rgba(54, 3, 87, .07); }
.work__tab[aria-selected="true"] {
  background: var(--purple);
  border-color: var(--purple);
  color: #fff;
}

/* filtered out: taken out of the grid entirely so the rows repack */
.work-card[hidden] { display: none; }
.work__count {
  margin: -28px 0 clamp(24px, 2.6vw, 36px);
  font-family: var(--font-mono);
  font-size: 13px;
  color: #8a8a93;
  text-align: center;
}

/* filter transition — see applyFilter() in script.js */
.work-card.is-leaving {
  opacity: 0;
  transform: scale(.94);
  transition: opacity .18s var(--ease), transform .18s var(--ease);
}
.work-card.is-entering {
  opacity: 0;
  transform: scale(.94);
}
/* entering tiles are released a frame later, so they need a transition ready
   to run when the class comes off */
.work-card {
  transition: opacity .42s var(--ease), transform .42s var(--ease);
}
.work__grid { transition: height .5s var(--ease); }
.work__grid.is-filtering { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .work-card, .work-card.is-leaving, .work-card.is-entering, .work__grid {
    transition: none;
  }
  .work__grid { height: auto !important; }
}

/* =========================================================================
   Contact hero illustration

   The export was flat, so tools/prepare_contact_hero.py wraps it into named
   parts. Each part is two nested groups: the outer one is moved by the
   pointer from script.js, the inner one carries the loop below. One element
   cannot hold two transforms.
   ========================================================================= */
#contact-hero { display: block; width: 100%; height: 100%; }

#contact-hero .nh-part {
  /* the pointer offset, written by script.js */
  transform: translate(var(--nh-x, 0px), var(--nh-y, 0px));
  transition: transform .9s cubic-bezier(.22, 1, .36, 1);
}
/* while the pointer is inside, the layers track it directly — the long
   transition above is what makes them settle back when it leaves */
#contact-hero.is-tracking .nh-part { transition: transform .35s linear; }

#contact-hero .nh-inner {
  transform-box: fill-box;
  transform-origin: center;
}

/* Ambient loops. Durations are deliberately not multiples of each other, so
   the scene never falls into step with itself. */
#nh-arrow    .nh-inner { animation: nh-drop 3.2s ease-in-out infinite; }
#nh-swirl    .nh-inner { animation: nh-orbit 18s ease-in-out infinite; }
#nh-platform .nh-inner { animation: nh-float 11s ease-in-out infinite; }
#nh-book     .nh-inner { animation: nh-float 9s  ease-in-out infinite -1.5s; }
#nh-magnifier .nh-inner { animation: nh-sway 8.5s ease-in-out infinite; }
#nh-envelope .nh-inner { animation: nh-float 7.5s ease-in-out infinite -3s; }
#nh-squares  .nh-inner { animation: nh-float 12s ease-in-out infinite -5s; }
#nh-cone     .nh-inner { animation: nh-float 6.5s ease-in-out infinite -2s; }
#nh-person   .nh-inner { animation: nh-bob 4.5s ease-in-out infinite; }
#nh-paper    .nh-inner { animation: nh-sway 10s ease-in-out infinite -4s; }
#nh-dots     .nh-inner { animation: nh-blink 4s steps(1, end) infinite; }

@keyframes nh-float { 0%, 100% { transform: translateY(0); }      50% { transform: translateY(-9px); } }
@keyframes nh-bob   { 0%, 100% { transform: translateY(0); }      50% { transform: translateY(-3px); } }
@keyframes nh-drop  { 0%, 100% { transform: translateY(-6px); }   55% { transform: translateY(10px); } }
@keyframes nh-sway  { 0%, 100% { transform: rotate(0deg); }       50% { transform: rotate(1.6deg); } }
@keyframes nh-orbit {
  0%, 100% { transform: rotate(0deg) translateY(0); }
  50%      { transform: rotate(2.5deg) translateY(-12px); }
}
@keyframes nh-blink { 0%, 74% { opacity: 1; } 78%, 82% { opacity: .35; } 86% { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  #contact-hero .nh-part { transition: none; transform: none; }
  #contact-hero .nh-inner { animation: none; }
}
