/* ============================================================
   Vantica — Main Stylesheet
   Stack: Vanilla CSS · Inter (text) + Newsreader (display)
   ============================================================ */

/* ============================================================
   Fonts — self-hosted. Both are variable; Newsreader's opsz axis is
   load-bearing, so do not substitute static instances.
   ============================================================ */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('../assets/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('../assets/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 300 500;
  font-display: swap;
  src: url('../assets/fonts/newsreader-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 300 500;
  font-display: swap;
  src: url('../assets/fonts/newsreader-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ============================================================
   Custom Properties
   ============================================================ */
:root {
  /* Typography — one serif for what is spoken, one sans for what is read */
  --font:         'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-display: 'Newsreader', Georgia, 'Times New Roman', serif;

  /* Spacing */
  --sp-xs:  0.5rem;
  --sp-sm:  1rem;
  --sp-md:  1.5rem;
  --sp-lg:  2.5rem;
  --sp-xl:  4rem;
  --sp-2xl: 6rem;

  /* Layout */
  --max-w:  1200px;
  --pad-x:  clamp(1.5rem, 5vw, 4rem);

  /* Motion */
  --ease:     cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --dur:      280ms;

  /* Nav */
  --nav-h: 72px;

  /* The page's one content grid: this plus a fluid column, --sp-xl apart.
     Peer grids use --sp-xl for two columns, --sp-lg for three or more. */
  --grid-side: 360px;

  /* Four steps. Nothing set in --font-display may use anything else. */
  --display-1: clamp(2.75rem, 6.5vw, 6rem);      /* hero, once per page   */
  --display-2: clamp(2rem, 4vw, 3.25rem);        /* section headings      */
  --display-3: clamp(1.875rem, 3.2vw, 2.75rem);  /* closing statement,
                                                    calculator figure     */
  --display-4: clamp(1.625rem, 2.6vw, 2.125rem); /* row + column headings,
                                                    figures, client quote */
}

/* ============================================================
   The two grounds
   Black is company-facing, white is talent-facing, and the switch
   between them is the only signal of a change of audience.

   Every colour lives HERE, on the band, never on :root — a component
   has to work on either ground without knowing which it is on, so it
   only ever references --fg / --muted / --line and so on. Declaring a
   colour at :root would break the light band; declaring one that
   resolves `var(--fg)` above the band would silently compute to
   nothing, because var() inside a custom property is substituted where
   it is declared, not where it is used.

   The two alphas are not symmetric: black text on white needs more
   weight than white text on black for the same contrast ratio.
   ============================================================ */
.skip-link,
.nav,
.consent,
.hero,
.section--dark,
.footer {
  --bg:      #050505;
  --surface: #101010;
  --fg:      #ffffff;
  --muted:   rgba(255, 255, 255, 0.66);   /* 8.8:1  body */
  --subtle:  rgba(255, 255, 255, 0.52);   /* 5.6:1  labels, captions */
  --faint:   rgba(255, 255, 255, 0.30);   /* 2.5:1  decorative only */
  --line:    rgba(255, 255, 255, 0.18);
  /* Softer than --line: the nav sits on a scrim, and over the light section
     the full value reads as a bright seam. */
  --nav-line: rgba(255, 255, 255, 0.09);
}

.section--light {
  --bg:      #fbfaf8;
  --surface: #f1efea;
  --fg:      #0a0a0a;
  --muted:   rgba(10, 10, 10, 0.68);      /* 7.7:1  body */
  --subtle:  rgba(10, 10, 10, 0.59);      /* 5.2:1  labels, captions */
  --faint:   rgba(10, 10, 10, 0.34);      /* 2.4:1  decorative only */
  --line:    rgba(10, 10, 10, 0.16);
}

/* ============================================================
   Reset
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

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

/* body and .grain are the only elements outside a ground, so they carry
   literals. These must stay equal to --bg / --fg on the dark band. */
body {
  font-family: var(--font);
  background: #050505;
  color: #ffffff;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img   { max-width: 100%; height: auto; display: block; }
a     { color: inherit; text-decoration: none; }
ul    { list-style: none; }
button { font-family: var(--font); }
/* Browser default is 700; the Inter scale stops at 600 */
strong { font-weight: 600; }

/* ============================================================
   Grain overlay — fixed, covers full page
   ============================================================ */
.grain {
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: 0.04;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.80' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 300px 300px;
}

/* ============================================================
   Utilities
   ============================================================ */
.container {
  max-width: var(--max-w);
  margin-inline: auto;
  padding-inline: var(--pad-x);
}

.mt-xl { margin-top: var(--sp-xl); }

.skip-link {
  position: absolute;
  top: 0;
  left: 50%;
  z-index: 200;
  transform: translate(-50%, -150%);
  padding: 0.75rem 1.25rem;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--bg);
  background: var(--fg);
  transition: transform var(--dur) var(--ease);
}
.skip-link:focus-visible { transform: translate(-50%, 0); outline: none; }

/* ============================================================
   Buttons
   ============================================================ */
/* Inverts against whichever ground it is on, so there is no light variant. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: 0.8rem 1.6rem;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1;
  background: var(--fg);
  color: var(--bg);
  border: 1px solid var(--fg);
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  transition:
    opacity   var(--dur) var(--ease),
    transform 150ms var(--ease);
}
.btn:hover  { opacity: 0.86; }
.btn:active { transform: scale(0.97); }
.btn:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

.btn--sm { padding: 0.55rem 1.15rem; }

/* ============================================================
   Label chip
   ============================================================ */
.label {
  display: inline-block;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--subtle);
  margin-bottom: var(--sp-md);
}

/* ============================================================
   Navigation
   ============================================================ */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--nav-h);
  border-bottom: 1px solid transparent;
  transition:
    background      var(--dur) var(--ease),
    backdrop-filter var(--dur) var(--ease),
    border-color    var(--dur) var(--ease);
}
.nav.scrolled {
  background: rgba(5, 5, 5, 0.88);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  border-bottom-color: var(--nav-line);
}

.nav__container {
  max-width: var(--max-w);
  margin-inline: auto;
  padding-inline: var(--pad-x);
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nav__logo {
  display: flex;
  align-items: center;
  transition: opacity var(--dur) var(--ease);
}
.nav__logo:hover { opacity: 0.75; }
.nav__logo:focus-visible { outline: 2px solid var(--fg); outline-offset: 4px; }

.nav__logo-img {
  height: 30px;
  width: auto;
  display: block;
  filter: brightness(0) invert(1);
}

.nav__links { display: flex; align-items: center; gap: var(--sp-lg); }

/* Hidden while the hero's own copy of the button is on screen.
   `visibility`, not just opacity, so it leaves the tab order. `translate`,
   not `transform`, so .btn:active's scale() still composes. The default is
   visible, so with no JS the button behaves normally. */
.nav.at-hero .nav__links .btn {
  opacity: 0;
  visibility: hidden;
  translate: 0 -6px;
}
/* Gated behind .is-ready, added one frame after the initial state is set.
   Without it the button paints at full opacity and visibly fades out on
   arrival: hiding must be instant on load, animated only on later scrolls. */
.nav.is-ready .nav__links .btn {
  transition:
    opacity    var(--dur) var(--ease),
    translate  var(--dur) var(--ease),
    visibility var(--dur) var(--ease);
}

.nav__link {
  position: relative;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--muted);
  transition: color var(--dur) var(--ease);
}
.nav__link:hover { color: var(--fg); }
.nav__link:focus-visible { outline: 2px solid var(--fg); outline-offset: 4px; }

/* Absolutely positioned so becoming current cannot change the link's height. */
.nav__link.is-current { color: var(--fg); }
.nav__link.is-current::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 1px;
  background: var(--fg);
}

/* Hamburger */
.nav__toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 6px;
}
.nav__toggle:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }
.nav__toggle span {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--fg);
  transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.nav__toggle.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav__toggle.open span:nth-child(2) { opacity: 0; transform: scaleX(0); }
.nav__toggle.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.nav__mobile {
  display: none;
  flex-direction: column;
  gap: var(--sp-md);
  padding: var(--sp-lg) var(--pad-x);
  background: rgba(5, 5, 5, 0.97);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  border-bottom: 1px solid var(--line);
}
.nav__mobile.open { display: flex; }

.nav__mobile-link {
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--muted);
  transition: color var(--dur) var(--ease);
}
.nav__mobile-link:hover { color: var(--fg); }
.nav__mobile-link.is-current { color: var(--fg); }

/* ============================================================
   Hero — editorial
   ============================================================ */
.hero {
  position: relative;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  background: var(--bg);
  padding-top: var(--nav-h);
  overflow: hidden;
}

/* Hero film. 22% opacity so the ground still wins. No CSS `filter` here —
   see the long comment on .hero__film-media below before adding one back. */
.hero__film {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

/* By class, not element, so <video> and the placeholder <canvas> match.
   Deliberately NOT `filter: grayscale(1) contrast(1.06)` any more, even
   though that's how this looked originally — a CSS `filter` on a <video>
   forces the browser to recomposite every decoded frame through a GPU
   shader instead of the hardware video-overlay path plain video normally
   gets, and was measured costing more GPU than a fullscreen 4K YouTube
   video despite being much smaller. `contrast(1.06)` is now baked into the
   asset's own pixels at generation time instead (see CLAUDE.md's Hero film
   section). Grayscale needed no baking: the asset only ever blends
   `#0b0b0b` and white, both already R=G=B, so it was already a no-op.
   `opacity` stays here in CSS — plain per-element alpha is a normal
   compositor operation most hardware overlay paths still support, unlike an
   arbitrary filter shader. If a future clip is ever substituted and isn't
   already neutral/contrast-adjusted, bake both into that file during its
   own preprocessing — do not put `filter` back here. */
.hero__film-media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 0.22;
}

/* Holds the reading column at the ground value whatever the clip does.
   Literals, not tokens: a color-mix() chain that failed to parse would drop
   the declaration and leave the headline on moving footage. They track the
   hero's own ground — if the hero moves off --bg, these move with it. */
.hero__film::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg, #050505 0%, rgba(5, 5, 5, 0.82) 38%, rgba(5, 5, 5, 0.35) 100%),
    radial-gradient(120% 90% at 50% 45%, transparent 40%, rgba(5, 5, 5, 0.85) 100%);
}

.hero__content {
  position: relative;
  z-index: 1;
  padding-inline: var(--pad-x);
  max-width: var(--max-w);
  margin-inline: auto;
  width: 100%;
  padding-block: var(--sp-2xl);
}

.hero__label {
  opacity: 0;
  animation: fade-up 0.7s var(--ease-out) 0.1s forwards;
}

.hero__headline {
  display: block;
  font-family: var(--font-display);
  font-weight: 300;
  font-size: var(--display-1);
  line-height: 1.06;
  letter-spacing: -0.01em;
  color: var(--fg);
  margin-bottom: var(--sp-lg);
}

.hero__line {
  display: block;
}

/* Keeps a word's chars together so the line wraps only at word boundaries. */
.hero-word {
  display: inline-block;
  white-space: nowrap;
}

/* Injected by main.js */
.hero-char {
  display: inline-block;
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity   0.65s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.65s cubic-bezier(0.16, 1, 0.3, 1);
  vertical-align: baseline;
  white-space: pre;
}
.hero-char.visible {
  opacity: 1;
  transform: translateY(0);
}

.hero__sub {
  font-size: clamp(0.875rem, 1.5vw, 1.0625rem);
  font-weight: 400;
  color: var(--muted);
  letter-spacing: 0.02em;
  margin-bottom: var(--sp-lg);
  opacity: 0;
  animation: fade-up 0.7s var(--ease-out) 0.9s forwards;
}

.hero__cta {
  opacity: 0;
  animation: fade-up 0.7s var(--ease-out) 1.05s forwards;
}

/* `translate`, not `transform`, so .btn:active's scale() still composes. */
@keyframes fade-up {
  from { opacity: 0; translate: 0 14px; }
  to   { opacity: 1; translate: 0 0; }
}

.hero__scroll {
  position: absolute;
  bottom: 2.25rem;
  left: 50%;
  transform: translateX(-50%);
  color: var(--faint);
  transition: color var(--dur) var(--ease);
  opacity: 0;
  /* Fade only; the bob is on the inner SVG to avoid a transform conflict */
  animation: hero-scroll-appear 0.6s var(--ease-out) 1.6s forwards;
}
.hero__scroll:hover { color: var(--fg); }
.hero__scroll svg   { animation: scroll-bob 2.4s ease-in-out 2.2s infinite; }

@keyframes hero-scroll-appear {
  to { opacity: 1; }
}

@keyframes scroll-bob {
  0%, 100% { transform: translateY(0); }
  50%       { transform: translateY(7px); }
}

/* ============================================================
   Why Vantica — figures in the side column, principles in the content
   column, on the page's one content grid (--grid-side / 1fr)
   ============================================================ */
.why {
  display: grid;
  grid-template-columns: var(--grid-side) minmax(0, 1fr);
  gap: var(--sp-xl);
}

.why__figures {
  display: flex;
  flex-direction: column;
  gap: var(--sp-lg);
}

.figure__value {
  display: flex;
  align-items: baseline;
  gap: 0.3rem;
}

.figure__num {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: var(--display-4);
  line-height: 1;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}

.figure__unit {
  font-size: 0.9375rem;
  font-weight: 400;
  color: var(--muted);
}

.figure__label {
  margin-top: 0.4rem;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--subtle);
}

.why__principles {
  display: flex;
  flex-direction: column;
}

.principle {
  padding-block: var(--sp-md);
  border-top: 1px solid var(--line);
}
.principle:first-child { padding-top: 0; border-top: 0; }

.principle__label {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg);
  margin-bottom: var(--sp-xs);
}

.principle__text {
  font-size: 0.9375rem;
  color: var(--muted);
  line-height: 1.6;
}

/* ============================================================
   Section base
   ============================================================ */
.section { padding-block: var(--sp-2xl); }
.section--dark,
.section--light { background: var(--bg); color: var(--fg); }

/* Tone is the only thing marking a section boundary — there is no full-width
   rule left on the page. The dark run alternates strictly:

     .hero bg · #companies surface · #offering bg · #testimonials surface
     · #team bg · #talents light · .footer bg

   Two invariants. A rule on top of a tonal step is redundant, and a rule
   between two blocks of the same tone splits one idea in half. The hero must
   stay on --bg: the nav's scrim composites to that value and sits over the
   hero for a full 100dvh, so a --surface hero puts a visible dark bar across
   the top of the first screen. See CLAUDE.md for the measurements. */
#companies,
#testimonials { background: var(--surface); }

/* Vertical rhythm, so nothing depends on margin collapsing:
     header -> first block  --sp-2xl
     block  -> block        --sp-xl, carried by the LOWER block, so a block
                            that owns a rule always has space above it
     block  -> section end  the section's own --sp-2xl padding */
.section__header {
  max-width: 760px;
  margin-bottom: var(--sp-2xl);
}
/* The header owns the gap below it; its last child must not add to it */
.section__header > :last-child { margin-bottom: 0; }

.section__title {
  font-family: var(--font-display);
  font-size: var(--display-2);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.005em;
  margin-bottom: var(--sp-md);
}

.section__lead {
  max-width: 560px;
  font-size: 1.0625rem;
  color: var(--muted);
  line-height: 1.75;
}

/* ============================================================
   Services — full-width rows, name-led
   Left column: the service name, set large in the display face. Right
   column: what it is, then what's included. Only ONE serif voice per
   row — the name and the explanation must not both be serif, or the
   eye can't tell which to read first.
   ============================================================ */
.service-list {
  display: flex;
  flex-direction: column;
}

.service-row {
  display: grid;
  grid-template-columns: var(--grid-side) minmax(0, 1fr);
  gap: var(--sp-xl);
  /* The row owns the space above its own rule */
  margin-top: var(--sp-lg);
  padding-top: var(--sp-lg);
  border-top: 1px solid var(--line);
}
.service-row:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.service-row__name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--display-4);
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--fg);
}

.service-row__price {
  margin-top: var(--sp-sm);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--fg);
}

/* The only 17px --fg text on the page, which is what ranks it above the
   section leads at 17px --muted. */
.service-row__lead {
  font-size: 1.0625rem;
  line-height: 1.65;
  color: var(--fg);
  margin-bottom: var(--sp-md);
}

.service-row__list {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

.service-row__list li {
  font-size: 0.875rem;
  color: var(--muted);
  padding-left: 1.125rem;
  position: relative;
  line-height: 1.55;
}
.service-row__list li::before {
  content: '—';
  position: absolute;
  left: 0;
  color: var(--subtle);
  font-size: 0.625rem;
  top: 0.4em;
}

/* ============================================================
   CTA Block
   ============================================================ */
.cta-block {
  margin-top: var(--sp-xl);
  padding-top: var(--sp-xl);
  border-top: 1px solid var(--line);
}

.cta-block__title {
  font-family: var(--font-display);
  font-size: var(--display-3);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.005em;
  margin-bottom: var(--sp-sm);
}
.cta-block__text  {
  font-size: 0.9375rem;
  color: var(--muted);
  max-width: 560px;
  margin-bottom: var(--sp-lg);
}

.cta-block__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
}

.cta-block__alt {
  font-size: 0.9375rem;
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color var(--dur) var(--ease);
}
.cta-block__alt:hover { color: var(--fg); }
.cta-block__alt:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

/* ============================================================
   Team — paired rows
   Two people per row, each a portrait beside its own text. The pair is a
   small version of the page's content grid; the outer grid is a peer grid,
   so --sp-xl, like .talent-cols and .footer__cols. It scales in pairs,
   which is what a growing team needs.
   ============================================================ */
.team-pairs {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-xl);
}

.team-pair {
  display: grid;
  grid-template-columns: 140px minmax(0, 1fr);
  gap: var(--sp-md);
  align-items: start;
}

/* Round, like the client portrait — the one softened shape in the system.
   Forced to greyscale regardless of the source photo, same reasoning as the
   customer logos: there is no accent colour on this page, and real headshots
   arrive in whatever colour clothing/background the photo happened to have. */
.team-pair__photo {
  width: 140px;
  height: 140px;
  border-radius: 50%;
  object-fit: cover;
  object-position: center top;
  background: var(--surface);
  filter: grayscale(1);
}

.team-pair__name {
  font-size: 1.125rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-bottom: 0.3rem;
}

/* Caption tier: 12px / 600 / 0.06em, as .story__role and .salary-calc__note */
.team-pair__role {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--subtle);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: var(--sp-sm);
}

.team-pair__bio {
  font-size: 0.9375rem;
  color: var(--muted);
  line-height: 1.68;
  margin-bottom: var(--sp-md);
}

.team-pair__linkedin {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--subtle);
  transition: color var(--dur) var(--ease);
}
.team-pair__linkedin:hover { color: var(--fg); }
.team-pair__linkedin:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

/* ============================================================
   Role switch — tabs to pick which talent role's panel is shown
   ============================================================ */
.role-switch {
  display: inline-flex;
  border: 1px solid var(--line);
}

.role-switch__btn {
  appearance: none;
  border: none;
  background: transparent;
  padding: 0.625rem 1.25rem;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.role-switch__btn:hover { color: var(--fg); }
.role-switch__btn + .role-switch__btn { border-left: 1px solid var(--line); }
.role-switch__btn.is-active {
  background: var(--fg);
  color: var(--bg);
}
.role-switch__btn:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 2px;
}

.role-panel { margin-top: var(--sp-lg); }

.role-panel__hint {
  font-size: 0.9375rem;
  color: var(--muted);
  max-width: 560px;
  margin-bottom: var(--sp-lg);
}

@media (max-width: 640px) {
  .role-switch { display: flex; width: 100%; }
  /* Tighter so all three labels stay on one line at 390px */
  .role-switch__btn {
    flex: 1 1 0;
    text-align: center;
    padding-inline: 0.5rem;
    font-size: 0.75rem;
    line-height: 1.2;
  }
}

/* ============================================================
   Salary calculator
   ============================================================ */
.salary-calc {
  background: var(--surface);
  border: 1px solid var(--line);
  padding: var(--sp-xl);
}

.salary-calc__header {
  margin-bottom: var(--sp-lg);
}

.salary-calc__title {
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--fg);
  margin-bottom: var(--sp-xs);
}

.salary-calc__sub {
  font-size: 0.9375rem;
  color: var(--muted);
}

.salary-calc__control {
  margin-bottom: var(--sp-lg);
}

/* Each label is centred on its own stop, not on a fifth of the row: the
   thumb's centre travels only between 11px (half its width) and 11px from
   the right edge, so space-between would put them at 0/25/50/75/100% instead.
   ASSUMES FIVE STOPS — matches every slider's min=1/max=5. Change the number
   of stops and the nth-child offsets below change with it. */
.salary-calc__day-labels {
  position: relative;
  height: 1.2em;
  margin-bottom: 0.75rem;
}

.salary-calc__day-labels span {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
}
.salary-calc__day-labels span:nth-child(1) { left: 11px; }
.salary-calc__day-labels span:nth-child(2) { left: calc(11px + (100% - 22px) / 4); }
.salary-calc__day-labels span:nth-child(3) { left: calc(11px + (100% - 22px) / 4 * 2); }
.salary-calc__day-labels span:nth-child(4) { left: calc(11px + (100% - 22px) / 4 * 3); }
.salary-calc__day-labels span:nth-child(5) { left: calc(100% - 11px); }

/* The filled portion is drawn here, not in JS: main.js sets only --pct, so
   both colours stay on the band. The fallback keeps the track correct with
   no JS. */
.salary-calc__slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 2px;
  background: linear-gradient(
    to right,
    var(--fg) 0 var(--pct, 0%),
    var(--line) var(--pct, 0%) 100%
  );
  outline: none;
  cursor: pointer;
  position: relative;
}

.salary-calc__slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 22px;
  height: 22px;
  background: var(--fg);
  border-radius: 50%;
  cursor: grab;
  transition: transform 150ms var(--ease);
}
.salary-calc__slider::-webkit-slider-thumb:active { cursor: grabbing; transform: scale(1.15); }

.salary-calc__slider::-moz-range-thumb {
  width: 22px;
  height: 22px;
  background: var(--fg);
  border: none;
  border-radius: 50%;
  cursor: grab;
}

.salary-calc__slider:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 4px;
}

.salary-calc__result {
  text-align: center;
  padding-top: var(--sp-lg);
  border-top: 1px solid var(--line);
}

.salary-calc__output {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: var(--display-3);
  color: var(--fg);
  line-height: 1;
  margin-bottom: 0.5rem;
  transition: opacity 0.12s var(--ease);
}
/* Dimmed while the value is debounced, so the number never flickers */
.salary-calc__output.is-updating { opacity: 0.4; }

.salary-calc__note {
  /* Caption tier: 12px / 600 / 0.06em, as .story__role and .team-pair__role */
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: var(--sp-sm);
}

.salary-calc__fine {
  font-size: 0.8125rem;
  color: var(--muted);
  max-width: 440px;
  margin-inline: auto;
  line-height: 1.6;
}

/* ============================================================
   Talents section
   ============================================================ */
.talent-cols {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-xl);
}

/* Label tier: 11px / 600 / 0.12em */
.talent-col__title {
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--fg);
  margin-bottom: var(--sp-md);
  padding-bottom: var(--sp-sm);
  border-bottom: 1px solid var(--line);
}

.talent-list { display: flex; flex-direction: column; gap: 0.625rem; }
.talent-list li {
  font-size: 0.9375rem;
  color: var(--muted);
  padding-left: 1.375rem;
  position: relative;
  line-height: 1.55;
}
/* Two-marker system: '—' describes, '✓' claims. See .talent-list--proof. */
.talent-list li::before {
  content: '—';
  position: absolute;
  left: 0;
  color: var(--muted);
  font-size: 0.8125rem;
  top: 0.1em;
}
.talent-list--proof li::before { content: '✓'; }

/* ============================================================
   Client stories — one featured quote at a time
   The section is built from type, a face and space: no card, no border,
   no bubble. The quote is the largest thing here on purpose, since it is
   the content.
   ============================================================ */
.story {
  /* Sits inside the page's side column rather than defining its own width,
     so the quote starts on the shared content axis */
  --story-portrait: 260px;
  --dot: 26px;
}

.story__stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* Floor for a very short quote; the real height guarantee below is what
     stops switching stories from jumping the section. */
  min-height: var(--story-portrait);
}

/* Every panel — hidden or not — shares this one cell (grid-area: 1 / 1
   below), so .story__stage's row is always sized to the TALLEST panel,
   not just whichever is currently shown. Real quotes vary a lot in length
   (Timo's two paragraphs against Mika's shorter one) and assuming every
   quote would stay shorter than the portrait broke the moment real copy
   replaced the uniform lorem-ipsum placeholders — this makes the height
   correct regardless of how long a future quote is, instead of re-tuning a
   fixed number by hand each time. */
.story__panel {
  grid-area: 1 / 1;
  display: grid;
  grid-template-columns: var(--grid-side) minmax(0, 1fr);
  gap: var(--sp-xl);
}
/* Hidden panels must stay in normal flow, not display:none, or they stop
   contributing to the stack's height the moment they're hidden — exactly
   the bug this whole rule exists to avoid. visibility:hidden still drops
   them from paint, pointer/keyboard interaction and the accessibility tree,
   which is everything the [hidden] attribute needs semantically; it just
   doesn't also collapse their box to zero like display:none would. */
.story__panel[hidden] {
  visibility: hidden;
  pointer-events: none;
}

.story__portrait {
  width: var(--story-portrait);
  height: var(--story-portrait);
  border-radius: 50%;
  object-fit: cover;
  object-position: center top;
  background: var(--surface);
  filter: grayscale(1);
}

/* When a story has no client photo yet, its company's own logo sits in the
   same circle instead: contain (not cover) so the mark isn't cropped, and
   padding so it reads as a mark placed inside the circle rather than a
   cropped photo. Greyscale is already inherited from .story__portrait above.

   Needs its own hairline ring, unlike a real photo: #testimonials sits on
   --surface, and .story__portrait's own fill is --surface too — invisible
   for an opaque cover-fit photo, but a transparent contain-fit logo lets
   that identical fill show straight through, so the circle had no visible
   edge at all. A hairline is this system's standard way to mark a boundary
   without a box or shadow, so it's the one used here too. */
.story__portrait--logo {
  border: 1px solid var(--line);
  object-fit: contain;
  object-position: center;
  /* NOT a percentage: padding percentages resolve against the containing
     block's width, not this element's own box — on mobile that's the full
     stacked-panel width while --story-portrait has shrunk to 168px, so a
     22% padding consumed nearly the whole circle and the logo shrank to a
     sliver. Tying it to --story-portrait itself keeps it proportional to
     the box's own current size at any breakpoint. */
  padding: calc(var(--story-portrait) * 0.22);
  box-sizing: border-box;
}

/* Longer quotes render smaller, automatically — main.js sets --quote-chars
   to the quote's own text length. Below ~100 characters (a single punchy
   sentence, the original design target) this resolves to exactly
   --display-4, unchanged. Past that it scales down, floored at 1.375rem, so
   a much longer real quote (Timo's, 370 characters) reads as a deliberately
   smaller, still-balanced block instead of a wall of --display-4 serif type.
   This is a scoped, content-driven exception to "one display scale, four
   steps" — every OTHER use of --display-4 (figures, service-row names) is
   untouched; only .story__quote's own font-size varies by its own content.
   Without JS this falls back to --quote-chars: 90, i.e. exactly the old
   fixed --display-4 behaviour — nothing breaks, it just doesn't auto-size. */
.story__quote {
  font-family: var(--font-display);
  font-weight: 400;
  --quote-chars: 90;
  font-size: clamp(
    1.375rem,
    calc(var(--display-4) - max(0, var(--quote-chars) - 100) * 0.0028rem),
    var(--display-4)
  );
  line-height: 1.35;
  letter-spacing: -0.005em;
  color: var(--fg);
}

/* Only needed for a quote long enough to need a paragraph break (Timo's) —
   the global reset zeroes <p> margins, so without this two paragraphs sit
   flush against each other. */
.story__quote p + p {
  margin-top: 0.6em;
}

/* Quotation glyphs are small and sit high in their em box, so they need to
   be far larger than the text to register. In em, so they track the quote's
   clamp() instead of needing a second one. */
.story__mark {
  color: var(--subtle);
  font-size: 2.4em;
  /* 300 is the lightest weight the file carries; 200 silently clamped to it */
  font-weight: 300;
}

/* Its own line, so nothing hangs into the gutter — which would also break
   once the layout stacks. Both numbers were tuned by rendering: line-height
   crops the empty space under the glyph, margin closes the rest. */
.story__mark--open {
  display: block;
  line-height: 0.34;
  margin-bottom: -0.22em;
}

/* line-height:0 stops it growing the final line box; vertical-align drags it
   down from its ascender height so it reads as attached to the sentence. */
.story__mark--close {
  line-height: 0;
  vertical-align: -0.3em;
  margin-left: 0.02em;
}

.story__attribution {
  margin-top: var(--sp-md);
}

.story__name {
  display: block;
  /* <cite> carries the citation; its default italic is not wanted */
  font-style: normal;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--fg);
  letter-spacing: -0.01em;
}

.story__role {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--subtle);
}

/* On the quote's own content column, not centred */
.story__picker {
  display: flex;
  gap: var(--sp-xs);
  margin-top: var(--sp-lg);
  margin-left: calc(var(--grid-side) + var(--sp-xl));
}

/* The hit area is padded out vertically so a 2px rule stays tappable */
.story__pick {
  width: var(--dot);
  height: 32px;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}
.story__pick::before {
  content: '';
  width: var(--dot);
  height: 2px;
  background: var(--fg);
  /* Must clear 3:1 to be perceivable as a control */
  opacity: 0.42;
  transition: opacity var(--dur) var(--ease);
}
.story__pick:hover::before      { opacity: 0.75; }
.story__pick.is-active::before  { opacity: 1; }
.story__pick:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 0;
}

@media (max-width: 860px) {
  .story          { --story-portrait: 168px; }
  .story__panel   { grid-template-columns: 1fr; gap: var(--sp-lg); }
  .story__stage   { min-height: 0; }
  /* Stacked: the quote starts at the container edge, so do the rules */
  .story__picker  { margin-left: 0; }
}

/* ============================================================
   Logo strip — the second movement of #testimonials, not a band of its
   own. Sits on the standard --sp-xl block gap (.mt-xl), same rhythm as
   every other block on the page.
   ============================================================ */

/* Label tier, like every other sub-block heading. This tier carries two or
   three words — do not put a sentence back in it. */
.logo-strip__head {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--subtle);
  margin-bottom: var(--sp-md);
}

/* Fixed grid, not wrapping flex: a flex row wraps unevenly on wide viewports */
.logo-strip__logos {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  align-items: center;
  justify-items: start;
  gap: var(--sp-lg);
}

.logo-item {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

/* Forced to one grey. Left to their own luminance, four marks of different
   colours read as mush. invert(1) also hides white PNG backgrounds.

   Bounded on BOTH axes, not just height: the four real logos range from
   Alfame's ~6:1 wordmark to Hi Shine's ~1.2:1 mark, and height-only sizing
   let each one's width swing with its own aspect ratio — Alfame rendered
   ~230px wide next to Hi Shine's ~45px. Fitting every logo inside the same
   160×40 box (object-fit: contain) instead means only the box changes per
   logo's shape, not an arbitrary multiple of it. */
.logo-img {
  width: auto;
  height: auto;
  max-width: min(160px, 100%);
  max-height: 40px;
  object-fit: contain;
  display: block;
  filter: grayscale(1) brightness(0) invert(1);
  opacity: 0.55;
}

/* ============================================================
   Legal pages — privacy.html
   Built from the page's own grid: the heading in the side column,
   the prose in the content column, one hairline per section. The
   same shape as .service-row and .principle, so a legal document
   still reads as part of this site.
   ============================================================ */
.legal__row {
  display: grid;
  grid-template-columns: var(--grid-side) minmax(0, 1fr);
  gap: var(--sp-xl);
  margin-top: var(--sp-lg);
  padding-top: var(--sp-lg);
  border-top: 1px solid var(--line);
}
.legal__row:first-of-type {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.legal__heading {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg);
}

/* The page's reading measure. The 648px content column runs to ~88
   characters at 15px, which is too wide for sustained reading. */
.legal__body { max-width: 560px; }
.legal__body > * + * { margin-top: var(--sp-sm); }
.legal__body p,
.legal__body li {
  font-size: 0.9375rem;
  line-height: 1.75;
  color: var(--muted);
}
.legal__body strong { color: var(--fg); font-weight: 600; }
.legal__body a {
  color: var(--fg);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.legal__body a:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

.legal__meta {
  font-size: 0.8125rem;
  color: var(--subtle);
  margin-top: var(--sp-xl);
}

/* ============================================================
   Cookie consent
   main.js keeps this hidden until it has read a stored choice, and only
   shows it at all on the production hosts (or with ?analytics) — see
   ANALYTICS_HOSTS in main.js.
   ============================================================ */
.consent {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 150;
  background: var(--bg);
  border-top: 1px solid var(--line);
  padding-block: var(--sp-md);
}
/* An explicit display beats the UA stylesheet's [hidden] rule — the same trap
   as .story__panel. Without this the bar shows before a choice is read. */
.consent[hidden] { display: none; }

.consent__inner {
  max-width: var(--max-w);
  margin-inline: auto;
  padding-inline: var(--pad-x);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-lg);
  flex-wrap: wrap;
}

.consent__text {
  font-size: 0.8125rem;
  line-height: 1.7;
  color: var(--muted);
  max-width: 62ch;
}
.consent__text a {
  color: var(--fg);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.consent__text a:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

/* Both buttons are identical on purpose: consent is only valid if refusing
   is as easy as accepting. A legal constraint, not a style choice — do not
   demote Reject to a text link. */
.consent__actions {
  display: flex;
  gap: var(--sp-sm);
  flex-shrink: 0;
}

/* ============================================================
   Footer
   ============================================================ */
.footer {
  background: var(--bg);
  color: var(--fg);
  padding-block: var(--sp-2xl) var(--sp-lg);
}

.footer__top {
  display: grid;
  grid-template-columns: var(--grid-side) minmax(0, 1fr);
  gap: var(--sp-xl);
  padding-bottom: var(--sp-xl);
}

.footer__logo-img {
  height: 34px;
  width: auto;
  display: block;
  filter: brightness(0) invert(1);
}

.footer__cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-lg);
}

.footer__heading {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--subtle);
  margin-bottom: var(--sp-md);
}

.footer__link,
.footer__text {
  display: block;
  font-size: 0.9375rem;
  line-height: 1.7;
  color: var(--muted);
}

.footer__link { transition: color var(--dur) var(--ease); }
.footer__link:hover { color: var(--fg); }
.footer__link:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

.footer__bar {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-md);
  flex-wrap: wrap;
  padding-top: var(--sp-md);
  border-top: 1px solid var(--line);
  font-size: 0.8125rem;
  color: var(--subtle);
}

/* Legal furniture, not navigation. The section links were removed on
   purpose; the privacy link is required to be easily accessible. */
.footer__legal {
  display: flex;
  gap: var(--sp-md);
}
.footer__legal a,
.footer__legal button {
  font-family: var(--font);
  font-size: 0.8125rem;
  color: var(--subtle);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: color var(--dur) var(--ease);
}
.footer__legal a:hover,
.footer__legal button:hover { color: var(--fg); }
.footer__legal a:focus-visible,
.footer__legal button:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }
.footer__legal [hidden] { display: none; }

/* ============================================================
   Responsive — Tablet
   ============================================================ */
@media (max-width: 960px) {
  .logo-strip__logos { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 860px) {
  .service-row { grid-template-columns: 1fr; gap: var(--sp-md); }
  .why         { grid-template-columns: 1fr; gap: var(--sp-lg); }
  .talent-cols { grid-template-columns: 1fr; gap: var(--sp-lg); }
  .footer__top { grid-template-columns: 1fr; gap: var(--sp-lg); }
  .team-pairs  { grid-template-columns: 1fr; gap: var(--sp-lg); }
}

@media (max-width: 768px) {
  :root { --sp-2xl: 4rem; --sp-xl: 3rem; }

  .nav__links       { display: none; }
  .nav__toggle      { display: flex; }
  .nav__logo-img    { height: 20px; }
  .footer__logo-img { height: 28px; }

  .footer__cols { grid-template-columns: 1fr; gap: var(--sp-lg); }

  .legal__row { grid-template-columns: 1fr; gap: var(--sp-md); }
  .consent__inner { align-items: flex-start; }

  .salary-calc { padding: var(--sp-lg); }

}

@media (max-width: 480px) {
  :root { --sp-2xl: 3rem; --sp-xl: 2.5rem; --sp-lg: 2rem; }

  /* A smaller portrait rather than a stack: side by side keeps the section
     short, and 96px still leaves the bio about 30 characters a line */
  .team-pair        { grid-template-columns: 96px minmax(0, 1fr); }
  .team-pair__photo { width: 96px; height: 96px; }

  .logo-strip__logos { grid-template-columns: repeat(1, 1fr); }

  /* Lower floor so the €X — €Y range can fit one line on a phone */
  .salary-calc__output { font-size: clamp(1.5rem, 6.5vw, 2.75rem); }
}

/* ============================================================
   Reduced motion
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
  html { scroll-behavior: auto; }
  .hero__scroll svg { animation: none; }
  .hero__label, .hero__line, .hero__sub, .hero__cta, .hero__scroll {
    opacity: 1;
    animation: none;
  }
  .hero-char { opacity: 1; transform: none; transition: none; }
  /* main.js also removes the element outright */
  .hero__film-media { display: none; }
}
